| 1 | /** \file |
| 2 | * \brief Graph collection for tests |
| 3 | * |
| 4 | * \author Tilo Wiedera |
| 5 | * |
| 6 | * \par License: |
| 7 | * This file is part of the Open Graph Drawing Framework (OGDF). |
| 8 | * |
| 9 | * \par |
| 10 | * Copyright (C)<br> |
| 11 | * See README.md in the OGDF root directory for details. |
| 12 | * |
| 13 | * \par |
| 14 | * This program is free software; you can redistribute it and/or |
| 15 | * modify it under the terms of the GNU General Public License |
| 16 | * Version 2 or 3 as published by the Free Software Foundation; |
| 17 | * see the file LICENSE.txt included in the packaging of this file |
| 18 | * for details. |
| 19 | * |
| 20 | * \par |
| 21 | * This program is distributed in the hope that it will be useful, |
| 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 24 | * GNU General Public License for more details. |
| 25 | * |
| 26 | * \par |
| 27 | * You should have received a copy of the GNU General Public |
| 28 | * License along with this program; if not, see |
| 29 | * http://www.gnu.org/copyleft/gpl.html |
| 30 | */ |
| 31 | |
| 32 | #pragma once |
| 33 | |
| 34 | #include <set> |
| 35 | |
| 36 | #include <ogdf/basic/graph_generators.h> |
| 37 | #include <ogdf/basic/simple_graph_alg.h> |
| 38 | |
| 39 | #include <resources.h> |
| 40 | |
| 41 | class GraphSizes { |
| 42 | const int m_min; |
| 43 | const int m_max; |
| 44 | const int m_step; |
| 45 | public: |
| 46 | //! Creates feasible graph sizes ranging from \p min to \p max |
| 47 | //! with a step size of \p step. |
| 48 | GraphSizes(int min, int max, int step) : |
| 49 | m_min(min), m_max(max), m_step(step) { |
| 50 | OGDF_ASSERT(m_min <= m_max); |
| 51 | OGDF_ASSERT(m_step > 0); |
| 52 | } |
| 53 | |
| 54 | //! Default graphs sizes result in 3 iterations |
| 55 | //! over graphs with at most 100 nodes |
| 56 | GraphSizes() : GraphSizes(16, 100, 42) {} |
| 57 | |
| 58 | //! Creates just one feasible size that is \p n |
| 59 | GraphSizes(int n) : GraphSizes(n, n, 1) {} |
| 60 | |
| 61 | //! calls \p func for each feasible graph size |
| 62 | void forEachSize(std::function<void(int size)> func) const { |
| 63 | for(int n = m_min; n <= m_max; n += m_step) { |
| 64 | func(n); |
| 65 | } |
| 66 | } |
| 67 | }; |
| 68 | |
| 69 | enum class GraphProperty { |
| 70 | //! Indicates graphs that are (directed!) acyclic. |
| 71 | acyclic, |
| 72 | |
| 73 | arborescenceForest, |
| 74 | connected, |
| 75 | biconnected, |
| 76 | nonPlanar, |
| 77 | maxDeg4, |
| 78 | planar, |
| 79 | triconnected, |
| 80 | |
| 81 | //! Indicates graphs that are (undirected!) simple. |
| 82 | simple, |
| 83 | |
| 84 | //! Indicates instances that have a reasonably low number of edges. |
| 85 | //! These graphs can, e.g., be used for planarization layouts without raising runtime too much. |
| 86 | sparse |
| 87 | }; |
| 88 | |
| 89 | //! Randomly adds loops and parallel edges to \p G. |
| 90 | /** |
| 91 | * For each edge, we add parallel edges until an event with probability 1 - \p p is encountered. |
| 92 | * For each node, we do the same creating loops. |
| 93 | */ |
| 94 | inline void addMultiEdges(Graph& G, double p) { |
| 95 | OGDF_ASSERT(p >= 0); |
| 96 | OGDF_ASSERT(p < 1); |
| 97 | |
| 98 | auto byChance = [&]() -> bool { return randomDouble(0, 1) < p; }; |
| 99 | |
| 100 | List<edge> edges; |
| 101 | G.allEdges(edges); |
| 102 | |
| 103 | for (node v : G.nodes) { |
| 104 | while (byChance()) { |
| 105 | G.newEdge(v, v); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | for (edge e : edges) { |
| 110 | node v = e->source(); |
| 111 | node w = e->target(); |
| 112 | |
| 113 | while (byChance()) { |
| 114 | G.newEdge(v, w); |
| 115 | |
| 116 | if (byChance()) { |
| 117 | std::swap(v, w); |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Creates a planar disconnected graphs that contains cut vertices. |
| 125 | * |
| 126 | * @param G Input graph. |
| 127 | * @param nMax Approximate maximum number of nodes, |
| 128 | * @param densityMin Approximate minimum edge density (relative to actual number of nodes). |
| 129 | * @param densityMax Approximate maximum edge density (relative to actual number of nodes). |
| 130 | * @param cc Number of connected components to create. |
| 131 | * @param bc Number of biconnected components to create per connected component. |
| 132 | */ |
| 133 | inline void createDisconnectedGraph( |
| 134 | Graph &G, |
| 135 | int nMax, |
| 136 | double densityMin, |
| 137 | double densityMax, |
| 138 | int cc, |
| 139 | int bc) |
| 140 | { |
| 141 | OGDF_ASSERT(cc > 0); |
| 142 | OGDF_ASSERT(bc > 0); |
| 143 | OGDF_ASSERT(densityMin > 0); |
| 144 | OGDF_ASSERT(densityMax >= densityMin); |
| 145 | OGDF_ASSERT(densityMax < 3); |
| 146 | |
| 147 | G.clear(); |
| 148 | |
| 149 | int nBcMax = ceil(nMax / (cc*bc)); |
| 150 | nBcMax = std::max(nBcMax, 2); |
| 151 | |
| 152 | for(int i = 0; i < cc; i++) { |
| 153 | int m = ceil(randomDouble(densityMin*nBcMax, densityMax*nBcMax)); |
| 154 | Graph H; |
| 155 | randomPlanarCNBGraph(H, nBcMax, m, bc); |
| 156 | G.insert(H); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | inline void createAlmostPlanarGraph(Graph &G, int n, int m, int add_m) |
| 161 | { |
| 162 | randomPlanarBiconnectedGraph(G, n, m); |
| 163 | |
| 164 | Array<node> table(n); |
| 165 | |
| 166 | int i = 0; |
| 167 | for(node v : G.nodes) { |
| 168 | OGDF_ASSERT(i < n); |
| 169 | table[i++] = v; |
| 170 | } |
| 171 | |
| 172 | for(i = 0; i < add_m; ++i) { |
| 173 | G.newEdge(table[randomNumber(0, n-1)], table[randomNumber(0, n-1)]); |
| 174 | } |
| 175 | |
| 176 | makeSimpleUndirected(G); |
| 177 | } |
| 178 | |
| 179 | //! Returns true if \p subset is a subset of \p superset |
| 180 | inline bool doesInclude(const std::set<GraphProperty>& subset, const std::set<GraphProperty>& superset) { |
| 181 | for (auto r : subset) { |
| 182 | if(superset.find(r) == superset.end()) { |
| 183 | return false; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | return true; |
| 188 | } |
| 189 | |
| 190 | inline void imply(std::set<GraphProperty>& props, GraphProperty conclusion, GraphProperty premise) { |
| 191 | if (doesInclude({premise}, props)) { |
| 192 | props.insert(conclusion); |
| 193 | } |
| 194 | }; |
| 195 | |
| 196 | inline void performImplications(std::set<GraphProperty>& props) { |
| 197 | imply(props, GraphProperty::biconnected, GraphProperty::triconnected); |
| 198 | imply(props, GraphProperty::connected, GraphProperty::biconnected); |
| 199 | imply(props, GraphProperty::planar, GraphProperty::arborescenceForest); |
| 200 | imply(props, GraphProperty::acyclic, GraphProperty::arborescenceForest); |
| 201 | |
| 202 | if (doesInclude({GraphProperty::simple}, props) && |
| 203 | (doesInclude({GraphProperty::maxDeg4}, props) || doesInclude({GraphProperty::planar}, props))) { |
| 204 | props.insert(GraphProperty::sparse); |
| 205 | } |
| 206 | |
| 207 | OGDF_ASSERT(!doesInclude({GraphProperty::nonPlanar, GraphProperty::planar}, props)); |
| 208 | }; |
| 209 | |
| 210 | //! Make \p G (undirected) simple by splitting parallel edges. |
| 211 | //! Compared to ogdf::makeSimpleUndirected, this maintains biconnectivity. |
| 212 | inline void splitParallelEdges(Graph& G) { |
| 213 | List<edge> edges; |
| 214 | G.allEdges(edges); |
| 215 | |
| 216 | for(edge e : edges) { |
| 217 | for (adjEntry adj : e->source()->adjEntries) { |
| 218 | if (adj->twinNode() == e->target() && adj->theEdge() != e) { |
| 219 | G.split(e); |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * Performs tests on a diverse set of graphs. |
| 227 | * |
| 228 | * @param requirements Required properties that feasible graphs must have. |
| 229 | * @param doTest Actual test routine for given graph. |
| 230 | * @param sizes Approximate number of nodes (and number of instances) for randomly generated graphs. |
| 231 | * @param stepSize Increase for number of nodes between steps for randomly generated graphs. |
| 232 | */ |
| 233 | inline void forEachGraphItWorks(std::set<GraphProperty> requirements, |
| 234 | std::function<void(const Graph&, const std::string &graphName, const std::set<GraphProperty>& props)> doTest, |
| 235 | GraphSizes sizes = GraphSizes()) { |
| 236 | auto testInstance = [&](const string& desc, std::set<GraphProperty> props, std::function<void(Graph&)> generateGraph) { |
| 237 | performImplications(props); |
| 238 | |
| 239 | if (doesInclude(requirements, props)) { |
| 240 | bandit::it("works on a " + desc, [&] { |
| 241 | Graph G; |
| 242 | generateGraph(G); |
| 243 | doTest(G, desc, props); |
| 244 | }); |
| 245 | } |
| 246 | }; |
| 247 | |
| 248 | auto testInstances = [&](const string& desc, std::set<GraphProperty> props, std::function<void(Graph&, int)> generateGraph) { |
| 249 | sizes.forEachSize([&](int n) { |
| 250 | testInstance(desc + " [n≈" + to_string(n) + "]" , props, [&](Graph& G) { generateGraph(G, n); }); |
| 251 | }); |
| 252 | }; |
| 253 | |
| 254 | testInstances("arborescence" , |
| 255 | { GraphProperty::arborescenceForest, |
| 256 | GraphProperty::connected, |
| 257 | GraphProperty::simple, |
| 258 | GraphProperty::sparse }, |
| 259 | [](Graph &G, int n) { randomTree(G, n); }); |
| 260 | |
| 261 | testInstances("arborescence forest" , |
| 262 | { GraphProperty::arborescenceForest, |
| 263 | GraphProperty::simple, |
| 264 | GraphProperty::sparse }, |
| 265 | [](Graph &G, int n) { |
| 266 | randomTree(G, n); |
| 267 | |
| 268 | // make graph disconnected |
| 269 | for(int i = 0; i < 3; i++) { |
| 270 | G.delEdge(G.chooseEdge()); |
| 271 | } |
| 272 | }); |
| 273 | |
| 274 | testInstances("3-regular arborescence" , |
| 275 | { GraphProperty::arborescenceForest, |
| 276 | GraphProperty::connected, |
| 277 | GraphProperty::maxDeg4, |
| 278 | GraphProperty::simple }, |
| 279 | [](Graph &G, int n) { regularTree(G, n, 3); }); |
| 280 | |
| 281 | testInstance("path-like tree" , |
| 282 | { GraphProperty::connected, |
| 283 | GraphProperty::planar, |
| 284 | GraphProperty::simple }, |
| 285 | [](Graph& G) { |
| 286 | std::stringstream ss{ResourceFile::get("misc/path-like_tree.gml" )->data()}; |
| 287 | GraphIO::read(G, ss); |
| 288 | }); |
| 289 | |
| 290 | testInstance("K4" , |
| 291 | { GraphProperty::maxDeg4, |
| 292 | GraphProperty::planar, |
| 293 | GraphProperty::simple, |
| 294 | GraphProperty::triconnected}, |
| 295 | [](Graph& G) { completeGraph(G, 4); }); |
| 296 | |
| 297 | testInstance("K2,3" , |
| 298 | { GraphProperty::maxDeg4, |
| 299 | GraphProperty::planar, |
| 300 | GraphProperty::simple, |
| 301 | GraphProperty::biconnected }, |
| 302 | [](Graph& G) { completeBipartiteGraph(G, 2, 3); }); |
| 303 | |
| 304 | testInstance("K5" , |
| 305 | { GraphProperty::nonPlanar, |
| 306 | GraphProperty::maxDeg4, |
| 307 | GraphProperty::simple, |
| 308 | GraphProperty::triconnected }, |
| 309 | [](Graph& G) { completeGraph(G, 5); }); |
| 310 | |
| 311 | testInstance("K3,3" , |
| 312 | { GraphProperty::nonPlanar, |
| 313 | GraphProperty::maxDeg4, |
| 314 | GraphProperty::simple, |
| 315 | GraphProperty::triconnected }, |
| 316 | [](Graph& G) { completeBipartiteGraph(G, 3, 3); }); |
| 317 | |
| 318 | testInstances("connected sparse graph" , |
| 319 | { GraphProperty::connected, |
| 320 | GraphProperty::simple, |
| 321 | GraphProperty::sparse }, |
| 322 | [](Graph &G, int n) { |
| 323 | randomSimpleGraph(G, n, 2*n); |
| 324 | makeConnected(G); |
| 325 | }); |
| 326 | |
| 327 | testInstances("connected dense graph" , |
| 328 | { GraphProperty::connected, |
| 329 | GraphProperty::simple }, |
| 330 | [](Graph &G, int n) { |
| 331 | randomSimpleGraph(G, n,(n*n)/4); |
| 332 | makeConnected(G); |
| 333 | }); |
| 334 | |
| 335 | testInstances("4-regular graph" , |
| 336 | { GraphProperty::maxDeg4 }, |
| 337 | [](Graph &G, int n) { randomRegularGraph(G, n, 4); }); |
| 338 | |
| 339 | |
| 340 | testInstances("acyclic grid graph" , |
| 341 | { GraphProperty::acyclic, |
| 342 | GraphProperty::biconnected, |
| 343 | GraphProperty::maxDeg4, |
| 344 | GraphProperty::planar, |
| 345 | GraphProperty::simple }, |
| 346 | [](Graph &G, int n) { gridGraph(G, sqrt(n), sqrt(n), false, false); }); |
| 347 | |
| 348 | testInstances("wheel graph" , |
| 349 | { GraphProperty::biconnected, |
| 350 | GraphProperty::planar, |
| 351 | GraphProperty::simple }, |
| 352 | [](Graph &G, int n) { wheelGraph(G, n); }); |
| 353 | |
| 354 | testInstances("series parallel DAG" , |
| 355 | { GraphProperty::acyclic, |
| 356 | GraphProperty::connected, |
| 357 | GraphProperty::planar, |
| 358 | GraphProperty::simple }, |
| 359 | [](Graph &G, int n) { randomSeriesParallelDAG(G, n); }); |
| 360 | |
| 361 | testInstances("connected planar graph" , |
| 362 | { GraphProperty::connected, |
| 363 | GraphProperty::planar, |
| 364 | GraphProperty::simple }, |
| 365 | [](Graph &G, int n) { randomPlanarConnectedGraph(G, n, 2*n); }); |
| 366 | |
| 367 | testInstances("biconnected almost planar graph" , |
| 368 | { GraphProperty::biconnected, |
| 369 | GraphProperty::nonPlanar, |
| 370 | GraphProperty::simple, |
| 371 | GraphProperty::sparse }, |
| 372 | [](Graph &G, int n) { createAlmostPlanarGraph(G, n, 2*n, 10); }); |
| 373 | |
| 374 | testInstances("biconnected graph" , |
| 375 | { GraphProperty::biconnected, |
| 376 | GraphProperty::simple, |
| 377 | GraphProperty::sparse }, |
| 378 | [](Graph &G, int n) { |
| 379 | randomBiconnectedGraph(G, n, 2*n); |
| 380 | splitParallelEdges(G); |
| 381 | }); |
| 382 | |
| 383 | testInstances("acyclic biconnected planar graph" , |
| 384 | { GraphProperty::biconnected, |
| 385 | GraphProperty::planar, |
| 386 | GraphProperty::simple }, |
| 387 | [](Graph &G, int n) { |
| 388 | randomPlanarBiconnectedDigraph(G, n, 2*n); |
| 389 | splitParallelEdges(G); |
| 390 | }); |
| 391 | |
| 392 | testInstances("acyclic biconnected non-planar graph" , |
| 393 | { GraphProperty::biconnected, |
| 394 | GraphProperty::nonPlanar, |
| 395 | GraphProperty::simple, |
| 396 | GraphProperty::sparse }, |
| 397 | [](Graph &G, int n) { |
| 398 | randomBiconnectedGraph(G, n, 3*n - 5); |
| 399 | splitParallelEdges(G); |
| 400 | }); |
| 401 | |
| 402 | testInstances("triconnected graph" , |
| 403 | { GraphProperty::simple, |
| 404 | GraphProperty::triconnected }, |
| 405 | [](Graph &G, int n) { randomTriconnectedGraph(G, n, .5, .5); }); |
| 406 | |
| 407 | testInstances("triconnected planar graph" , |
| 408 | { GraphProperty::planar, |
| 409 | GraphProperty::simple, |
| 410 | GraphProperty::triconnected }, |
| 411 | [](Graph &G, int n) { randomPlanarTriconnectedGraph(G, n, .5, .5); }); |
| 412 | |
| 413 | testInstances("maximal planar graph" , |
| 414 | { GraphProperty::planar, |
| 415 | GraphProperty::simple, |
| 416 | GraphProperty::triconnected }, |
| 417 | [](Graph &G, int n) { randomPlanarBiconnectedGraph(G, n, 3*n - 6); }); |
| 418 | |
| 419 | testInstances("disconnected planar graph" , |
| 420 | { GraphProperty::planar, |
| 421 | GraphProperty::simple }, |
| 422 | [](Graph &G, int n) { createDisconnectedGraph(G, n, 1.4, 2.6, 3, 3); }); |
| 423 | |
| 424 | testInstances("planar dense triconnected multi-graph" , |
| 425 | { GraphProperty::planar, |
| 426 | GraphProperty::triconnected }, |
| 427 | [](Graph &G, int n) { |
| 428 | randomPlanarTriconnectedGraph(G, n, .5, .5); |
| 429 | addMultiEdges(G, .5); |
| 430 | }); |
| 431 | |
| 432 | testInstances("planar sparse triconnected multi-graph" , |
| 433 | { GraphProperty::planar, |
| 434 | GraphProperty::sparse, |
| 435 | GraphProperty::triconnected }, |
| 436 | [](Graph &G, int n) { |
| 437 | randomPlanarTriconnectedGraph(G, n, .5, .5); |
| 438 | addMultiEdges(G, 5.0/n); |
| 439 | }); |
| 440 | } |
| 441 | |
| 442 | inline void forEachGraphItWorks(std::set<GraphProperty> requirements, |
| 443 | std::function<void(const Graph&)> doTest, |
| 444 | GraphSizes sizes = GraphSizes()) { |
| 445 | forEachGraphItWorks( |
| 446 | requirements, |
| 447 | [&](const Graph& G, const std::string&, const std::set<GraphProperty>&) { doTest(G); }, |
| 448 | sizes); |
| 449 | } |
| 450 | |