Skip to content

implement a random graph generator : layer by layer method#6

Open
huggingstar wants to merge 2755 commits into
lingeringsocket:masterfrom
jgrapht:master
Open

implement a random graph generator : layer by layer method#6
huggingstar wants to merge 2755 commits into
lingeringsocket:masterfrom
jgrapht:master

Conversation

@huggingstar
Copy link
Copy Markdown

package dag.auxiliary;

import org.apache.commons.math3.distribution.NormalDistribution;
import org.jgrapht.DirectedGraph;
import org.jgrapht.Graph;
import org.jgrapht.VertexFactory;
import org.jgrapht.generate.GraphGenerator;
import org.jgrapht.graph.AbstractBaseGraph;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;



/**
 *
 * random graph generator : layer by layer generaotr
 * In the academic paper, it is shown below,
 * Algorithm:  Layer-by-Layer method.
 * Require: n, k, p ∈ N.
 * Distribute n vertices between k different sets enumerated as L1, . . . , Lk.
 * Let layer(v) be the layer assigned to vertex v.
 * Let M be an adjacency matrix n×n initialized as the zero matrix.
 * for all i = 1 to n do
 *     for all j = 1 to n do
 *         if layer(j) > layer(i) then
 *             if Random() < p then
 *                 M[i][j] = 1
 *             else
 *                 M[i][j] = 0
 * return a random DAG with k layers and n nodes
 *
 * the original algorithm indicates that a node in layer 2 may connect to nodes in layer 3
 * or layer 4 and so on.
 * I make some changes that the node only can connect to the nodes in the next layer.
 * for example, a node in layer 2 only can connect to the nodes in layer 3.
 *
 *     graph features:
 * 1. there is only one node in the first and the last layer, respectively.
 * 2. the only one node in the first layer connects to every node in the second layer.
 * 3. every node in the second to last layer connects to the only one node in the last layer.
 * 4. layers except the first layer and the last layer are called middle layers.
 * 5. Every node connect to the each node which is in next layer according to some probability.
 * 6. Every node in the middle layers has at least one predecessor and one successor.
 *
 * for example, there is a allocaion of nodes expressed in array.
 * int[][] allocation = {{0},                      // layer 0
 *                       {11, 12, 13, 14}},        // layer 1
 *                       {21, 22, 23, 24, 25},     // layer 2
 *                       {4}};                     // layer 3
 * the number of each entry is the node's name,
 * every one-dimensional array represents the content expressing the corresponding layer
 * the edges are like that,
 * 0 --> 11, 0 --> 12, 0 --> 13, 0 --> 14,
 * 11 --> 21, 11 --> 24,    (according to some probability)
 * 12 --> 22, 12 --> 23, 12 --> 25,     (according to some probability)
 * 13 --> 24, 13 --> 25       (according to some probability)
 * 14 --> 21, 14 --> 23     (according to some probability)
 * 21 --> 4,
 * 22 --> 4,
 * 23 --> 4,
 * 24 --> 4,
 * 25 --> 4,
 *
 */



/**
 * @param <V>
 * @param <E>
 */

public class LayerByLayerGenerator<V, E> implements GraphGenerator<V, E, V> {
    private static final boolean DEFAULT_ALLOW_LOOPS = false;


    private final int num_layer;    // 层数  the number of layer
    private final Random rng;
    private final int n;        // 节点数  the number of node
    private final double p;
    private final boolean loops;
    private HashMap<Integer, Integer> vertex_cost = new HashMap<>();    // every node has its cost
    private int total_cost = 0;
    // 每一层有哪些节点
    private int[][] detail;     // allocation of nodes

    /**
     * Create a new Layer-by-Layer random graph generator. The generator does not create self-loops.
     *
     * @param n the number of nodes
     * @param p the edge probability
     */
    public LayerByLayerGenerator(int n, double p, int num_layer) {
        this(n, p, new Random(), DEFAULT_ALLOW_LOOPS, num_layer);
    }


    /**
     * Create a new Layer-by-Layer random graph generator. The generator does not create self-loops.
     *
     * @param n    the number of nodes
     * @param p    the edge probability
     * @param seed seed for the random number generator
     */
    public LayerByLayerGenerator(int n, double p, long seed, int num_layer) {
        this(n, p, new Random(seed), DEFAULT_ALLOW_LOOPS, num_layer);
    }

    /**
     * Create a new Layer-by-Layer random graph generator.
     *
     * @param n     the number of nodes
     * @param p     the edge probability
     * @param seed  seed for the random number generator
     * @param loops whether the generated graph may create loops
     */
    public LayerByLayerGenerator(int n, double p, long seed, boolean loops, int num_layer) {
        this(n, p, new Random(seed), loops, num_layer);
    }

    /**
     * Create a new Layer-by-Layer random graph generator.
     *
     * @param n         the number of nodes
     * @param p         the edge probability
     * @param rng       the random number generator to use
     * @param loops     whether the generated graph may create loops
     * @param num_layer the number of layer
     */
    public LayerByLayerGenerator(int n, double p, Random rng, boolean loops, int num_layer) {
        if (n < 6) {
            // the number of node can not smaller than 6
            throw new IllegalArgumentException("节点个数不能小于 6 ");
        }
        this.n = n;
        if (p < 0.0 || p > 1.0) {
            throw new IllegalArgumentException("概率P在 (0, 1) 之间");
        }
        if (num_layer < 3 || num_layer > n) {

            throw new IllegalArgumentException("层数不能小于3,且层数不能大于节点数");
        }
        this.num_layer = num_layer;
        this.p = p;
        this.rng = rng;
        this.loops = loops;
    }

    /**
     * Generates a random graph based on the Layer-by-Layer model.
     *
     * @param target        the target graph
     * @param vertexFactory the vertex factory
     * @param resultMap     not used by this generator, can be null
     */
    @Override
    public void generateGraph(Graph<V, E> target, VertexFactory<V> vertexFactory, Map<String, V> resultMap) {
        // special case
        if (n == 0) {
            return;
        }

        // check whether to also create loops
        boolean createLoops = loops;
        if (createLoops) {
            if (target instanceof AbstractBaseGraph<?, ?>) {
                AbstractBaseGraph<V, E> abg = (AbstractBaseGraph<V, E>) target;
                if (!abg.isAllowingLoops()) {
                    throw new IllegalArgumentException(
                            "Provided graph does not support self-loops");
                }
            } else {
                createLoops = false;
            }
        }

        /**
         *  创建节点
         *  create vertices
          */
        // 节点从 0 开始计数
        // name of node start from number 0

        // 操作 HashMap<Integer, Integer> vertex_cost;
        // 设置 每个节点的 执行时间
        int previousVertexSetSize = target.vertexSet().size();
        HashMap<Integer, V> vertices = new HashMap<>(n);
        for (int i = 0; i < n; i++) {
            V v = vertexFactory.createVertex();
            target.addVertex(v);
            vertices.put(i, v);
            // 设置节点的 cost
            // set node's cost value
            int cost = getVertexCost();
            total_cost += cost;
            vertex_cost.put(i, cost);
        }

        if (target.vertexSet().size() != previousVertexSetSize + n) {
            throw new IllegalArgumentException(
                    "Vertex factory did not produce " + n + " distinct vertices.");
        }

        // check if graph is directed
        boolean isDirected = target instanceof DirectedGraph<?, ?>;

        // 确定各层节点数分布情况
        // specify the allocation of the number of node of each layer
        detail = quantifyAllocation(n, num_layer);


        /**
         * 创建边
         * create edge
         *
         * 一层中,一个节点至少要与下一层中的一个节点相连接
         */
        // 对每一层
        // for each layer
        for (int i = 0; i < detail.length - 1; i++) {
            // 判断当前层的下一层中的每个节点是否都至少有一个前驱节点
            // check whether the nodes in middle has at least one predecessor
            boolean[] hasPredecessor = new boolean[detail[i + 1].length];
            for (int index = 0; index < hasPredecessor.length; index++) {
                hasPredecessor[index] = false;
            }
            // 对该层中的每一个元素
            // for each entry in current layer
            for (int j = 0; j < detail[i].length; j++) {
                boolean hasSuccessor = false;
                int a = detail[i][j];
                V s = vertices.get(a);
                // 对下一层中的每一个元素
                // for each entry in next layer
                for (int k = 0; k < detail[i + 1].length; k++) {
                    int b = detail[i + 1][k];
                    V t = vertices.get(b);
                    if (a == b) {
                        if (!createLoops) {
                            // no self-loops`
                            continue;
                        }
                    }
                    if (i == 0 || i == detail.length - 2) {
                        // 第一层和第二层中的所有节点都连接
                        // there is only one node in the first layer and the last layer respectively
                        // the only node in the first layer connects to the every node in the second layer
                        // 倒数第二层和最有一层的所有节点都连接
                        // every node in the second last layer connects to the only one node in the last layer

                        target.addEdge(s, t);
                        hasSuccessor = true;
                        hasPredecessor[k] = true;
                    } else {
                        if (rng.nextDouble() < p) {
                            // 其他层中的节点按照概率进行连接
                            // nodes in other layers connect to the nodes in correspondingly next layer according to a certain probability p
                            // addEdge
                            target.addEdge(s, t);
                            hasSuccessor = true;
                            hasPredecessor[k] = true;
                        }
//                        target.addEdge(s, t);
                    }
                }
                // 如果这个节点没有连接下一层的任何一个节点
                // if the node has no successor
                if (!hasSuccessor) {
                    Random random = new Random();
                    int index = random.nextInt(detail[i + 1].length);
                    int b = detail[i + 1][index];
                    V t = vertices.get(b);
                    target.addEdge(s, t);
                }
            }
            // 处理没有前驱节点的节点
            // process the nodes which has no predecessor
            for (int index = 0; index < hasPredecessor.length; index++) {
                if (!hasPredecessor[index]) {
                    int[] current = detail[i];
                    Random r = new Random();
                    int position = r.nextInt(current.length);
                    int a = current[position];
                    V s = vertices.get(a);
                    int b = detail[i + 1][index];
                    V t = vertices.get(b);
                    target.addEdge(s, t);
                }
            }
        }

//        showGraphInfo();


    }


    /**
     * 确定每一层都有哪些节点
     * specify the allocation of nodes
     *
     * @param n
     * @param num_layer
     * @return
     */
    public int[][] quantifyAllocation(int n, int num_layer) {
        return quantifyAllocation(getNumOfNodeOfEachLayer(n, num_layer));
    }


    /**
     * 确定每一层都是哪些节点
     * determine which nodes are on each level
     *
     * @param allocation
     */
    public int[][] quantifyAllocation(int[] allocation) {
        int[][] result = new int[allocation.length][];
        int value = 0;
        for (int i = 0; i < allocation.length; i++) {
            int[] temp = new int[allocation[i]];
            for (int j = 0; j < allocation[i]; j++) {
                int store = value++;
                temp[j] = store;
            }
            result[i] = temp;
        }
        return result;
    }


    /**
     * 分层方式
     * the way of layering
     * 确定不同层中节点个数
     * specify the number of node in each layer
     * 第一层和最后一层都只有一个节点
     * only one node in the first layer
     * only one node in the last layer
     *
     * @param n
     * @param num_layer
     * @return
     */
    public int[] getNumOfNodeOfEachLayer(int n, int num_layer) {
        int[] result = new int[num_layer];
        result[0] = 1;
        result[num_layer - 1] = 1;
        int available_n = n - 2;
        int available_num_layer = num_layer - 2;
        int mean = available_n / available_num_layer;
        double standardDeviation = 2;
        NormalDistribution normalDistribution = new NormalDistribution(mean, standardDeviation);
//        System.out.println("概要\t\t" + "node : " + n + "\t layer number : " + num_layer + "\tmean : " + mean);

        // 对数组的前一半进行赋值
        // assign values to the first half of array
        for (int i = 1; i < (num_layer / 2); i++) {
            while (true) {
                int random = (int) normalDistribution.sample();
                if (random > 0 && random < mean * 2) {
                    result[i] = random;
                    break;
                }

            }
        }

        // 对数组的后一半进行赋值
        // assign values to the second half of array
        // 使用均值的2倍减去在前半部分对应位置的值,从而获得后半部分对应位置的值


        if (num_layer % 2 == 0) {
            for (int i = 1; i < num_layer / 2; i++) {
                result[num_layer - i - 1] = mean * 2 - result[i];
            }
        } else {
            for (int i = 1; i < (num_layer - 1) / 2; i++) {
                result[num_layer - i - 1] = mean * 2 - result[i];
            }
        }

        // 计算误差
        // calculate error
        int sum = 0;
        for (int c : result) {
            sum += c;
        }
        int error = n - sum;

        // 处理误差
        // deal with error
        if (num_layer % 2 == 0) {
            result[(num_layer - 2) / 2] += error;
        } else {
            result[(num_layer - 1) / 2] += error;
        }

        return result;
    }

    // 显示分层内容
    // demonstrate the result after the layering
    public void showLayerInfo() {
        System.out.println("\t\t\t分层方式  detail");
        int count = 0;
        for (int[] i : detail) {
            System.out.print("layer : " + count++ + "|\t");
            for (int each : i) {
                System.out.print(each + "\t");
            }
            System.out.println();
        }
        System.out.println();
    }



    // 显示所有节点的 cost
    // show all nodes' cost
    public void showAllCost() {
        for (Integer key : vertex_cost.keySet()) {
            System.out.println("vertex : cost\t\t\t" + key + " : " + vertex_cost.get(key));
        }
        System.out.println();
    }





    // 获取一个节点的 cost
    // get a node's cost
    private static int getVertexCost() {
        int period = getPeriod();
        int n = getNumOfVertex();
        int max = period / n;
        while (true) {
            int result = (int) (Math.random() * max);
            if (result > 0) {
                return result;
            }
        }
    }


    // 获取节点数, [1, 30]
    // get the number of node
    private static int getNumOfVertex() {
        int max = 30;
        while (true) {
            int n = (int) (Math.random() * max);
            if (n > 5) {
                return n;
            }
        }
    }


    // 获取周期, [100, 1000]
    // get the period value
    private static int getPeriod() {
        while (true) {
            int a = (int) (Math.random() * 1000);
            if (a >= 100 && a <= 1000) {
                return a;
            }
        }
    }


    // 获取 HashMap<Integer, Integer> vertex_cost;
    // get HashMap<Integer, Integer> vertex_cost;
    public HashMap<Integer, Integer> getVertexCostMap() {
        return vertex_cost;
    }


    // 获取总的 cost
    // get all nodes' cost
    public int getToalCost() {
        return total_cost;
    }


    // 显示 graph 的信息
    // show the graph information
    public void showGraphInfo() {
        System.out.println("------------ graph info from generator -------------------");
        showLayerInfo();
        showAllCost();
        System.out.println("------------- end graph info -----------------");
        System.out.println();
    }



}

jkinable and others added 30 commits March 14, 2021 18:39
…936)

* [Tour] simplifications and improvements of HamiltonianCycleAlgorithms

TwoOptHeuristicTSP:
	- use tour-copy free path swap
	- use VertexToIntegerMapping

NearestNeighborHeuristicTSP:
	- use array based data structure

PalmerHamiltonianCycle:
	- use array of vertices instead of left and right indices.
	- makes implementation more comprehensible, saves memory and makes it
faster

* Reduce number of changes

TwoOptHeuristicTSP: revert changes in fields and initialization
The intended change was already addressed with another PR.

NearestNeighborHeuristicTSP:
Also adjust the variable-names in NearestNeighborHeuristicTSP

* Add ArrayUtil class
* [SCC] Improvements of StrongConnectivityInspector-algorithms

GabowStrongConnectivityInspector:
- replace Vector by ArrayList
- create HashSets holding the SCC's vertices with the size of each SCC
and use Collections.singleton() for single vertex SCCs

- cosmetic changes:
  - simplify method signatures (just use graph field)
  - rename field "stack" to "S" to comply to paper of Gabow
  - use stack-methods (push()/pop()/peek()) instead of Dequeue methods
  - inline methods of VertexNumber (also could reduce Object creation)

KosarajuStrongConnectivityInspector:
- replace Vector by ArrayList

AbstractStrongConnectivityInspector:
- use edge-supplier
- create HashMap with expected size

StrongConnectivityAlgorithmTest:
- use parameterized JUnit runner to test different implementations
- simplify/unify graph creation and assertions/checks of computed SCCs
- add more test cases

* [Gabow-SCC] avoid Integer-objects and left behind initial node

- use one-based vertex numbers instead of zero-based. Using zero-based
numbers caused the first node of a DFS to be on the stacks twice and
also to remain on stack S and B after DFS has completed, if the first
node is part of an SCC. This also had the effect that cycles that
contain the initial node are detected not before a successor of the
initial node was explored. The example of a three vertex directed ring,
makes this issue clear.

- using one-based numbers allows to use VertexNumber objects in stack B
too. This avoids creation of Integer-objects (which are only cached up
to 127 by default. See Integer$IntegerCache). This is possible because
each vertex is on stack S (and B) at most once and has a unique number
(as long as it is on S).

* [GabowStrongConnectivityInspector] prefix stack-fields with "stack"

AbstractStrongConnectivityInspector use the class-argument constructor
again. The SupplierUtil now uses a performant and a proper
(serializable) supplier.

* Create HashSet with expected size and make constructor protected
* updated dependency versions

* deleted deprecated code

* fixed few javadoc issues

* updated history

* updated release doc

Co-authored-by: Joris Kinable <kinable@amazon.com>
* Fix warnings in tests

Replace deprecated code in order to fix deprecation warnings:
- use hamcreset.MatcherAssert.assertThat instead of
junit.Assert.asserThat
- use assertThrows instead of ExpectedException
- and many more like new Integer(int) oder new ModifiableInteger()

Use assertEquals(expected, actual) instead of
assertTrue(actual==expected) and fix argument order in calls
assertEquals (actual and expected was swapped)

Fix unused warnings by either removing the unused method/variable or by
adding an suppress warning annotation, if removal would disturb the code
structure.

* Revert fix that accidentally broke the tests
… part 1) (#1066)

* [Checkstyle] update to version 8.41 and update DTD-file links

* [CheckStyle] enforce Variable/Method/Type-name conventions for non API

Add variable/method/type naming-convention checkstyle rules.

Fix all variable/method/type naming-convention violations that do not
require modifications of the public API.

* [CheckStyle] suppress Variable/Method/Type-name rules in API elements

Suppress all Variable/Method/Type name violations on API elements.
Fixing those rules requires API modifications.

In case of (static) fields, the fields are already deprecated and a
replacement is provided.

org.jgrapht.nio.DefaultAttribute : make static field 'NULL' final. This
is actually an API break, but I assume it was never intended to make it
modifiable and it was not-final by accident.
* [TSPLIBImporter] Consider multi-space delimiters (#1060)

+ adjust the tests to cover this case

+ use FileReader in GraphImporter

* [TSPLIBImporter] ignore everything after whitespace for metadata values

* Fix accidentally wrong input-data for tests

* Replace trim() by strip() to remove all leading/trailing whitespace

trim() only removes spaces, strip() removes all whitespace.
Allowed are the characters in the Unicode-blocks BASIC_LATIN (equal to
ASCII) and LATIN_1_SUPPLEMENT.
dependabot Bot and others added 30 commits April 11, 2026 23:22
* Bump org.junit.jupiter:junit-jupiter from 5.10.1 to 6.0.3

Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework) from 5.10.1 to 6.0.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](junit-team/junit-framework@r5.10.1...r6.0.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Remove outdated JUnit version from pom.xml

Removed outdated JUnit version entries from pom.xml.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Sichi <jsichi@gmail.com>
* deprecate(util): Deprecate CollectionUtil new*WithExpectedSize methods

These methods are superseded by JDK 19 factory methods HashMap.newHashMap(int),
HashSet.newHashSet(int), LinkedHashMap.newLinkedHashMap(int), and
LinkedHashSet.newLinkedHashSet(int) which accept an expected size directly.

* refactor(util): Replace CollectionUtil.new*WithExpectedSize with JDK 19 factory methods in utility and traversal classes

* refactor(core): Replace all CollectionUtil.new*WithExpectedSize with JDK 19 factory methods

Migrate all 64 call sites across generators, coloring, connectivity, cycle,
flow, isomorphism, matching, shortest path, spanning, tour, and clique
packages to use HashMap.newHashMap(int), HashSet.newHashSet(int),
LinkedHashMap.newLinkedHashMap(int), and LinkedHashSet.newLinkedHashSet(int).

* refactor(core): Apply Java 16 pattern matching instanceof across codebase

Apply pattern matching to equals() methods in Pair, Triple, UnorderedPair,
ModifiableInteger, GraphWalk, WeightedUnmodifiableSet, RatioVertex,
ColorRefinementIsomorphismInspector, and AhujaOrlinSharmaCyclicExchange.
Also apply to non-equals() sites in UniformIntrusiveEdgesSpecifics,
WeightedIntrusiveEdgesSpecifics, CollectionUtil.getElement(), and
AhujaOrlinSharmaCapacitatedMinimumSpanningTree. Eliminates redundant
casts and @SuppressWarnings("unchecked") annotations.

* refactor(core): Replace Arrays.asList with List.of, use named threads, use Map.entry()

Replace Arrays.asList with List.of for fixed literal lists in cycle,
shortest path, partition, and GraphTests. Use named daemon platform
threads in ConcurrencyUtil. Replace AbstractMap.SimpleImmutableEntry
with Map.entry() in RandomRegularGraphGenerator.

---------

Co-authored-by: kinable <kinable@amazon.com>
* fix(io): Resolve 3 compiler warnings in jgrapht-io

- Remove 'transitive' from ANTLR requires in module-info.java
- Replace this.setParameter() with direct field access in GEXFExporter
  constructor to avoid this-escape warning
- Make implicit int-to-byte cast explicit in Graph6Sparse6Exporter

* refactor(demo): Extract KnightTour and TourType to own source file

Move KnightTour class and TourType enum (now nested inside KnightTour)
out of WarnsdorffRuleKnightTourHeuristic.java into KnightTour.java to
resolve auxiliary-class-access and module-accessibility warnings.

* fix(demo): Add private constructors to demo utility classes

Prevents default constructors from being exposed in the module's
public API. Affects: CompleteGraphDemo, DependencyDemo,
DirectedGraphDemo, GraphBuilderDemo, GraphMLDemo, LabeledEdges,
PerformanceDemo.

* fix(demo): Migrate JGraphXAdapterDemo from JApplet to JFrame

JApplet is deprecated for removal since Java 9. Rewrite the demo
as a standalone JFrame application. Also make KnightTour and its
inner classes public to match their use in public API signatures.
Update TourType references in test file.

* Fixed a ton of compile warnings

* Fixed javadoc

* Fixed more javadoc

---------

Co-authored-by: kinable <kinable@amazon.com>
Bumps [org.apfloat:apfloat](https://github.com/mtommila/apfloat) from 1.14.0 to 1.15.0.
- [Commits](mtommila/apfloat@1.14.0...1.15.0)

---
updated-dependencies:
- dependency-name: org.apfloat:apfloat
  dependency-version: 1.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.14.1 to 3.15.0.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](apache/maven-compiler-plugin@maven-compiler-plugin-3.14.1...maven-compiler-plugin-3.15.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-version: 3.15.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1322)

Bumps [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.6.1 to 3.6.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](apache/maven-shade-plugin@maven-shade-plugin-3.6.1...maven-shade-plugin-3.6.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1323)

Bumps [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 3.4.2 to 3.5.0.
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](apache/maven-jar-plugin@maven-jar-plugin-3.4.2...maven-jar-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-jar-plugin
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps `maven-surefire-plugin.version` from 3.5.4 to 3.5.5.

Updates `org.apache.maven.surefire:surefire-junit-platform` from 3.5.4 to 3.5.5

Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.4 to 3.5.5
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](apache/maven-surefire@surefire-3.5.4...surefire-3.5.5)

Updates `org.apache.maven.plugins:maven-failsafe-plugin` from 3.5.4 to 3.5.5
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](apache/maven-surefire@surefire-3.5.4...surefire-3.5.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.surefire:surefire-junit-platform
  dependency-version: 3.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
  dependency-version: 3.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [org.apache.commons:commons-text](https://github.com/apache/commons-text) from 1.14.0 to 1.15.0.
- [Changelog](https://github.com/apache/commons-text/blob/master/RELEASE-NOTES.txt)
- [Commits](apache/commons-text@rel/commons-text-1.14.0...rel/commons-text-1.15.0)

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-text
  dependency-version: 1.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
#1327)

Bumps [org.apache.maven.plugins:maven-release-plugin](https://github.com/apache/maven-release) from 3.1.1 to 3.3.1.
- [Release notes](https://github.com/apache/maven-release/releases)
- [Commits](apache/maven-release@maven-release-3.1.1...maven-release-3.3.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-release-plugin
  dependency-version: 3.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.10.3 to 2.11.0.
- [Release notes](https://github.com/xmlunit/xmlunit/releases)
- [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md)
- [Commits](xmlunit/xmlunit@v2.10.3...v2.11.0)

---
updated-dependencies:
- dependency-name: org.xmlunit:xmlunit-core
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Bump com.google.guava:guava from 33.3.1-jre to 33.6.0-jre

Bumps [com.google.guava:guava](https://github.com/google/guava) from 33.3.1-jre to 33.6.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-version: 33.6.0-jre
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pom.xml to remove failureaccess exclusion

Removed exclusion for 'failureaccess' from dependencies.

* Update pom.xml to add new exclusions

Added exclusions for error_prone_annotations and jsap in pom.xml.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Sichi <jsichi@gmail.com>
Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 13.4.0 to 13.4.2.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](checkstyle/checkstyle@checkstyle-13.4.0...checkstyle-13.4.2)

---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
  dependency-version: 13.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#1330)

Bumps [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/apache/maven-source-plugin/releases)
- [Commits](apache/maven-source-plugin@maven-source-plugin-3.3.1...maven-source-plugin-3.4.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits)

---
updated-dependencies:
- dependency-name: org.sonatype.central:central-publishing-maven-plugin
  dependency-version: 0.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…5.0 (#1332)

Bumps [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.3.1 to 3.5.0.
- [Release notes](https://github.com/apache/maven-resources-plugin/releases)
- [Commits](apache/maven-resources-plugin@maven-resources-plugin-3.3.1...maven-resources-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-resources-plugin
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [org.apfloat:apfloat](https://github.com/mtommila/apfloat) from 1.15.0 to 1.16.0.
- [Commits](mtommila/apfloat@1.15.0...1.16.0)

---
updated-dependencies:
- dependency-name: org.apfloat:apfloat
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…gable spur engines (#1338)

Add a new exact Yen-family implementation, BoundedPrunedYenKShortestPath,
alongside the existing YenKShortestPath, with two separable optimisations
behind a single public class:

1. SpurShortestPathEngine abstraction with two adapter back-ends:
   - DijkstraSpurEngine: thin adapter delegating to DijkstraShortestPath
     via MaskSubgraph for ban handling.
   - AStarSpurEngine: thin adapter delegating to AStarShortestPath via
     MaskSubgraph + an AStarAdmissibleHeuristic built from a one-time
     reverse-distance precomputation. The heuristic is admissible by
     construction (reverse distances are computed on the original graph,
     and removing vertices/edges only increases shortest-path distances),
     so A* remains exact under bans. No shortest-path logic is duplicated.

2. BoundedPrunedYenKShortestPath: bounded-pruned Yen driver that defers
   each spur as a SpurTask keyed by an admissible lower bound and only
   materialises tasks that could still beat the cheapest known candidate.
   Includes an exact impossible-spur skip that eliminates doomed tasks
   before any spur shortest-path query is issued (resolves the path-chain
   pathology cleanly: every spur on a single-path chain has its only
   outgoing edge banned by the Yen rule, so all n-1 spurs are skipped).

Defaults: the no-engine constructor uses DijkstraSpurEngine (mirrors the
spur step of the existing YenKShortestPath); AStarSpurEngine is opt-in
via the two-argument constructor and is recommended for dense graphs or
larger k. The existing YenKShortestPath is unchanged.

Tests (BoundedPrunedYenKShortestPathTest, 24 cases, JUnit 5):
- hand-built edge cases: negative-k, k=0, unreachable sink, source==sink,
  zero-weight edges, single-edge graph, k larger than total paths,
  large-k stress, negative-weight rejection
- explicit cross-engine smoke (Dijkstra + A* vs YenKShortestPath)
- property-style fuzz suites (~115 random graph configurations across
  random DAGs, random cyclic digraphs, and grids), every case asserting
  the same ordered path-weight sequence as YenKShortestPath
- impossible-spur skip behaviour assertions

Benchmark: BoundedPrunedYenKShortestPathPerformance is a JMH benchmark
at the canonical jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath
path, in the same style as KShortestPathsPerformance. On Gnp random
digraphs, BoundedPrunedYenKShortestPath with AStarSpurEngine measures
10.9x faster than YenKShortestPath at n=500 and 27.3x faster at n=1500
(tight measurement: -f 2 -wi 2 -i 8, Cnt=16). The Dijkstra-backed
bounded variant is slower on this workload, so the dense-random-graph
win comes from the A* spur engine; the bounded scheduling layer is
workload-conditional.

References (in BoundedPrunedYenKShortestPath javadoc):
- Yen, J. Y. (1971). Finding the k shortest loopless paths in a network.
- Martins, E. Q. V., & Pascoal, M. M. B. (2003). A new implementation
  of Yen's ranking loopless paths algorithm.
- Aljazzar, H., & Leue, S. (2011). K*: A heuristic search algorithm for
  finding the k shortest paths.

Discussed on jgrapht-dev before opening this PR:
https://groups.google.com/g/jgrapht-dev/c/JHFs5n7dMpI

Co-authored-by: Claude Sonnet 4.7 <noreply@anthropic.com>
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.19.1 to 1.19.3.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](sparklemotion/nokogiri@v1.19.1...v1.19.3)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.19.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1341)

* perf(shortestpath): avoid unused pathVertices set and use ArrayDeque in AllDirectedPaths

In AllDirectedPaths.generatePaths the per-pop pathVertices HashSet is
only consulted by the simple-path self-intersection filter, but it was
rebuilt unconditionally on every iteration of the main loop. In
non-simple-paths mode the rebuild is wasted O(path length) work; guard
it behind the simplePathsOnly check. Java short-circuit evaluation
keeps the dereference safe.

Also switch the incompletePaths queue from LinkedList to ArrayDeque to
remove the per-node linked-list overhead. The queue is only used through
the Deque interface (poll / addFirst / add / isEmpty), which ArrayDeque
supports identically with better allocation behavior.

Pure refactor; no behavioral difference. Already covered by
testCycleBehavior on AllDirectedPathsTest, which exercises both
simplePathsOnly modes on a cyclic toy graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(shortestpath): add JMH bench for AllDirectedPaths non-simple mode

* test(shortestpath): expand AllDirectedPaths non-simple-mode coverage with brute-force oracle

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…(V) to run one source search (#1340)

* perf(shortestpath): use one Dijkstra in DijkstraManyToManyShortestPaths.getPaths

Override getPaths(V) on DijkstraManyToManyShortestPaths to run a single
DijkstraClosestFirstIterator over the source via getShortestPathsTree,
instead of inheriting the BaseManyToManyShortestPaths fallback that calls
getPath(source, v) once per vertex of the graph (which re-runs Dijkstra
from the same source via getManyToManyPaths each time, an
O(|V| * (V log V + E)) cost for what should be O(V log V + E)).

The base-class fallback is preserved unchanged for
DefaultManyToManyShortestPaths (which would otherwise bypass the
user-supplied algorithm function) and CHManyToManyShortestPaths (which
would otherwise bypass its contraction-hierarchy preprocessing).

Adds regression tests:
- DijkstraManyToManyShortestPathsTest: oracle agreement vs DijkstraShortestPath
  on reachable/unreachable targets; precondition.
- DefaultManyToManyShortestPathsTest: function-spy proving the user-supplied
  algorithm function is consulted on getPaths(source).
- CHManyToManyShortestPathsTest: oracle agreement on getPaths(source) for
  every vertex of the test graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* perf(shortestpath): add JMH bench for DijkstraManyToManyShortestPaths.getPaths

* test(shortestpath): expand DijkstraManyToManyShortestPaths.getPaths coverage with fuzz + edge cases

* test(shortestpath): factor SingleSourcePaths assertCorrectPaths into shared base

Per code review on #1340: extract the SingleSourcePaths
oracle-agreement check into a new BaseManyToManyShortestPathsTest
.assertCorrectPaths(graph, paths, source, targets) helper, alongside
the existing ManyToManyShortestPaths variant.

Replaces the inline DijkstraShortestPath oracle loops in:
- CHManyToManyShortestPathsTest.testGetPathsMatchesDijkstraOracle
- DefaultManyToManyShortestPathsTest
  .testGetPathsConsultsProvidedAlgorithmFunction
- DijkstraManyToManyShortestPathsTest:
    * testGetPathsSingleSourceMatchesDijkstra
    * testGetPathsSingleVertexGraph
    * testGetPathsSourceHasNoOutgoingEdges
    * testGetPathsDisconnectedGraph
    * testGetPathsCyclicGraphWithSelfLoopOnSource
    * testGetPathsFuzzAgainstDijkstraOracle

The helper handles the unreachable-target contract (null path /
+Inf weight) so callers no longer hand-roll the per-target
null/non-null branches. testGetPathsCalledTwiceReturnsConsistentResults
keeps its bespoke pair-of-calls comparison since it does not verify
against an oracle.

Pure refactor. 40/40 m2m tests still pass; full jgrapht-core suite
6885/6885 green; checkstyle + javadoc clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* perf(shortestpath): add forward-pruning preprocessing to AllDirectedPaths

Before this change, `edgeMinDistancesBackwards` walked back from every target
and decorated every edge whose head was backward-reachable to a target within
the budget, including edges whose tail is not forward-reachable from any source
within the budget. Such edges cannot lie on any feasible source -> target walk
and would never be traversed by the forward enumeration in `generatePaths` in
either simple or non-simple mode, so decorating them is wasted preprocessing
work that the subsequent enumeration pays for again per outgoing edge.

This change adds a forward BFS from the source set (`vertexMinDistancesForwards`)
and threads its result into `edgeMinDistancesBackwards`. An edge (u, v) is
retained only when u is forward-reachable from some source and the sandwich
inequality holds:

    dF(u) + 1 + dB(v) <= maxPathLength

where dF(u) is the forward BFS distance from the source set to u and 1 + dB(v)
is the backward BFS distance from v to a target through this edge. Edges that
fail the sandwich cannot appear on any feasible source -> target walk within
the budget. The inequality is written as
`forwardOfSource > maxPathLength - childDistance` to avoid integer overflow at
extreme `maxPathLength` values; the BFS bounds guarantee
`childDistance <= maxPathLength` when `maxPathLength` is set, so the right-hand
side is non-negative.

The optimisation is enabled by default. A setter on the existing instance
exposes the toggle:

    public void setForwardPruning(boolean forwardPruning)
    public boolean isForwardPruning()

The two existing public constructors, `AllDirectedPaths(Graph)` and
`AllDirectedPaths(Graph, PathValidator)`, are unchanged. Callers that want the
historical backward-only preprocessing behaviour exactly can recover it with
`setForwardPruning(false)`. The prune is exact - it never drops an edge that
could lie on a feasible walk - so the produced path set is identical in both
modes. On graphs where the prune never fires (e.g. small dense strongly-
connected digraphs), enabling it adds the cost of one extra O(V + E) BFS per
`getAllPaths` call, which is dominated by the enumeration itself.

The class-level Javadoc is expanded to describe the two-phase preprocessing /
enumeration design and the forward-pruning preprocessing, so the IDE-visible
documentation explains when the toggle is useful without requiring the reader
to find the setter first.

Tests in `AllDirectedPathsTest` are extended with cases that exercise both
`forwardPruning=true` and `forwardPruning=false`:

  - `testTargetReachableButSourceUnreachableBranchIsIgnored` -
    canonical garden-vertex case
  - `testMultipleSourcesPartialGarden` - one source reaches target,
    another is isolated
  - `testUnboundedSimplePathsWithUnreachableGarden` -
    `maxPathLength=null` boundary
  - `testSourceEqualsTargetInDisconnectedGraph` - trivial zero-length walk
  - `testDenseStronglyConnectedLossCase` - bounded overhead, identical results
  - `testFuzzAgainstBruteForce` - 8 seeded random graphs, both modes, simple
    and non-simple, compared as path sets against a brute-force enumeration

Refs: https://groups.google.com/g/jgrapht-dev/c/fhhJk9FVFoo

Co-Authored-By: Claude <noreply@anthropic.com>

* perf(shortestpath): add JMH bench for AllDirectedPaths forward pruning

Two cell families, each parameterised over `forwardPruning` so the cost of
the optimisation is directly comparable to the historical preprocessing
path in the same JVM:

  - Win case: forward chain of `chainLen` vertices 0 -> 1 -> ... -> T plus a
    `gardenSize`-vertex source-disconnected garden whose every vertex has an
    edge into T. Simple-paths mode, `maxPathLength = chainLen + 5`. The
    garden is reachable backwards from T but no garden vertex is reachable
    forwards from the source, so when forward pruning is off the backward
    sweep marks every garden vertex.
  - Loss case: small dense strongly-connected digraph where F = B = V, so
    the forward BFS is pure overhead and the prune never drops anything.
    Bounds the regression cost when forward pruning is enabled on a graph
    it cannot help.

Sample numbers on JDK 21.0.10 (8 measurement iterations, 2s each):

  Win case (chainLen=20):
    gardenSize  off (ms/op)        on (ms/op)         speedup
    500         0.043 +/- 0.001    0.011 +/- 0.001    3.9x
    1000        0.080 +/- 0.002    0.015 +/- 0.001    5.3x
    2000        0.171 +/- 0.014    0.022 +/- 0.001    7.8x

  Loss case (n=20, maxPathLength=3):
    off: 0.137 +/- 0.008 ms/op   on: 0.141 +/- 0.003 ms/op (within noise)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Bumps [faraday](https://github.com/lostisland/faraday) from 2.14.1 to 2.14.2.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](lostisland/faraday@v2.14.1...v2.14.2)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.