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();