diff --git a/jgalgo-core/src/main/java/com/jgalgo/gen/GnmBipartiteGraphGenerator.java b/jgalgo-core/src/main/java/com/jgalgo/gen/GnmBipartiteGraphGenerator.java
new file mode 100644
index 0000000000..e89cf2fa0d
--- /dev/null
+++ b/jgalgo-core/src/main/java/com/jgalgo/gen/GnmBipartiteGraphGenerator.java
@@ -0,0 +1,429 @@
+/*-
+ * Copyright 2023 Barak Ugav
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jgalgo.gen;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.BiFunction;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+import com.jgalgo.alg.BipartiteGraphs;
+import com.jgalgo.alg.VertexBiPartition;
+import com.jgalgo.gen.BipartiteGenerators.Direction;
+import com.jgalgo.graph.Graph;
+import com.jgalgo.graph.GraphBuilder;
+import com.jgalgo.graph.IntGraph;
+import com.jgalgo.graph.IntGraphBuilder;
+import com.jgalgo.graph.WeightsBool;
+import com.jgalgo.internal.util.Bitmap;
+import com.jgalgo.internal.util.IntAdapters;
+import com.jgalgo.internal.util.JGAlgoUtils;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+
+/**
+ * Generates a uniformly random bipartite graph among all graphs with \(n\) vertices and \(m\) edges.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices. The
+ * generator uses the \(G(n_1,n_2,m)\) model to generate a uniformly random bipartite graph among all graphs with
+ * \(n_1\) left vertices, \(n_2\) right vertices and \(m\) edges.
+ *
+ *
+ * Both undirected and directed graphs can be generated. If the graph is directed, there are three options for the
+ * considered edges between each pair of left and right vertices: edges in both directions, edge(s) from the left vertex
+ * to the right vertex, or edge(s) from the right vertex to the left vertex. If no parallel edges are generated
+ * ({@link #setParallelEdges(boolean)}) than at most a single edge is generated from the left to right vertex, and
+ * another one from right to left. See {@link #setDirectedAll()}, {@link #setDirectedLeftToRight()} and
+ * {@link #setDirectedRightToLeft()} for more details.
+ *
+ *
+ * The generated graph(s) will have vertex {@linkplain WeightsBool boolean weights} with key
+ * {@link BipartiteGraphs#VertexBiPartitionWeightKey} which is the partition of the vertices into the left and right set
+ * of vertices. The weight is set to {@code true} for vertices in the left set, and {@code false} for vertices in the
+ * right set. The {@link VertexBiPartition} can be created later using
+ * {@link BipartiteGraphs#getExistingPartition(Graph)}.
+ *
+ *
+ * By default, the generated graph(s) will be undirected without parallel edges. Self edges are never generated.
+ *
+ *
+ * For deterministic behavior, set the seed of the generator using {@link #setSeed(long)}.
+ *
+ *
+ * This generator is the bipartite version of {@link GnmGraphGenerator}.
+ *
+ * @see BipartiteGraphs
+ * @author Barak Ugav
+ */
+public class GnmBipartiteGraphGenerator implements GraphGenerator {
+
+ private final boolean intGraph;
+ private List leftVertices;
+ private List rightVertices;
+ private int m;
+ private BiFunction edgeBuilder;
+ private Direction direction = Direction.Undirected;
+ private boolean parallelEdges = true;
+ private Random rand = new Random();
+
+ private GnmBipartiteGraphGenerator(boolean intGraph) {
+ this.intGraph = intGraph;
+ }
+
+ /**
+ * Creates a new \(G(n_1,n_2,m)\) generator.
+ *
+ * @param the vertices type
+ * @param the edges type
+ * @return a new \(G(n_1,n_2,m)\) generator
+ */
+ public static GnmBipartiteGraphGenerator newInstance() {
+ return new GnmBipartiteGraphGenerator<>(false);
+ }
+
+ /**
+ * Creates a new \(G(n_1,n_2,m)\) generator for {@link IntGraph}.
+ *
+ * @return a new \(G(n_1,n_2,m)\) generator for {@link IntGraph}
+ */
+ public static GnmBipartiteGraphGenerator newIntInstance() {
+ return new GnmBipartiteGraphGenerator<>(true);
+ }
+
+ /**
+ * Set the vertices of the generated graph(s).
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * This method sets these two sets.
+ *
+ *
+ * If the generator is used to generate multiple graphs, the same vertex sets will be used for all of them.
+ *
+ * @param leftVertices the set of left vertices of the generated graph(s)
+ * @param rightVertices the set of right vertices of the generated graph(s)
+ */
+ @SuppressWarnings("unchecked")
+ public void setVertices(Collection leftVertices, Collection rightVertices) {
+ if (intGraph) {
+ this.leftVertices =
+ (List) new IntArrayList(IntAdapters.asIntCollection((Collection) leftVertices));
+ this.rightVertices =
+ (List) new IntArrayList(IntAdapters.asIntCollection((Collection) rightVertices));
+ } else {
+ this.leftVertices = new ObjectArrayList<>(leftVertices);
+ this.rightVertices = new ObjectArrayList<>(rightVertices);
+ }
+ }
+
+ /**
+ * Set the vertices set of the generated graph(s) from a supplier.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * This method sets these two sets.
+ *
+ *
+ * The supplier will be called exactly {@code leftVerticesNum+rightVerticesNum} times, and the same sets of vertices
+ * created will be used for multiple graphs if {@link #generate()} is called multiple times.
+ *
+ * @param leftVerticesNum the number of vertices in the left set
+ * @param rightVerticesNum the number of vertices in the right set
+ * @param vertexSupplier the supplier of vertices
+ */
+ @SuppressWarnings("unchecked")
+ public void setVertices(int leftVerticesNum, int rightVerticesNum, Supplier vertexSupplier) {
+ if (intGraph) {
+ IntList leftVertices = new IntArrayList(leftVerticesNum);
+ IntList rightVertices = new IntArrayList(rightVerticesNum);
+ IntSupplier vSupplier = IntAdapters.asIntSupplier((Supplier) vertexSupplier);
+ for (int i = 0; i < leftVerticesNum; i++)
+ leftVertices.add(vSupplier.getAsInt());
+ for (int i = 0; i < rightVerticesNum; i++)
+ rightVertices.add(vSupplier.getAsInt());
+ this.leftVertices = (List) leftVertices;
+ this.rightVertices = (List) rightVertices;
+ } else {
+ List leftVertices = new ObjectArrayList<>(leftVerticesNum);
+ List rightVertices = new ObjectArrayList<>(rightVerticesNum);
+ for (int i = 0; i < leftVerticesNum; i++)
+ leftVertices.add(vertexSupplier.get());
+ for (int i = 0; i < rightVerticesNum; i++)
+ rightVertices.add(vertexSupplier.get());
+ this.leftVertices = leftVertices;
+ this.rightVertices = rightVertices;
+ }
+ }
+
+ /**
+ * Set the number of edges and the edge supplier of the generated graph(s).
+ *
+ *
+ * The number of edges must be non-negative, and if parallel edges are not allowed, it must be at most \(n_1 \cdot
+ * n_2\) for undirected graphs and directed graphs in which only one direction is allowed
+ * ({@linkplain #setDirectedLeftToRight() left to right}, or {@linkplain #setDirectedRightToLeft() right to left}),
+ * and at most \(2 \cdot n_1 \cdot n_2\) for directed graphs in which both directions are allowed
+ * ({@linkplain #setDirectedAll() all directions}).
+ *
+ *
+ * The supplier will be called for any edge created, for any graph generated. This behavior is different from
+ * {@link #setVertices(int, int, Supplier)}, where the supplier is used to generate a set of vertices which is
+ * reused for any generated graph.
+ *
+ * @param m the number of edges
+ * @param edgeSupplier the edge supplier
+ */
+ public void setEdges(int m, Supplier edgeSupplier) {
+ Objects.requireNonNull(edgeSupplier);
+ setEdges(m, (u, v) -> edgeSupplier.get());
+ }
+
+ /**
+ * Set the number of edges and the edge builder function of the generated graph(s).
+ *
+ *
+ * The number of edges must be non-negative, and if parallel edges are not allowed, it must be at most \(n_1 \cdot
+ * n_2\) for undirected graphs and directed graphs in which only one direction is allowed
+ * ({@linkplain #setDirectedLeftToRight() left to right}, or {@linkplain #setDirectedRightToLeft() right to left}),
+ * and at most \(2 \cdot n_1 \cdot n_2\) for directed graphs in which both directions are allowed
+ * ({@linkplain #setDirectedAll() all directions}).
+ *
+ *
+ * The edge builder will be called for any edge created, for any graph generated. This behavior is different from
+ * {@link #setVertices(int, int, Supplier)}, where the supplier is used to generate a set of vertices which is
+ * reused for any generated graph.
+ *
+ * @param m the number of edges
+ * @param edgeBuilder the edge builder function
+ */
+ public void setEdges(int m, BiFunction edgeBuilder) {
+ if (m < 0)
+ throw new IllegalArgumentException("number of edges must be non-negative");
+ this.m = m;
+ this.edgeBuilder = Objects.requireNonNull(edgeBuilder);
+ }
+
+ /**
+ * Sets the generated graph(s) to be undirected.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * Calling this method will cause the generated graph(s) to be undirected, and a single edge between each pair of
+ * left and right vertices will considered and generated with probability \(p\). The maximum number of edges will be
+ * \(|U| \cdot |V|\).
+ *
+ *
+ * By default, the generated graph(s) is undirected.
+ *
+ * @see #setDirectedAll()
+ * @see #setDirectedLeftToRight()
+ * @see #setDirectedRightToLeft()
+ */
+ public void setUndirected() {
+ direction = Direction.Undirected;
+ }
+
+ /**
+ * Sets the generated graph(s) to be directed with edges in both directions.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * Calling this method will cause the generated graph(s) to be directed, and edges in both directions (from left
+ * vertices to right vertices and visa versa) may be generated. In case parallel edges are not allowed, the maximum
+ * number of edges will be \(2 \cdot |U| \cdot |V|\).
+ *
+ *
+ * By default, the generated graph(s) is undirected.
+ *
+ * @see #setUndirected()
+ * @see #setDirectedLeftToRight()
+ * @see #setDirectedRightToLeft()
+ */
+ public void setDirectedAll() {
+ direction = Direction.DirectedAll;
+ }
+
+ /**
+ * Sets the generated graph(s) to be directed with edges from left to right.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * Calling this method will cause the generated graph(s) to be directed, and only edges from left vertices to right
+ * vertices may be generated. In case parallel edges are not allowed, the maximum number of edges will be \(|U|
+ * \cdot |V|\).
+ *
+ *
+ * By default, the generated graph(s) is undirected.
+ *
+ * @see #setUndirected()
+ * @see #setDirectedAll()
+ * @see #setDirectedRightToLeft()
+ */
+ public void setDirectedLeftToRight() {
+ direction = Direction.DirectedLeftToRight;
+ }
+
+ /**
+ * Sets the generated graph(s) to be directed with edges from right to left.
+ *
+ *
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets \(U\) and \(V\) such that every
+ * edge connects a vertex in \(U\) to one in \(V\). The two sets are usually called the left and right vertices.
+ * Calling this method will cause the generated graph(s) to be directed, and only edges from right vertices to left
+ * vertices may be generated. In case parallel edges are not allowed, the maximum number of edges will be \(|U|
+ * \cdot |V|\).
+ *
+ *
+ * By default, the generated graph(s) is undirected.
+ *
+ * @see #setUndirected()
+ * @see #setDirectedAll()
+ * @see #setDirectedLeftToRight()
+ */
+ public void setDirectedRightToLeft() {
+ direction = Direction.DirectedRightToLeft;
+ }
+
+ /**
+ * Determine if the generated graph(s) will contain parallel-edges.
+ *
+ *
+ * Parallel edges are a set of edges that connect the same two vertices. By default, the generated graph(s) will
+ * contain parallel-edges.
+ *
+ * @param parallelEdges {@code true} if the generated graph(s) will contain parallel-edges, {@code false} otherwise
+ */
+ public void setParallelEdges(boolean parallelEdges) {
+ this.parallelEdges = parallelEdges;
+ }
+
+ /**
+ * Set the seed of the random number generator used to generate the graph(s).
+ *
+ *
+ * By default, a random seed is used. For deterministic behavior, set the seed of the generator.
+ *
+ * @param seed the seed of the random number generator
+ */
+ public void setSeed(long seed) {
+ rand = new Random(seed);
+ }
+
+ @Override
+ public GraphBuilder generateIntoBuilder() {
+ if (leftVertices == null)
+ throw new IllegalStateException("Vertices not set");
+ if (edgeBuilder == null)
+ throw new IllegalStateException("Number of edges and edge supplier were not set");
+
+ final int maxNumberOfEdges =
+ leftVertices.size() * rightVertices.size() * (direction == Direction.DirectedAll ? 2 : 1);
+ if (!parallelEdges && m > maxNumberOfEdges)
+ throw new IllegalArgumentException("number of edges must be at most " + maxNumberOfEdges);
+
+ GraphBuilder g;
+ if (intGraph) {
+ @SuppressWarnings("unchecked")
+ GraphBuilder g0 =
+ (GraphBuilder) (direction != Direction.Undirected ? IntGraphBuilder.newDirected()
+ : IntGraphBuilder.newUndirected());
+ g = g0;
+ } else {
+ g = direction != Direction.Undirected ? GraphBuilder.newDirected() : GraphBuilder.newUndirected();
+ }
+ g.expectedVerticesNum(leftVertices.size() + rightVertices.size());
+ g.expectedEdgesNum(m);
+
+ WeightsBool partition = g.addVerticesWeights(BipartiteGraphs.VertexBiPartitionWeightKey, boolean.class);
+ for (V v : leftVertices) {
+ g.addVertex(v);
+ partition.set(v, true);
+ }
+ for (V v : rightVertices) {
+ g.addVertex(v);
+ partition.set(v, false);
+ }
+
+ if (parallelEdges || m <= maxNumberOfEdges / 2) {
+ /* Start with an empty graph and add edges one by one */
+
+ LongSet edges = parallelEdges ? null : new LongOpenHashSet(m);
+ final int n1 = leftVertices.size(), n2 = rightVertices.size();
+ while (g.edges().size() < m) {
+ V u, v;
+ int uIdx, vIdx;
+ if (direction != Direction.DirectedRightToLeft
+ && (direction != Direction.DirectedAll || rand.nextBoolean())) {
+ /* left to right edge */
+ uIdx = rand.nextInt(n1);
+ vIdx = rand.nextInt(n2);
+ u = leftVertices.get(uIdx);
+ v = rightVertices.get(vIdx);
+ vIdx += n1;
+ } else {
+ /* right to left edge */
+ uIdx = rand.nextInt(n2);
+ vIdx = rand.nextInt(n1);
+ u = rightVertices.get(uIdx);
+ v = leftVertices.get(vIdx);
+ uIdx += n1;
+ }
+ if (parallelEdges || edges.add(JGAlgoUtils.longPack(uIdx, vIdx)))
+ g.addEdge(u, v, edgeBuilder.apply(u, v));
+ }
+
+ } else {
+ /* Start with a complete bipartite graph and remove edges one by one */
+
+ Bitmap edges = new Bitmap(maxNumberOfEdges);
+ edges.setAll();
+ for (int edgesNum = maxNumberOfEdges; edgesNum > m;) {
+ int i = rand.nextInt(maxNumberOfEdges);
+ if (edges.get(i)) {
+ edges.clear(i);
+ edgesNum--;
+ }
+ }
+
+ int i = 0;
+ if (direction != Direction.DirectedRightToLeft)
+ for (V u : leftVertices)
+ for (V v : rightVertices)
+ if (edges.get(i++))
+ g.addEdge(u, v, edgeBuilder.apply(u, v));
+ if (direction == Direction.DirectedRightToLeft || direction == Direction.DirectedAll)
+ for (V u : rightVertices)
+ for (V v : leftVertices)
+ if (edges.get(i++))
+ g.addEdge(u, v, edgeBuilder.apply(u, v));
+ }
+
+ return g;
+ }
+
+}
diff --git a/jgalgo-core/src/main/java/com/jgalgo/gen/GnmGraphGenerator.java b/jgalgo-core/src/main/java/com/jgalgo/gen/GnmGraphGenerator.java
index d176a13481..1cf38177a4 100644
--- a/jgalgo-core/src/main/java/com/jgalgo/gen/GnmGraphGenerator.java
+++ b/jgalgo-core/src/main/java/com/jgalgo/gen/GnmGraphGenerator.java
@@ -39,7 +39,7 @@
* Generates a uniformly random graph among all graphs with \(n\) vertices and \(m\) edges.
*
*
- * The generator uses the G(n,m) model to generate a uniformly random graph among all graphs with \(n\) vertices and
+ * The generator uses the \(G(n,m)\) model to generate a uniformly random graph among all graphs with \(n\) vertices and
* \(m\) edges. Both directed and undirected graphs are supported, as well as self-edges and parallel-edges. By default,
* the generated graph(s) is undirected, does not contain self-edges and may contain parallel-edges.
*
@@ -64,20 +64,20 @@ private GnmGraphGenerator(boolean intGraph) {
}
/**
- * Creates a new G(n,m) generator.
+ * Creates a new \(G(n,m)\) generator.
*
* @param the vertices type
* @param the edges type
- * @return a new G(n,m) generator
+ * @return a new \(G(n,m)\) generator
*/
public static GnmGraphGenerator newInstance() {
return new GnmGraphGenerator<>(false);
}
/**
- * Creates a new G(n,m) generator for {@link IntGraph}.
+ * Creates a new \(G(n,m)\) generator for {@link IntGraph}.
*
- * @return a new G(n,m) generator for {@link IntGraph}
+ * @return a new \(G(n,m)\) generator for {@link IntGraph}
*/
public static GnmGraphGenerator newIntInstance() {
return new GnmGraphGenerator<>(true);
@@ -245,7 +245,7 @@ public GraphBuilder generateIntoBuilder() {
} else {
factory = directed ? GraphFactory.newDirected() : GraphFactory.newUndirected();
}
- GraphBuilder g = factory.allowSelfEdges(selfEdges).allowParallelEdges(parallelEdges).newBuilder();
+ GraphBuilder g = factory.allowSelfEdges(selfEdges).newBuilder();
g.expectedVerticesNum(n);
g.expectedEdgesNum(m);
diff --git a/jgalgo-core/src/test/java/com/jgalgo/gen/GnmBipartiteGraphGeneratorTest.java b/jgalgo-core/src/test/java/com/jgalgo/gen/GnmBipartiteGraphGeneratorTest.java
new file mode 100644
index 0000000000..2afa1c8c07
--- /dev/null
+++ b/jgalgo-core/src/test/java/com/jgalgo/gen/GnmBipartiteGraphGeneratorTest.java
@@ -0,0 +1,297 @@
+/*-
+ * Copyright 2023 Barak Ugav
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jgalgo.gen;
+
+import static com.jgalgo.internal.util.Range.range;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.Test;
+import com.jgalgo.alg.BipartiteGraphs;
+import com.jgalgo.alg.VertexBiPartition;
+import com.jgalgo.graph.Graph;
+import com.jgalgo.graph.Graphs;
+import com.jgalgo.internal.util.TestBase;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+public class GnmBipartiteGraphGeneratorTest extends TestBase {
+
+ @Test
+ public void testVertices() {
+ final long seed = 0xc01d2657d68af779L;
+ GnmBipartiteGraphGenerator g = GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(seed);
+ g.setEdges(5, new AtomicInteger()::getAndIncrement);
+
+ /* vertices were not set yet */
+ assertThrows(IllegalStateException.class, () -> g.generate());
+
+ g.setVertices(Set.of("a", "b"), Set.of("c", "d"));
+ assertEquals(Set.of("a", "b", "c", "d"), g.generate().vertices());
+ /* assert the vertices are reused */
+ assertEquals(Set.of("a", "b", "c", "d"), g.generate().vertices());
+
+ AtomicInteger vertexId = new AtomicInteger();
+ g.setVertices(2, 2, () -> String.valueOf(vertexId.getAndIncrement()));
+ assertEquals(Set.of("0", "1", "2", "3"), g.generate().vertices());
+ /* assert the vertices are reused */
+ assertEquals(Set.of("0", "1", "2", "3"), g.generate().vertices());
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
+ public void testVerticesIntGraph() {
+ final long seed = 0x608e465602fdc995L;
+ GnmBipartiteGraphGenerator g = GnmBipartiteGraphGenerator.newIntInstance();
+ g.setSeed(seed);
+ g.setEdges(5, new AtomicInteger()::getAndIncrement);
+
+ /* vertices were not set yet */
+ assertThrows(IllegalStateException.class, () -> g.generate());
+
+ g.setVertices(Set.of(17, 86, 5), Set.of(2, 22));
+ assertEquals(Set.of(17, 86, 5, 2, 22), g.generate().vertices());
+ /* assert the vertices are reused */
+ assertEquals(Set.of(17, 86, 5, 2, 22), g.generate().vertices());
+
+ AtomicInteger vertexId = new AtomicInteger();
+ g.setVertices(1, 3, () -> vertexId.getAndIncrement());
+ assertEquals(Set.of(0, 1, 2, 3), g.generate().vertices());
+ /* assert the vertices are reused */
+ assertEquals(Set.of(0, 1, 2, 3), g.generate().vertices());
+ }
+
+ @Test
+ public void testEdges() {
+ foreachBoolConfig(intGraph -> {
+ final long seed = 0xb03f507f9a5e6db0L;
+ GnmBipartiteGraphGenerator g =
+ intGraph ? GnmBipartiteGraphGenerator.newIntInstance() : GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(seed);
+ g.setVertices(range(5), range(5, 10));
+
+ /* edges were not set yet */
+ assertThrows(IllegalStateException.class, () -> g.generate());
+
+ g.setEdges(5, new AtomicInteger()::getAndIncrement);
+ Graph g1 = g.generate();
+ assertEquals(range(5), g1.edges());
+ });
+ }
+
+ @Test
+ public void testDirected() {
+ foreachBoolConfig(intGraph -> {
+ final long seed = 0x2c4aa9b55709eaceL;
+ GnmBipartiteGraphGenerator g =
+ intGraph ? GnmBipartiteGraphGenerator.newIntInstance() : GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(seed);
+ g.setVertices(5, 5, new AtomicInteger()::getAndIncrement);
+ g.setEdges(5, new AtomicInteger()::getAndIncrement);
+
+ /* check default */
+ Graph g1 = g.generate();
+ assertFalse(g1.isDirected());
+ assertTrue(BipartiteGraphs.isBipartite(g1));
+ Optional> g1Partition0 = BipartiteGraphs.getExistingPartition(g1);
+ assertTrue(g1Partition0.isPresent());
+ VertexBiPartition g1Partition = g1Partition0.get();
+ for (Integer e : g1.edges()) {
+ boolean sourceIsLeft = g1Partition.leftVertices().contains(g1.edgeSource(e));
+ boolean targetIsLeft = g1Partition.leftVertices().contains(g1.edgeTarget(e));
+ assertNotEqualsBool(sourceIsLeft, targetIsLeft);
+ }
+
+ /* check directed all */
+ g.setDirectedAll();
+ Graph g2 = g.generate();
+ assertTrue(g2.isDirected());
+ assertTrue(BipartiteGraphs.isBipartite(g2));
+ Optional> g2Partition0 = BipartiteGraphs.getExistingPartition(g2);
+ assertTrue(g2Partition0.isPresent());
+ VertexBiPartition g2Partition = g2Partition0.get();
+ for (Integer e : g2.edges()) {
+ boolean sourceIsLeft = g2Partition.leftVertices().contains(g2.edgeSource(e));
+ boolean targetIsLeft = g2Partition.leftVertices().contains(g2.edgeTarget(e));
+ assertNotEqualsBool(sourceIsLeft, targetIsLeft);
+ }
+
+ /* check undirected */
+ g.setUndirected();
+ Graph g3 = g.generate();
+ assertFalse(g3.isDirected());
+ assertTrue(BipartiteGraphs.isBipartite(g3));
+ Optional> g3Partition0 = BipartiteGraphs.getExistingPartition(g3);
+ assertTrue(g3Partition0.isPresent());
+ VertexBiPartition g3Partition = g3Partition0.get();
+ for (Integer e : g3.edges()) {
+ boolean sourceIsLeft = g3Partition.leftVertices().contains(g3.edgeSource(e));
+ boolean targetIsLeft = g3Partition.leftVertices().contains(g3.edgeTarget(e));
+ assertNotEqualsBool(sourceIsLeft, targetIsLeft);
+ }
+
+ /* check directed with edges left to right */
+ g.setDirectedLeftToRight();
+ Graph g4 = g.generate();
+ assertTrue(g4.isDirected());
+ assertTrue(BipartiteGraphs.isBipartite(g4));
+ Optional> g4Partition0 = BipartiteGraphs.getExistingPartition(g4);
+ assertTrue(g4Partition0.isPresent());
+ VertexBiPartition g4Partition = g4Partition0.get();
+ for (Integer e : g4.edges()) {
+ assertTrue(g4Partition.leftVertices().contains(g4.edgeSource(e)));
+ assertTrue(g4Partition.rightVertices().contains(g4.edgeTarget(e)));
+ }
+
+ /* check directed with edges right to left */
+ g.setDirectedRightToLeft();;
+ Graph g5 = g.generate();
+ assertTrue(g5.isDirected());
+ assertTrue(BipartiteGraphs.isBipartite(g5));
+ Optional> g5Partition0 = BipartiteGraphs.getExistingPartition(g5);
+ assertTrue(g5Partition0.isPresent());
+ VertexBiPartition g5Partition = g5Partition0.get();
+ for (Integer e : g5.edges()) {
+ assertTrue(g5Partition.rightVertices().contains(g5.edgeSource(e)));
+ assertTrue(g5Partition.leftVertices().contains(g5.edgeTarget(e)));
+ }
+ });
+ }
+
+ @Test
+ public void testEdgeNum() {
+ foreachBoolConfig(intGraph -> {
+ GnmBipartiteGraphGenerator g =
+ intGraph ? GnmBipartiteGraphGenerator.newIntInstance() : GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(0xb8c895d35088b0cL);
+ g.setVertices(5, 5, new AtomicInteger()::getAndIncrement);
+ g.setParallelEdges(false);
+ assertThrows(IllegalArgumentException.class, () -> g.setEdges(-1, new AtomicInteger()::getAndIncrement));
+ g.setEdges(500, new AtomicInteger()::getAndIncrement);
+ assertThrows(IllegalArgumentException.class, () -> g.generate());
+ g.setEdges(5, new AtomicInteger()::getAndIncrement);
+ assertNotNull(g.generate());
+ });
+ }
+
+ @Test
+ public void testParallelEdges() {
+ foreachBoolConfig(intGraph -> {
+ final int Undirected = 0;
+ final int DirectedAll = 1;
+ final int DirectedLeftToRight = 2;
+ final int DirectedRightToLeft = 3;
+ for (int direction : IntList.of(Undirected, DirectedAll, DirectedLeftToRight, DirectedRightToLeft)) {
+ final long seed = 0x9ce94054e6b1bd41L;
+ GnmBipartiteGraphGenerator g = intGraph ? GnmBipartiteGraphGenerator.newIntInstance()
+ : GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(seed);
+ if (direction == Undirected) {
+ g.setUndirected();
+ } else if (direction == DirectedAll) {
+ g.setDirectedAll();
+ } else if (direction == DirectedLeftToRight) {
+ g.setDirectedLeftToRight();
+ } else if (direction == DirectedRightToLeft) {
+ g.setDirectedRightToLeft();
+ } else {
+ throw new AssertionError();
+ }
+
+ final int n1 = 4, n2 = 5;
+ g.setVertices(n1, n2, new AtomicInteger()::getAndIncrement);
+
+ int maxNumberOfEdges = n1 * n2 * (direction == DirectedAll ? 2 : 1);
+
+ /* check default */
+ g.setEdges(maxNumberOfEdges + 1, new AtomicInteger()::getAndIncrement);
+ Graph g1 = g.generate();
+ assertTrue(Graphs.containsParallelEdges(g1));
+
+ /* check parallel-edges disabled */
+ for (int repeat = 0; repeat < 20; repeat++) {
+ g.setParallelEdges(false);
+ g.setEdges(maxNumberOfEdges, new AtomicInteger()::getAndIncrement);
+ Graph g2 = g.generate();
+ assertFalse(Graphs.containsParallelEdges(g2));
+ }
+
+ /* check parallel-edges enabled */
+ g.setParallelEdges(true);
+ g.setEdges(maxNumberOfEdges + 1, new AtomicInteger()::getAndIncrement);
+ Graph g3 = g.generate();
+ assertTrue(Graphs.containsParallelEdges(g3));
+
+ /* Too much edges to enforce non-parallel edges */
+ g.setParallelEdges(false);
+ g.setEdges(maxNumberOfEdges + 1, new AtomicInteger()::getAndIncrement);
+ assertThrows(IllegalArgumentException.class, () -> g.generate());
+ }
+ });
+ }
+
+ @Test
+ public void denseGraphs() {
+ final long seed = 0xce3625df14f40f2fL;
+ final Random rand = new Random(seed);
+ foreachBoolConfig((intGraph, parallelEdges) -> {
+ final int Undirected = 0;
+ final int DirectedAll = 1;
+ final int DirectedLeftToRight = 2;
+ final int DirectedRightToLeft = 3;
+ for (int direction : IntList.of(Undirected, DirectedAll, DirectedLeftToRight, DirectedRightToLeft)) {
+ GnmBipartiteGraphGenerator g = intGraph ? GnmBipartiteGraphGenerator.newIntInstance()
+ : GnmBipartiteGraphGenerator.newInstance();
+ g.setSeed(rand.nextLong());
+ g.setParallelEdges(parallelEdges);
+ if (direction == Undirected) {
+ g.setUndirected();
+ } else if (direction == DirectedAll) {
+ g.setDirectedAll();
+ } else if (direction == DirectedLeftToRight) {
+ g.setDirectedLeftToRight();
+ } else if (direction == DirectedRightToLeft) {
+ g.setDirectedRightToLeft();
+ } else {
+ throw new AssertionError();
+ }
+
+ final int n1 = 40, n2 = 60;
+ g.setVertices(n1, n2, new AtomicInteger()::getAndIncrement);
+
+ int maxNumberOfEdges = n1 * n2 * (direction == DirectedAll ? 2 : 1);
+
+ for (int repeat = 50; repeat-- > 0;) {
+ final int m = rand.nextInt(maxNumberOfEdges + 1);
+ g.setEdges(m, new AtomicInteger()::getAndIncrement);
+ Graph g1 = g.generate();
+ assertEquals(n1 + n2, g1.vertices().size());
+ assertEquals(m, g1.edges().size());
+ assertEqualsBool(direction != Undirected, g1.isDirected());
+ if (!parallelEdges)
+ assertFalse(Graphs.containsParallelEdges(g1));
+ }
+ }
+ });
+ }
+
+}
diff --git a/jgalgo-core/src/test/java/com/jgalgo/gen/GnmGraphGeneratorTest.java b/jgalgo-core/src/test/java/com/jgalgo/gen/GnmGraphGeneratorTest.java
index 19a33b6b3d..3e25bf1fc1 100644
--- a/jgalgo-core/src/test/java/com/jgalgo/gen/GnmGraphGeneratorTest.java
+++ b/jgalgo-core/src/test/java/com/jgalgo/gen/GnmGraphGeneratorTest.java
@@ -43,11 +43,13 @@ public void testVertices() {
g.setVertices(Set.of("a", "b", "c"));
assertEquals(Set.of("a", "b", "c"), g.generate().vertices());
+ /* assert the vertices are reused */
assertEquals(Set.of("a", "b", "c"), g.generate().vertices());
AtomicInteger vertexId = new AtomicInteger();
g.setVertices(4, () -> String.valueOf(vertexId.getAndIncrement()));
assertEquals(Set.of("0", "1", "2", "3"), g.generate().vertices());
+ /* assert the vertices are reused */
assertEquals(Set.of("0", "1", "2", "3"), g.generate().vertices());
}
@@ -64,11 +66,13 @@ public void testVerticesIntGraph() {
g.setVertices(Set.of(17, 86, 5));
assertEquals(Set.of(17, 86, 5), g.generate().vertices());
+ /* assert the vertices are reused */
assertEquals(Set.of(17, 86, 5), g.generate().vertices());
AtomicInteger vertexId = new AtomicInteger();
g.setVertices(4, () -> vertexId.getAndIncrement());
assertEquals(Set.of(0, 1, 2, 3), g.generate().vertices());
+ /* assert the vertices are reused */
assertEquals(Set.of(0, 1, 2, 3), g.generate().vertices());
}
@@ -123,10 +127,8 @@ public void testEdgeNum() {
g.setVertices(5, new AtomicInteger()::getAndIncrement);
g.setParallelEdges(false);
assertThrows(IllegalArgumentException.class, () -> g.setEdges(-1, new AtomicInteger()::getAndIncrement));
- assertThrows(IllegalArgumentException.class, () -> {
- g.setEdges(500, new AtomicInteger()::getAndIncrement);
- g.generate();
- });
+ g.setEdges(500, new AtomicInteger()::getAndIncrement);
+ assertThrows(IllegalArgumentException.class, () -> g.generate());
g.setEdges(5, new AtomicInteger()::getAndIncrement);
assertNotNull(g.generate());
});
@@ -182,7 +184,7 @@ public void testParallelEdges() {
Graph g1 = g.generate();
assertTrue(Graphs.containsParallelEdges(g1));
- /* check parallel-edges enabled */
+ /* check parallel-edges disabled */
for (int repeat = 0; repeat < 20; repeat++) {
g.setParallelEdges(false);
g.setEdges(maxNumberOfEdges, new AtomicInteger()::getAndIncrement);
@@ -190,7 +192,7 @@ public void testParallelEdges() {
assertFalse(Graphs.containsParallelEdges(g2));
}
- /* check parallel-edges disabled */
+ /* check parallel-edges enabled */
g.setParallelEdges(true);
g.setEdges(maxNumberOfEdges + 1, new AtomicInteger()::getAndIncrement);
Graph g3 = g.generate();