diff --git a/impl/graph_inl.h b/impl/graph_inl.h index a4f8273..d2211cf 100644 --- a/impl/graph_inl.h +++ b/impl/graph_inl.h @@ -90,6 +90,9 @@ class GraphRandom { "Number of vertices and edges in the graph must be nonnegative"); checkLargeParameter(n); return BuilderProxy(Traits(n), [](Traits t) { + ensure( + t.n <= 1 || !t.connected, + "Empty graph on >1 vertices cannot be connected"); Graph g; if (t.directed) { g.directed_ = true; @@ -108,6 +111,7 @@ class GraphRandom { Graph g; if (t.directed) { g.directed_ = true; + ensure(!t.acyclic, "Cannot generate acyclic cycle"); } for (int i = 0; i < t.n; ++i) { g.addEdge(i, (i+1)%t.n); @@ -132,9 +136,6 @@ class GraphRandom { private: static Graph doRandom(Traits t) { - if (t.directed && t.acyclic) { - return doDag(t); - } int n = t.n; int m = t.m; @@ -159,15 +160,6 @@ class GraphRandom { } auto edgeIsGood = [&usedEdges, t](std::pair edge) { - // TODO: move this check to edges generation loop - if (!t.allowLoops && edge.first == edge.second) { - ENSURE(false); - return false; - } - if (!t.directed && edge.first > edge.second) { - ENSURE(false); - std::swap(edge.first, edge.second); - } if (!t.allowMulti && usedEdges.count(edge)) { return false; } @@ -194,6 +186,10 @@ class GraphRandom { "Not enough edges found"); Graph graph; + + if (t.directed && t.acyclic) { + makeAcyclic(result); + } if (t.directed) { graph.directed_ = true; } @@ -209,19 +205,35 @@ class GraphRandom { } static Graph doRandomStretched(Traits t, int elongation, int spread) { - ensure( - !t.directed, - "randomStretched not available for directed graphs"); - Tree tree = Tree::randomPrim(t.n, elongation); Array parents = tree.parents(0); + parents[0] = 0; Graph graph(tree); auto treeEdges = tree.edges(); + if (t.directed && !t.acyclic) { + for (auto& edge: treeEdges) { + if (rnd.next(2)) { + std::swap(edge.first, edge.second); + } + } + } std::set> usedEdges( treeEdges.begin(), treeEdges.end()); + auto edgeIsGood = [&usedEdges, t](std::pair edge) { + if (!t.allowMulti && usedEdges.count(edge)) { + return false; + } + if (t.directed && !t.allowAntiparallel && + usedEdges.count({edge.second, edge.first})) + { + return false; + } + return true; + }; + while (graph.m() != t.m) { int u = rnd.next(t.n); int up = rnd.next(0, spread); @@ -236,10 +248,14 @@ class GraphRandom { continue; } - if (!t.allowMulti && usedEdges.count({v, u})) { + if (!edgeIsGood({v, u})) { continue; } + if (t.directed && !t.acyclic && rnd.next(2)) { + std::swap(u, v); + } + graph.addEdge(u, v); usedEdges.emplace(u, v); } @@ -248,12 +264,6 @@ class GraphRandom { return graph; } - static Graph doDag(Traits t) { - (void)t; - ENSURE(false, "No dags at the moment"); - - } - static std::pair randomEdge(int n, const Traits& t) { return rnd.nextp(n, RandomPairTraits{!t.directed, !t.allowLoops}); } @@ -269,6 +279,15 @@ class GraphRandom { } return res; } + + static void makeAcyclic(Arrayp& edges) { + auto numbering = Array::id(edges.size()).shuffle(); + for (auto& edge: edges) { + if (numbering[edge.first] > numbering[edge.second]) { + std::swap(edge.first, edge.second); + } + } + } }; } // namespace graph_detail diff --git a/jngen.h b/jngen.h index 6ff854a..9d68361 100644 --- a/jngen.h +++ b/jngen.h @@ -6164,6 +6164,9 @@ class GraphRandom { "Number of vertices and edges in the graph must be nonnegative"); checkLargeParameter(n); return BuilderProxy(Traits(n), [](Traits t) { + ensure( + t.n <= 1 || !t.connected, + "Empty graph on >1 vertices cannot be connected"); Graph g; if (t.directed) { g.directed_ = true; @@ -6182,6 +6185,7 @@ class GraphRandom { Graph g; if (t.directed) { g.directed_ = true; + ensure(!t.acyclic, "Cannot generate acyclic cycle"); } for (int i = 0; i < t.n; ++i) { g.addEdge(i, (i+1)%t.n); @@ -6206,9 +6210,6 @@ class GraphRandom { private: static Graph doRandom(Traits t) { - if (t.directed && t.acyclic) { - return doDag(t); - } int n = t.n; int m = t.m; @@ -6233,15 +6234,6 @@ class GraphRandom { } auto edgeIsGood = [&usedEdges, t](std::pair edge) { - // TODO: move this check to edges generation loop - if (!t.allowLoops && edge.first == edge.second) { - ENSURE(false); - return false; - } - if (!t.directed && edge.first > edge.second) { - ENSURE(false); - std::swap(edge.first, edge.second); - } if (!t.allowMulti && usedEdges.count(edge)) { return false; } @@ -6268,6 +6260,10 @@ class GraphRandom { "Not enough edges found"); Graph graph; + + if (t.directed && t.acyclic) { + makeAcyclic(result); + } if (t.directed) { graph.directed_ = true; } @@ -6283,19 +6279,35 @@ class GraphRandom { } static Graph doRandomStretched(Traits t, int elongation, int spread) { - ensure( - !t.directed, - "randomStretched not available for directed graphs"); - Tree tree = Tree::randomPrim(t.n, elongation); Array parents = tree.parents(0); + parents[0] = 0; Graph graph(tree); auto treeEdges = tree.edges(); + if (t.directed && !t.acyclic) { + for (auto& edge: treeEdges) { + if (rnd.next(2)) { + std::swap(edge.first, edge.second); + } + } + } std::set> usedEdges( treeEdges.begin(), treeEdges.end()); + auto edgeIsGood = [&usedEdges, t](std::pair edge) { + if (!t.allowMulti && usedEdges.count(edge)) { + return false; + } + if (t.directed && !t.allowAntiparallel && + usedEdges.count({edge.second, edge.first})) + { + return false; + } + return true; + }; + while (graph.m() != t.m) { int u = rnd.next(t.n); int up = rnd.next(0, spread); @@ -6310,10 +6322,14 @@ class GraphRandom { continue; } - if (!t.allowMulti && usedEdges.count({v, u})) { + if (!edgeIsGood({v, u})) { continue; } + if (t.directed && !t.acyclic && rnd.next(2)) { + std::swap(u, v); + } + graph.addEdge(u, v); usedEdges.emplace(u, v); } @@ -6322,12 +6338,6 @@ class GraphRandom { return graph; } - static Graph doDag(Traits t) { - (void)t; - ENSURE(false, "No dags at the moment"); - - } - static std::pair randomEdge(int n, const Traits& t) { return rnd.nextp(n, RandomPairTraits{!t.directed, !t.allowLoops}); } @@ -6343,6 +6353,15 @@ class GraphRandom { } return res; } + + static void makeAcyclic(Arrayp& edges) { + auto numbering = Array::id(edges.size()).shuffle(); + for (auto& edge: edges) { + if (numbering[edge.first] > numbering[edge.second]) { + std::swap(edge.first, edge.second); + } + } + } }; } // namespace graph_detail diff --git a/tests/graph.cpp b/tests/graph.cpp index 1c583c3..6a22118 100644 --- a/tests/graph.cpp +++ b/tests/graph.cpp @@ -16,7 +16,7 @@ BOOST_AUTO_TEST_CASE(output) { std::ostringstream ss; ss << g.printN().printM().add1() << std::endl; - BOOST_CHECK_EQUAL(ss.str(), "4 6\n1 3\n2 4\n1 4\n1 4\n2 3\n3 2\n"); + BOOST_TEST(ss.str() == "4 6\n1 3\n2 4\n1 4\n1 4\n2 3\n3 2\n"); } BOOST_AUTO_TEST_CASE(weights_and_labelling) { @@ -40,8 +40,8 @@ BOOST_AUTO_TEST_CASE(weights_and_labelling) { int v5 = -1, v8 = -1; std::string s; ss >> v1 >> v2; - BOOST_CHECK_EQUAL(v1, g.n()); - BOOST_CHECK_EQUAL(v2, g.m()); + BOOST_TEST(v1 == g.n()); + BOOST_TEST(v2 == g.m()); for (int i = 0; i < g.n(); ++i) { ss >> s; @@ -63,7 +63,58 @@ BOOST_AUTO_TEST_CASE(weights_and_labelling) { } } - BOOST_CHECK_EQUAL(count, 1); + BOOST_TEST(count == 1); +} + +template +void generateWithTraitsMask(T&& generator, const std::string& name, int mask) { + if (mask&(1<<0)) generator.allowAntiparallel(); + if (mask&(1<<1)) generator.allowLoops(); + if (mask&(1<<2)) generator.allowMulti(); + if (mask&(1<<3)) generator.connected(); + if (mask&(1<<4)) generator.directed(); + if (mask&(1<<5)) generator.acyclic(); + try { + generator.g(); + } catch (jngen::Exception) { + // directed acyclic cycle + if (name == "cycle" && (mask & ((1<<4) | (1<<5)))) { + return; + } + + // connected empty graph + if (name == "empty" && (mask & (1<<3))) { + return; + } + + throw; + + /* + // left here for debug purposes + std::cerr << name << ": "; + for (int i = 0; i < 6; ++i) { + if (mask&(1<