├── .gitmodules ├── BoostImplementation.hpp ├── CMakeLists.txt ├── DirectedAcyclicGraph.cpp ├── DirectedAcyclicGraph.hpp ├── LICENSE_1_0.txt ├── NocycleConfig.hpp.in ├── Nstate.hpp ├── OrientedGraph.cpp ├── OrientedGraph.hpp ├── PerformanceTest.cpp ├── README.md ├── RandomEdgePicker.hpp ├── nocycle-logo.png └── research └── tarjan-algorithm.pdf /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hostilefork/nocycle/71e48484ae6eef74ba92d72ed4f0d62bff5a7e4e/.gitmodules -------------------------------------------------------------------------------- /BoostImplementation.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // BoostImplementation.hpp - These classes put a relatively thin 3 | // wrapper on top of the boost graph library to give it the 4 | // same interface as the OrientedGraph and DirectedAcyclicGraph. 5 | // The resulting classes (BoostDirectedAcyclicGraph and 6 | // BoostOrientedGraph) are useful for testing. 7 | // 8 | // Copyright (c) 2009 HostileFork.com 9 | // 10 | // Distributed under the Boost Software License, Version 1.0. (See 11 | // accompanying file LICENSE_1_0.txt or copy at 12 | // http://www.boost.org/LICENSE_1_0.txt) 13 | // 14 | // See http://hostilefork.com/nocycle for documentation. 15 | // 16 | 17 | #pragma once 18 | 19 | #include // boost makes heavy use of "std::pair" 20 | #include 21 | #include 22 | #include 23 | 24 | #include "OrientedGraph.hpp" 25 | #include "DirectedAcyclicGraph.hpp" 26 | 27 | namespace nocycle { 28 | 29 | struct BoostVertexProperties { 30 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE || DIRECTEDACYCLICGRAPH_USER_TRISTATE 31 | bool exists; 32 | #endif 33 | }; 34 | 35 | struct BoostEdgeProperties { 36 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 37 | Nstate<3> tristate; 38 | #endif 39 | }; 40 | 41 | typedef boost::adjacency_matrix< 42 | // 43 | // `directedS` does not allow easy access to `in_edges` in `adjacency_list` 44 | // so you'd have to use `bidirectionalS`, which is unfortunately not 45 | // supported by `adjacency_matrix` (!). But fortunately for this code, 46 | // `adjacency_matrix` supports in_edges on `directedS`. Go figure. 47 | // 48 | boost::directedS, 49 | 50 | // "bundled properties" 51 | // http://www.boost.org/doc/libs/1_38_0/libs/graph/doc/bundles.html 52 | // 53 | BoostVertexProperties, 54 | BoostEdgeProperties 55 | > BoostBaseGraph; 56 | 57 | typedef boost::graph_traits BoostGraphTraits; 58 | 59 | // technically speaking, the boost vertex_descriptor is just an unsigned int 60 | // that increases in order, and many tutorials use it this way. BUT the guy 61 | // who wrote the book about the boost graph library says you should 62 | // treat it as an opaque handle and use properties if you want to associate 63 | // an integer with every vertex. 64 | // 65 | // http://lists.boost.org/boost-users/2002/07/1288.php 66 | // 67 | // So don't use integers directly to address vertices in boost, even though a 68 | // lot of code samples on the web do this. To be "correct", use: 69 | // 70 | // vertex_descriptor vertex(vertices_size_type n, const adjacency_matrix& g) 71 | // "Returns the nth vertex in the graph's vertex list." 72 | // 73 | typedef BoostGraphTraits::vertex_descriptor BoostVertex; 74 | typedef BoostGraphTraits::edge_descriptor BoostEdge; 75 | 76 | // http://www.boost.org/doc/libs/1_38_0/libs/graph/doc/using_adjacency_list.html 77 | 78 | // Something of a kluge, we publicly inherit from BoostBaseGraph 79 | // As it's just a class for test, we'll let this slide 80 | class BoostOrientedGraph : public BoostBaseGraph { 81 | public: 82 | typedef OrientedGraph::VertexID VertexID; 83 | 84 | public: 85 | BoostOrientedGraph (const size_t initial_size) : 86 | BoostBaseGraph (initial_size) 87 | { 88 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 89 | for (VertexID vertex = 0; vertex < initial_size; vertex++) { 90 | // used to use boost::add_vertex, but that's not implemented 91 | BoostVertex bv = boost::vertex(vertex, *this); 92 | (*this)[bv].exists = false; 93 | } 94 | #endif 95 | } 96 | 97 | public: 98 | void CreateVertex(DirectedAcyclicGraph::VertexID vertex) { 99 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 100 | BoostVertex bv = boost::vertex(vertex, *this); 101 | assert(!(*this)[bv].exists); 102 | (*this)[bv].exists = true; 103 | #else 104 | // do nothing; CreateVertex is a noop, but DestroyVertex is illegal 105 | #endif 106 | } 107 | void DestroyVertex(DirectedAcyclicGraph::VertexID vertex) { 108 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 109 | BoostVertex bv = boost::vertex(vertex, *this); 110 | assert((*this)[bv].exists); 111 | (*this)[bv].exists = false; 112 | 113 | // 1. remove_vertex is not implemented in boost::adjacency_matrix 114 | // 2. on all graph types, remove_vertex invalidates any vertex descriptors you 115 | // might have outstanding... and your vertex_index property map will 116 | // be renumbered... 117 | 118 | boost::clear_vertex(bv, (*this)); // only removes incoming and outgoing edges 119 | #else 120 | assert(false); 121 | #endif 122 | } 123 | bool VertexExists(DirectedAcyclicGraph::VertexID vertex) const { 124 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 125 | return (*this)[boost::vertex(vertex, (*this))].exists; 126 | #else 127 | return true; 128 | #endif 129 | } 130 | VertexID GetFirstInvalidVertexID() const { 131 | return num_vertices((*this)); 132 | } 133 | 134 | public: 135 | std::set OutgoingEdgesForVertex(VertexID vertex) const { 136 | assert(VertexExists(vertex)); 137 | 138 | BoostVertex bv = boost::vertex(vertex, *this); 139 | BoostGraphTraits::out_edge_iterator out_i, out_end; 140 | tie(out_i, out_end) = out_edges(bv, (*this)); // tie just breaks a pair down 141 | 142 | // "right" way to get a vertex index from a vertex_descriptor 143 | // instead of static_cast(vertex_descriptor) 144 | boost::property_map::type v_index = boost::get(boost::vertex_index, (*this)); 145 | 146 | std::set outgoing; 147 | while (out_i != out_end) { 148 | BoostGraphTraits::edge_descriptor e = *out_i; 149 | BoostVertex src = boost::source(e, *this); 150 | assert(src == bv); 151 | BoostVertex targ = boost::target(e, *this); 152 | outgoing.insert(v_index[targ]); 153 | ++out_i; 154 | } 155 | return outgoing; 156 | } 157 | std::set IncomingEdgesForVertex(VertexID vertex) const { 158 | assert(VertexExists(vertex)); 159 | 160 | BoostVertex bv = boost::vertex(vertex, *this); 161 | BoostGraphTraits::in_edge_iterator in_i, in_end; 162 | tie(in_i, in_end) = boost::in_edges(bv, (*this)); // tie just breaks a pair down 163 | 164 | // "right" way to get a vertex index from a vertex_descriptor 165 | // instead of static_cast(vertex_descriptor) 166 | boost::property_map::type v_index = boost::get(boost::vertex_index, (*this)); 167 | 168 | std::set incoming; 169 | while (in_i != in_end) { 170 | BoostGraphTraits::edge_descriptor e = *in_i; 171 | BoostVertex targ = boost::target(e, *this); 172 | assert(targ == bv); 173 | BoostVertex src = boost::source(e, *this); 174 | incoming.insert(v_index[src]); 175 | ++in_i; 176 | } 177 | return incoming; 178 | } 179 | 180 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 181 | public: 182 | Nstate<3> GetTristateForConnection(VertexID fromVertex, VertexID toVertex) const { 183 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 184 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 185 | 186 | BoostEdge be; 187 | bool edgeExists; 188 | 189 | tie(be, edgeExists) = boost::edge(bvSource, bvDest, (*this)); 190 | assert(edgeExists); 191 | 192 | return (*this)[be].tristate; 193 | } 194 | void SetTristateForConnection(VertexID fromVertex, VertexID toVertex, Nstate<3> tristate) { 195 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 196 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 197 | 198 | BoostEdge be; 199 | bool edgeExists; 200 | 201 | tie(be, edgeExists) = boost::edge(bvSource, bvDest, (*this)); 202 | assert(edgeExists); 203 | 204 | (*this)[be].tristate = tristate; 205 | } 206 | #endif 207 | 208 | public: 209 | bool HasLinkage(VertexID fromVertex, VertexID toVertex, bool* forwardLink = NULL, bool* reverseLink = NULL) const { 210 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 211 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 212 | 213 | BoostEdge beForward; 214 | bool forwardEdge; 215 | BoostEdge beReverse; 216 | bool reverseEdge; 217 | 218 | tie(beForward, forwardEdge) = boost::edge(bvSource, bvDest, (*this)); 219 | tie(beReverse, reverseEdge) = boost::edge(bvDest, bvSource, (*this)); 220 | if (forwardLink) 221 | *forwardLink = forwardEdge; 222 | if (reverseLink) 223 | *reverseLink = reverseEdge; 224 | return (forwardEdge || reverseEdge); 225 | } 226 | bool EdgeExists(VertexID fromVertex, VertexID toVertex) const { 227 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 228 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 229 | 230 | BoostEdge beForward; 231 | bool forwardEdge; 232 | 233 | boost::tie(beForward, forwardEdge) = boost::edge(bvSource, bvDest, (*this)); 234 | return forwardEdge; 235 | } 236 | bool SetEdge(VertexID fromVertex, VertexID toVertex) { 237 | assert(VertexExists(fromVertex)); 238 | assert(VertexExists(toVertex)); 239 | 240 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 241 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 242 | 243 | // If the edge is already in the graph then a duplicate will not be added 244 | // http://www.boost.org/doc/libs/1_38_0/libs/graph/doc/adjacency_matrix.html 245 | bool edgeIsNew; 246 | BoostEdge be; 247 | tie(be, edgeIsNew) = boost::add_edge(bvSource, bvDest, (*this)); 248 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 249 | (*this)[be].tristate = 0; 250 | #endif 251 | return edgeIsNew; 252 | } 253 | void AddEdge(VertexID fromVertex, VertexID toVertex) { 254 | if (!SetEdge(fromVertex, toVertex)) 255 | assert(false); 256 | } 257 | bool ClearEdge(VertexID fromVertex, VertexID toVertex) { 258 | BoostVertex bvSource = boost::vertex(fromVertex, (*this)); 259 | BoostVertex bvDest = boost::vertex(toVertex, (*this)); 260 | 261 | BoostEdge be; 262 | bool edgeExists; 263 | 264 | tie(be, edgeExists) = boost::edge(bvSource, bvDest, (*this)); 265 | if (!edgeExists) 266 | return false; 267 | 268 | boost::remove_edge(be, (*this)); 269 | return true; 270 | } 271 | void RemoveEdge(VertexID fromVertex, VertexID toVertex) { 272 | if (!ClearEdge(fromVertex, toVertex)) 273 | assert(false); 274 | } 275 | bool operator == (const OrientedGraph & og) const { 276 | if (og.GetFirstInvalidVertexID() != boost::num_vertices(*this)) 277 | return false; 278 | 279 | for (VertexID vertexCheck = 0; vertexCheck < og.GetFirstInvalidVertexID(); vertexCheck++) { 280 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 281 | if (!this->VertexExists(vertexCheck)) { 282 | if (og.VertexExists(vertexCheck)) 283 | return false; 284 | 285 | continue; 286 | } 287 | 288 | if (!og.VertexExists(vertexCheck)) 289 | return false; 290 | #else 291 | if (!og.VertexExists(vertexCheck)) 292 | continue; 293 | #endif 294 | std::set incomingEdges = og.IncomingEdgesForVertex(vertexCheck); 295 | std::set outgoingEdges = og.OutgoingEdgesForVertex(vertexCheck); 296 | 297 | assert(incomingEdges == this->IncomingEdgesForVertex(vertexCheck)); 298 | assert(outgoingEdges == this->OutgoingEdgesForVertex(vertexCheck)); 299 | 300 | for (VertexID vertexOther = 0; vertexOther < og.GetFirstInvalidVertexID(); vertexOther++) { 301 | BoostVertex bvOther = boost::vertex(vertexOther, *this); 302 | 303 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 304 | if (!VertexExists(vertexOther)) { 305 | if(og.VertexExists(vertexOther)) 306 | return false; 307 | continue; 308 | } 309 | if (!og.VertexExists(vertexOther)) 310 | return false; 311 | #else 312 | if (!og.VertexExists(vertexOther)) 313 | continue; 314 | #endif 315 | 316 | if (vertexCheck != vertexOther) { 317 | bool forwardEdgeInOg, reverseEdgeInOg; 318 | og.HasLinkage(vertexCheck, vertexOther, &forwardEdgeInOg, &reverseEdgeInOg); 319 | 320 | if (this->EdgeExists(vertexCheck, vertexOther)) { 321 | if (!forwardEdgeInOg) 322 | return false; 323 | assert(outgoingEdges.find(vertexOther) != outgoingEdges.end()); 324 | } else { 325 | if (forwardEdgeInOg) 326 | return false; 327 | assert(outgoingEdges.find(vertexOther) == outgoingEdges.end()); 328 | } 329 | 330 | if (this->EdgeExists(vertexOther, vertexCheck)) { 331 | if (!reverseEdgeInOg) 332 | return false; 333 | assert(incomingEdges.find(vertexOther) != incomingEdges.end()); 334 | } else { 335 | if (reverseEdgeInOg) 336 | return false; 337 | assert(incomingEdges.find(vertexOther) == incomingEdges.end()); 338 | } 339 | } 340 | } 341 | } 342 | return true; 343 | } 344 | bool operator != (const OrientedGraph & og) const { 345 | return !((*this) == og); 346 | } 347 | virtual ~BoostOrientedGraph() {}; 348 | }; 349 | 350 | // Something of a kluge, we publicly inherit from BoostOrientedGraph 351 | // as this is just a test class, we'll let it pass for now. 352 | class BoostDirectedAcyclicGraph : public BoostOrientedGraph { 353 | public: 354 | typedef DirectedAcyclicGraph::VertexID VertexID; 355 | 356 | public: 357 | BoostDirectedAcyclicGraph (const size_t initial_size = 0) : 358 | BoostOrientedGraph (initial_size) 359 | { 360 | } 361 | 362 | 363 | private: 364 | // this method hooks the DFS "back_edge", taken from: 365 | // http://www.boost.org/doc/libs/1_38_0/libs/graph/doc/file_dependency_example.html 366 | class cycle_detector : public boost::dfs_visitor<> { 367 | public: 368 | cycle_detector(bool& has_cycle) 369 | : _has_cycle(has_cycle) 370 | { 371 | } 372 | 373 | template 374 | void back_edge(BoostEdge, BoostBaseGraph&) { 375 | _has_cycle = true; 376 | } 377 | 378 | protected: 379 | bool& _has_cycle; 380 | }; 381 | 382 | private: 383 | 384 | // this method hooks the DFS "on_discover_vertex", adapted from listing 1: 385 | // http://www.ddj.com/cpp/184401546 386 | class reachability_detector : public boost::base_visitor { 387 | public: 388 | typedef boost::on_discover_vertex event_filter; 389 | reachability_detector (BoostVertex test_vertex, bool& is_reachable) : 390 | _test_vertex (test_vertex), 391 | _is_reachable (is_reachable) 392 | { 393 | } 394 | template 395 | void operator()(BoostVertex vertex, BoostBaseGraph& graph) 396 | { 397 | // boost algorithms have no termination condition other than finishing the visitation 398 | // the only way out is to throw an exception 399 | // "Note that, like with other BGL search algorithms, our A* implementation 400 | // has no termination condition other than that it has visited every vertex in the 401 | // same connected component as the start vertex. A custom visitor can cause the 402 | // algorithm to terminate by throwing an exception" 403 | // http://www.cs.rpi.edu/~beevek/research/astar_bgl04.pdf 404 | if (vertex == _test_vertex) { 405 | _is_reachable = true; 406 | } 407 | } 408 | 409 | protected: 410 | BoostVertex _test_vertex; 411 | bool& _is_reachable; 412 | }; 413 | 414 | public: 415 | bool SetEdge(VertexID fromVertex, VertexID toVertex) { 416 | 417 | if (0) { 418 | 419 | // First way I found on the web was a cycle detector 420 | // to see if an insertion would cause a cycle, perform it and then see if cycle 421 | // If so, remove the link, otherwise success 422 | 423 | bool newEdge = BoostOrientedGraph::SetEdge(fromVertex, toVertex); 424 | if (!newEdge) 425 | return false; 426 | 427 | bool has_cycle = false; 428 | cycle_detector vis (has_cycle); 429 | 430 | if (0) { 431 | // Searches entire graph for cycles 432 | boost::depth_first_search((*this), boost::visitor(vis)); 433 | } else { 434 | // First improvement: searches only region connected to fromVertex for cycles 435 | // see also: http://lists.boost.org/Archives/boost/2000/10/5573.php 436 | // http://www.boost.org/doc/libs/1_38_0/libs/graph/doc/depth_first_visit.html 437 | // http://archives.free.net.ph/message/20080228.115853.14aa712c.el.html 438 | 439 | // depth_first_visit is not documented well, and BGL is fairly abstruse if you get beyond the samples 440 | // but thanks to this message by Vladimir Prus I was able to figure out the magic to use it: 441 | // http://aspn.activestate.com/ASPN/Mail/Message/boost/2204027 442 | std::vector colors(num_vertices((*this))); 443 | 444 | boost::depth_first_visit((*this), boost::vertex(fromVertex, (*this)), vis, 445 | boost::make_iterator_property_map(&colors[0], boost::get(boost::vertex_index, (*this)))); 446 | } 447 | 448 | if (has_cycle) { 449 | BoostOrientedGraph::RemoveEdge(fromVertex, toVertex); 450 | 451 | bad_cycle bc; 452 | throw bc; 453 | } 454 | 455 | return true; 456 | 457 | } else { 458 | 459 | // Second improvement: do reachability without modifying the graph 460 | // Builds on depth_first_visit instead of depth_first_search 461 | if (EdgeExists(fromVertex, toVertex)) 462 | return false; 463 | 464 | bool is_reachable = false; 465 | BoostVertex find_vertex = boost::vertex(fromVertex, (*this)); 466 | 467 | // this line from http://www.ddj.com/cpp/184401546 -- listing 1, again. 468 | boost::dfs_visitor > vis2 = 469 | std::make_pair(reachability_detector(find_vertex, is_reachable), boost::null_visitor()); 470 | 471 | // http://aspn.activestate.com/ASPN/Mail/Message/boost/2204027 472 | std::vector colors(num_vertices((*this))); 473 | 474 | boost::depth_first_visit((*this), boost::vertex(toVertex, (*this)), vis2, 475 | boost::make_iterator_property_map(&colors[0], boost::get(boost::vertex_index, (*this)))); 476 | 477 | if (is_reachable) { 478 | bad_cycle bc; 479 | throw bc; 480 | } 481 | 482 | bool newEdge = BoostOrientedGraph::SetEdge(fromVertex, toVertex); 483 | assert(newEdge); 484 | return true; 485 | } 486 | 487 | } 488 | void AddEdge(VertexID fromVertex, VertexID toVertex) { 489 | if (!SetEdge(fromVertex, toVertex)) 490 | assert(false); 491 | } 492 | bool operator == (const DirectedAcyclicGraph & dag) const { 493 | if (static_cast(*this) != static_cast(dag)) 494 | return false; 495 | 496 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 497 | // additional checking - the tristates on the edges must match 498 | for (OrientedGraph::VertexID vertexCheck = 0; vertexCheck < dag.GetFirstInvalidVertexID(); vertexCheck++) { 499 | if (!VertexExists(vertexCheck)) 500 | continue; 501 | 502 | for (OrientedGraph::VertexID vertexOther = 0; vertexOther < dag.GetFirstInvalidVertexID(); vertexOther++) { 503 | if (vertexOther == vertexCheck) 504 | continue; 505 | 506 | if (!VertexExists(vertexOther)) 507 | continue; 508 | 509 | if (EdgeExists(vertexCheck, vertexOther)) { 510 | if (dag.GetTristateForConnection(vertexCheck, vertexOther) != GetTristateForConnection(vertexCheck, vertexOther)) 511 | return false; 512 | } 513 | } 514 | } 515 | #endif 516 | 517 | return true; 518 | } 519 | bool operator != (const DirectedAcyclicGraph & dag) const { 520 | return !((*this) == dag); 521 | } 522 | virtual ~BoostDirectedAcyclicGraph() { } 523 | }; 524 | 525 | } // end namespace nocycle 526 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is an input file for the CMake makefile generator 2 | # http://en.wikipedia.org/wiki/CMake 3 | 4 | # 5 | # CMakeLists.txt - This is a centralization of the flags for conditional 6 | # compilation of the Nocycle library...mostly to do with debug 7 | # code. Although the boost library is needed for much of the 8 | # testing, if you aren't doing a test build then it should not 9 | # be necessary to include any boost libraries (at least, not yet). 10 | # 11 | # Copyright (c) 2009 HostileFork.com 12 | # 13 | # Distributed under the Boost Software License, Version 1.0. (See 14 | # accompanying file LICENSE_1_0.txt or copy at 15 | # http://www.boost.org/LICENSE_1_0.txt) 16 | # 17 | # See http://hostilefork.com/nocycle for documentation. 18 | # 19 | 20 | # If you want to run cmake in an interactive mode that gives you prompts 21 | # for build options, use "ccmake" (or one of the graphical interfaces) 22 | # 23 | # https://cmake.org/cmake/help/latest/manual/ccmake.1.html 24 | # 25 | # Some resources for learning how to use CMake: 26 | # 27 | # http://www.drdobbs.com/cpp/the-cmake-build-manager/184405251 28 | # http://www.elpauer.org/stuff/learning_cmake.pdf 29 | 30 | 31 | ### SETUP ### 32 | ### http://www.cmake.org/cmake/help/examples.html 33 | 34 | # The name of our project is "NOCYCLE". CMakeLists files in this project can 35 | # refer to the root source directory of the project as ${NOCYCLE_SOURCE_DIR} 36 | # and to the root binary directory of the project as ${NOCYCLE_BINARY_DIR}. 37 | # 38 | cmake_minimum_required (VERSION 2.6) 39 | project (nocycle) 40 | 41 | 42 | ### OPTIONS ### 43 | ### http://www.cmake.org/cmake/help/cmake2.6docs.html#command:option 44 | 45 | # Several pieces of Nocycle have self testing code. These tests may rely on 46 | # the boost library. If you would like to build a version of Nocycle without 47 | # a dependency on boost, make sure all these are set to NO. 48 | # 49 | option (NSTATE_SELFTEST "Self-test Nstate library?" NO) 50 | option (ORIENTEDGRAPH_SELFTEST "Self-test Oriented Graph?" NO) 51 | option (DIRECTEDACYCLICGRAPH_SELFTEST "Self-test Directed Acyclic Graph?" NO) 52 | 53 | # Experimental attempt to cache transitive closure, not for general use 54 | # 55 | option ( 56 | DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 57 | "Use experimental transitive closure cache (doubles data structure size)" 58 | NO 59 | ) 60 | 61 | if (DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY) 62 | # 63 | # If caching the transitive closure... 64 | # ...there is an "extra tristate" we get in the canreach graph when there 65 | # is a physical edge in the data graph. We can use this to accelerate the 66 | # invalidation process, but for testing purposes it's nice to make sure the 67 | # algorithms aren't corrupting this implicitly when modifying other edges. 68 | # 69 | option ( 70 | DIRECTEDACYCLICGRAPH_USER_TRISTATE 71 | "Expose spare per-node tristate in transitive closure to client code?" 72 | NO 73 | ) 74 | 75 | # If caching the transitive closure... 76 | # ...and the extra tristate per edge is NOT visible to the user... 77 | # ...then we can use it to cache whether a vertex can still be reached even 78 | # if a physical link to it is removed from the graph. 79 | # 80 | if (NOT DIRECTEDACYCLICGRAPH_USER_TRISTATE) 81 | option ( 82 | DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 83 | "Use per-node tristate in closure for reachability after removals?" 84 | NO 85 | ) 86 | endif () 87 | 88 | # If caching the transitive closure... 89 | # ...then we might want to perform heavy consistency checks on the 90 | # transitive closure sidestructure while running. 91 | # 92 | option ( 93 | DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK 94 | "Heavy (slow!) consistency checks on transitive closure sidestructure?" 95 | NO 96 | ) 97 | endif (DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY) 98 | 99 | option ( 100 | TEST_AGAINST_BOOST 101 | "Test nocycle against reference implementation built on the boost library?" 102 | NO 103 | ) 104 | 105 | if (TEST_AGAINST_BOOST) 106 | # 107 | # Though nocycle distinguishes between vertices that have no connections 108 | # and those which "don't exist", boost's default assumption is that all 109 | # nodes in its capacity "exist". The only way to conceptually delete a 110 | # boost vertex from an adjacency_matrix is to remove all of its incoming 111 | # and outgoing connections. 112 | # 113 | # Though it is possible to inject a vertex property map to track existence, 114 | # the property map adds overhead and may not fairly represent boost's 115 | # performance in scenarios with large numbers of nodes when 116 | # existence tracking is not needed. 117 | # 118 | option ( 119 | BOOSTIMPLEMENTATION_TRACK_EXISTENCE 120 | "Track existence for nodes in the Boost reference implementation?" 121 | NO 122 | ) 123 | endif (TEST_AGAINST_BOOST) 124 | 125 | 126 | ### CONFIG HEADER ### 127 | ### http://www.cmake.org/pipermail/cmake/2003-August/004256.html 128 | ### http://www.vtk.org/Wiki/CMake_HowToDoPlatformChecks 129 | 130 | configure_file (${CMAKE_CURRENT_SOURCE_DIR}/NocycleConfig.hpp.in 131 | ${CMAKE_CURRENT_BINARY_DIR}/NocycleConfig.hpp) 132 | include_directories (${CMAKE_CURRENT_BINARY_DIR}) 133 | 134 | 135 | ### COMPILER FLAGS ### 136 | 137 | macro (add_cxx_flags flags) 138 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flags}") 139 | endmacro () 140 | 141 | # Nocycle was initially C++98, but as it is just some old experimental code it 142 | # might as well be a place to try usages of C++17 features. 143 | # 144 | # Note that this *should* work: 145 | # 146 | # set(CMAKE_CXX_STANDARD 17) 147 | # 148 | # But for some reason it isn't working on the CMake with Travis CI. 149 | # 150 | # http://stackoverflow.com/questions/40877744/ 151 | # 152 | add_cxx_flags("-std=c++17") 153 | 154 | # Turn up the warnings very high. 155 | # 156 | # http://stackoverflow.com/a/9862800/211160 157 | # 158 | # Currently not adding in `-Wshadow`, because @HostileFork likes naming 159 | # constructor arguments the same thing as the variables they initialize. 160 | # 161 | # For compiler identification notes: 162 | # 163 | # http://stackoverflow.com/a/10055571/211160 164 | 165 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 166 | 167 | # GCC and regular Clang or AppleClang share a lot of compatible switches 168 | 169 | add_cxx_flags(-Wall) 170 | add_cxx_flags(-Wsign-conversion) 171 | add_cxx_flags(-Wextra) 172 | add_cxx_flags(-Wcast-align) 173 | add_cxx_flags(-Wctor-dtor-privacy) 174 | add_cxx_flags(-Wdisabled-optimization) 175 | add_cxx_flags(-Wformat=2) 176 | add_cxx_flags(-Winit-self) 177 | add_cxx_flags(-Wmissing-declarations) 178 | add_cxx_flags(-Wmissing-include-dirs) 179 | add_cxx_flags(-Woverloaded-virtual) 180 | add_cxx_flags(-Wredundant-decls) 181 | add_cxx_flags(-Wsign-promo) 182 | add_cxx_flags(-Wstrict-overflow=5) 183 | add_cxx_flags(-Wswitch-default) 184 | add_cxx_flags(-Wundef) 185 | add_cxx_flags(-Wno-unused) 186 | add_cxx_flags(-pedantic) 187 | 188 | # We encourage *everyone* who is building the project to use the `-Werror` 189 | # switch. This converts messages that are considered "warnings" to the 190 | # status of being show-stopper errors. Being rigorous about this helps 191 | # keep distracting warnings from accumulating in the build process over 192 | # time, drowning out important messages that should be heeded. 193 | # 194 | # However...this idealism isn't long-term compatible with telling the 195 | # compiler it can throw in as many errors as it can think of (via the 196 | # switches `-Wpedantic`, `-Wall`, and `-Wextra`). Each time a new 197 | # compiler or new compiler version is used to build the project, it may 198 | # invent new and never before seen warnings that haven't been explicitly 199 | # disabled yet. 200 | # 201 | # Reports to help adjust the warnings to keep them at zero on relevant 202 | # platforms are preferred to turning off the switch. But the switch is 203 | # available to use on the command line: `-DRIGOROUS=no` 204 | 205 | if(NOT DEFINED(RIGOROUS) OR RIGOROUS) 206 | add_cxx_flags(-Werror) 207 | endif() 208 | 209 | endif() 210 | 211 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 212 | 213 | # using regular Clang or AppleClang 214 | 215 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 216 | 217 | # using GCC 218 | 219 | add_cxx_flags(-Wlogical-op) 220 | add_cxx_flags(-Wstrict-null-sentinel) 221 | 222 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 223 | 224 | # using Visual Studio C++ (note C++11 and beyond support on by default) 225 | 226 | else() 227 | 228 | # !!! If you're not using GCC, Clang, or MSVC then you're pretty much on 229 | # your own. Pull requests welcome, however. 230 | # 231 | # e.g. if(CMAKE_CXX_COMPILER_ID MATCHES "Intel")... 232 | 233 | endif() 234 | 235 | 236 | ### ADDRESS SANITIZER ### 237 | 238 | if (SANITIZE MATCHES "yes") 239 | add_cxx_flags(-fno-omit-frame-pointer) 240 | add_cxx_flags(-fsanitize=address) 241 | 242 | add_link_debug_flags(-fno-omit-frame-pointer) 243 | add_link_debug_flags(-fsanitize=address) 244 | endif () 245 | 246 | 247 | ### BUILD STEPS ### 248 | ### http://public.kitware.com/cgi-bin/viewcvs.cgi/Tests/Tutorial/?root=CMake 249 | 250 | # Make sure the compiler can find include files from our library. 251 | # 252 | include_directories (${NOCYCLE_SOURCE_DIR}) 253 | 254 | # Make sure the linker can find the Nocycle library once it is built. 255 | # 256 | link_directories (${NOCYCLE_BINARY_DIR}/nocycle) 257 | 258 | # Note: "lib" prefix is added automatically, using lowercase convention 259 | # (libnocycle) because that seems to be the way people do it 260 | # 261 | add_library (nocycle OrientedGraph.cpp DirectedAcyclicGraph.cpp) 262 | 263 | if (TEST_AGAINST_BOOST) 264 | find_package (Boost 1.34 REQUIRED) 265 | include_directories (${Boost_INCLUDE_DIRS}) 266 | 267 | add_executable (PerformanceTest PerformanceTest.cpp) 268 | # Link the executable to the libnocycle library 269 | target_link_libraries (PerformanceTest nocycle) 270 | endif (TEST_AGAINST_BOOST) 271 | -------------------------------------------------------------------------------- /DirectedAcyclicGraph.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // DirectedAcyclicGraph.cpp - Experimental DAG graph class with a 3 | // sidestructure of its transitive closure. Implemented as two 4 | // OrientedGraph objects, and various enhancements to the 5 | // sidestructure can be tried out. 6 | // 7 | // Copyright (c) 2009 HostileFork.com 8 | // 9 | // Distributed under the Boost Software License, Version 1.0. (See 10 | // accompanying file LICENSE_1_0.txt or copy at 11 | // http://www.boost.org/LICENSE_1_0.txt) 12 | // 13 | // See http://hostilefork.com/nocycle for documentation. 14 | // 15 | 16 | #include "DirectedAcyclicGraph.hpp" 17 | 18 | #if DIRECTEDACYCLICGRAPH_SELFTEST 19 | 20 | #include 21 | #include "BoostImplementation.hpp" 22 | #include "RandomEdgePicker.hpp" 23 | 24 | const unsigned NUM_TEST_NODES = 128; 25 | const float REMOVE_PROBABILITY = 1.0/8.0; // one in eight 26 | 27 | namespace nocycle { 28 | 29 | bool DirectedAcyclicGraph::SelfTest() { 30 | 31 | // Here are some simple test cases that can reveal basic breaks and are 32 | // easier to debug than random testing. 33 | 34 | if (true) { // Direct cycle 35 | DirectedAcyclicGraph dag(2); 36 | 37 | dag.CreateVertex(0); 38 | dag.CreateVertex(1); 39 | 40 | dag.SetEdge(0, 1); 41 | try { 42 | dag.SetEdge(1, 0); 43 | std::cout << "FAILURE: Did not catch direct cycle." << std::endl; 44 | return false; 45 | } catch (bad_cycle& e) { 46 | } 47 | } 48 | 49 | if (true) { // Simple transitive cycle 50 | DirectedAcyclicGraph dag(3); 51 | 52 | dag.CreateVertex(0); 53 | dag.CreateVertex(1); 54 | dag.CreateVertex(2); 55 | 56 | dag.SetEdge(0, 1); 57 | dag.SetEdge(1, 2); 58 | try { 59 | dag.SetEdge(2, 0); 60 | std::cout << "FAILURE: Did not catch simple transitive cycle." << std::endl; 61 | return false; 62 | } catch (bad_cycle& e) { 63 | } 64 | } 65 | 66 | if (true) { // Simple case of removing edge that would have caused a transitive cycle 67 | DirectedAcyclicGraph dag(3); 68 | 69 | dag.CreateVertex(0); 70 | dag.CreateVertex(1); 71 | dag.CreateVertex(2); 72 | 73 | dag.SetEdge(0, 1); 74 | dag.SetEdge(1, 2); 75 | dag.RemoveEdge(1, 2); 76 | try { 77 | dag.SetEdge(2, 0); 78 | } catch (bad_cycle& e) { 79 | std::cout << "FAILURE: Deletion of simple transitive cycle edge still threw cycle exception." << std::endl; 80 | return false; 81 | } 82 | } 83 | 84 | if (true) { // Drew this out on paper as a first test, keeping it 85 | 86 | DirectedAcyclicGraph dag(5); 87 | 88 | dag.CreateVertex(0); 89 | dag.CreateVertex(1); 90 | dag.CreateVertex(2); 91 | dag.CreateVertex(3); 92 | dag.CreateVertex(4); 93 | 94 | dag.SetEdge(0, 2); 95 | dag.SetEdge(1, 2); 96 | dag.SetEdge(1, 3); 97 | dag.SetEdge(2, 3); 98 | dag.SetEdge(4, 0); 99 | dag.SetEdge(4, 3); 100 | try { 101 | dag.SetEdge(2, 4); 102 | std::cout << "FAILURE: Did not catch random case I drew on a sheet of paper." << std::endl; 103 | return false; 104 | } catch (bad_cycle& e) { 105 | } 106 | } 107 | 108 | if (true) { // Reduced case of something that triggered a code path that broke once 109 | DirectedAcyclicGraph dag(5); 110 | 111 | dag.CreateVertex(0); 112 | dag.CreateVertex(1); 113 | dag.CreateVertex(2); 114 | dag.CreateVertex(3); 115 | 116 | dag.SetEdge(1,2); 117 | dag.RemoveEdge(1,2); 118 | dag.SetEdge(3,1); 119 | dag.SetEdge(0,3); 120 | try { 121 | dag.SetEdge(2, 0); 122 | } catch (bad_cycle& e) { 123 | std::cout << "FAILURE: False cycle found, no path from 0->2 yet insertion of 2->0 failed." << std::endl; 124 | return false; 125 | } 126 | try { 127 | dag.SetEdge(1, 0); 128 | std::cout << "FAILURE: Did not find cycle 1->0->3->1." << std::endl; 129 | return false; 130 | } catch (bad_cycle& e) { 131 | } 132 | } 133 | 134 | // Here is the fuzz testing approach with a lot of random adds and removes. 135 | // http://en.wikipedia.org/wiki/Fuzz_testing 136 | // (If this fails, try recompiling with DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK set to 1, 137 | // that will help pinpoint the source of the invalid state) 138 | 139 | typedef RandomEdgePicker DAGType; 140 | 141 | if (true) { 142 | unsigned numCyclesCaught = 0; 143 | unsigned numInsertions = 0; 144 | unsigned numDeletions = 0; 145 | 146 | DAGType dag (NUM_TEST_NODES); 147 | BoostDirectedAcyclicGraph bdag (NUM_TEST_NODES); 148 | 149 | for (DAGType::VertexID vertex = 0; vertex < NUM_TEST_NODES; vertex++) { 150 | dag.CreateVertex(vertex); 151 | bdag.CreateVertex(vertex); 152 | } 153 | 154 | // keep adding random vertices and make sure that if it causes a cycle exception on the boost 155 | // implemented class, that it also causes a cycle exception on the non-boost class 156 | unsigned index = 0; 157 | while (index < (NUM_TEST_NODES * NUM_TEST_NODES) / 4) { 158 | DAGType::VertexID vertexSource; 159 | DAGType::VertexID vertexDest; 160 | 161 | bool removeEdge = (dag.NumEdges() > 0) && ((rand() % 10000) < (REMOVE_PROBABILITY * 10000)); 162 | 163 | if (removeEdge) { 164 | dag.GetRandomEdge(vertexSource, vertexDest); 165 | 166 | bdag.RemoveEdge(vertexSource, vertexDest); 167 | dag.RemoveEdge(vertexSource, vertexDest); 168 | numDeletions++; 169 | 170 | } else { 171 | dag.GetRandomNonEdge(vertexSource, vertexDest); 172 | 173 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 174 | Nstate<3> randomTristate (static_cast(rand()) % 3); 175 | #endif 176 | 177 | bool causedCycleInBoost = false; 178 | try { 179 | bdag.AddEdge(vertexSource, vertexDest); 180 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 181 | bdag.SetTristateForConnection(vertexSource, vertexDest, randomTristate); 182 | #endif 183 | } catch (bad_cycle& e) { 184 | causedCycleInBoost = true; 185 | } 186 | 187 | bool causedCycle = false; 188 | try { 189 | dag.AddEdge(vertexSource, vertexDest); 190 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 191 | dag.SetTristateForConnection(vertexSource, vertexDest, randomTristate); 192 | #endif 193 | } catch (bad_cycle& e) { 194 | causedCycle = true; 195 | } 196 | 197 | if (causedCycle != causedCycleInBoost) { 198 | std::cout << "FAILURE: Insertion of edge that " << (causedCycleInBoost ? "caused a cycle" : "did not cause a cycle") << 199 | " in Boost " << (causedCycle ? "caused a cycle" : "did not cause a cycle") << " in the DirectedAcyclicGraph implementation." << std::endl; 200 | return false; 201 | } 202 | 203 | if (causedCycle) 204 | numCyclesCaught++; 205 | else { 206 | numInsertions++; 207 | } 208 | } 209 | 210 | index++; 211 | } 212 | 213 | std::cout << "NOTE: Inserted " << numInsertions << ", Deleted " << numDeletions << ", and Caught " << numCyclesCaught << " cycles." << std::endl; 214 | 215 | // Graphs should be identical after this 216 | if (bdag != dag) { 217 | std::cout << "FAILURE: DirectedAcyclicGraph not equivalent to version of DirectedAcyclicGraph implemented via Boost Graph library." << std::endl; 218 | return false; 219 | } 220 | 221 | } 222 | 223 | return true; 224 | } 225 | 226 | } // end namespace nocycle 227 | 228 | #endif 229 | -------------------------------------------------------------------------------- /DirectedAcyclicGraph.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // DirectedAcyclicGraph.hpp - Experimental DAG graph class with a 3 | // sidestructure of its transitive closure. Implemented as two 4 | // OrientedGraph objects, and various enhancements to the 5 | // sidestructure can be tried out. 6 | // 7 | // Copyright (c) 2009 HostileFork.com 8 | // 9 | // Distributed under the Boost Software License, Version 1.0. (See 10 | // accompanying file LICENSE_1_0.txt or copy at 11 | // http://www.boost.org/LICENSE_1_0.txt) 12 | // 13 | // See http://hostilefork.com/nocycle for documentation. 14 | // 15 | 16 | #pragma once 17 | 18 | #include "NocycleConfig.hpp" 19 | 20 | #include "OrientedGraph.hpp" 21 | 22 | #include 23 | #include 24 | 25 | namespace nocycle { 26 | 27 | // 28 | // EXCEPTIONS 29 | // See: http://www.cplusplus.com/doc/tutorial/exceptions.html 30 | // 31 | class bad_cycle : public std::exception { 32 | virtual const char* what() const throw() { 33 | return "Attempt to insert a cycle into a DirectedAcyclicGraph"; 34 | } 35 | }; 36 | 37 | 38 | // Each class that uses another in its implementation should insulate its 39 | // clients from that fact. A lazy man's composition pattern can be achieved with 40 | // private inheritance: http://www.parashift.com/c++-faq-lite/private-inheritance.html 41 | // 42 | // Public inheritance lets users coerce pointers from the derived class to the 43 | // base class. Without virtual methods, this is bad because it disregards the 44 | // overriden methods. As Nocycle is refined I will make decisions about this, but I 45 | // use public inheritance for expedience while the library is still in flux. 46 | class DirectedAcyclicGraph : public OrientedGraph { 47 | public: 48 | typedef OrientedGraph::VertexID VertexID; 49 | 50 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 51 | // Sidestructure for fast O(1) acyclic insertion 52 | // 53 | // IF no edge A->B or A<-B is in the main data, then m_canreach is the 54 | // transitive closure relationship. e.g.: 55 | // 56 | // * no edge between A and B indicates any edge is permitted. e.g., 57 | // (B cantreach A) and (A cantreach B) 58 | // 59 | // * A->B indicates that (A canreach B) and (B cantreach A) 60 | // hence edges from A->B are ok, but B->A would create a cycle 61 | // 62 | // * A<-B indicates that (A cantreach B) and (B canreach A) 63 | // hence edges from B->A are ok, but A->B would create a cycle 64 | // 65 | // IF there is a edge in the main data, then the canreach data for 66 | // that edge is somewhat redundant, and it is possible to derive 67 | // an independent tristate for each such connection. It may be 68 | // possible to use that information to enhance the behavior of 69 | // the transitive closure sidestructure, so there's some experiments 70 | // to that effect...such as caching whether the pointed to vertex 71 | // would be reachable if the physical edge were removed. 72 | private: 73 | OrientedGraph m_canreach; 74 | #endif 75 | 76 | public: 77 | DirectedAcyclicGraph(const size_t initial_size) : 78 | OrientedGraph(initial_size) 79 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 80 | , m_canreach (initial_size) 81 | #endif 82 | { 83 | } 84 | 85 | virtual ~DirectedAcyclicGraph() { 86 | } 87 | 88 | // Special features of our DAG's sidestructure 89 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 90 | // If a physical connection exists between fromVertex and toVertex, we can use its 91 | // reachability data for other purposes...effectively, a tristate! 92 | // public for testing, but will probably go private 93 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 94 | private: 95 | enum ExtraTristate { 96 | isReachableWithoutEdge = 0, 97 | notReachableWithoutEdge = 1, 98 | thirdStateNotSureWhatToDoWithIt = 2 99 | }; 100 | #endif 101 | 102 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 103 | public: 104 | #else 105 | private: 106 | #endif 107 | Nstate<3> GetTristateForConnection(VertexID fromVertex, VertexID toVertex) const { 108 | assert(EdgeExists(fromVertex, toVertex)); 109 | 110 | bool forwardEdge, reverseEdge; 111 | if (m_canreach.HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge)) { 112 | if (forwardEdge) 113 | return 1; 114 | 115 | if (reverseEdge) 116 | return 2; 117 | 118 | assert(false); 119 | } 120 | 121 | return 0; 122 | } 123 | void SetTristateForConnection(VertexID fromVertex, VertexID toVertex, Nstate<3> tristate) { 124 | assert(EdgeExists(fromVertex, toVertex)); 125 | 126 | bool forwardEdge, reverseEdge; 127 | m_canreach.HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge); 128 | 129 | switch (tristate) { 130 | case 0: 131 | if (forwardEdge) 132 | m_canreach.RemoveEdge(fromVertex, toVertex); 133 | if (reverseEdge) 134 | m_canreach.RemoveEdge(toVertex, fromVertex); 135 | break; 136 | 137 | case 1: 138 | if (reverseEdge) 139 | m_canreach.RemoveEdge(toVertex, fromVertex); 140 | m_canreach.SetEdge(fromVertex, toVertex); 141 | break; 142 | 143 | case 2: 144 | if (forwardEdge) 145 | m_canreach.RemoveEdge(fromVertex, toVertex); 146 | m_canreach.SetEdge(toVertex, fromVertex); 147 | break; 148 | 149 | default: 150 | assert(false); 151 | } 152 | } 153 | 154 | private: 155 | // For each vertex in the canreach data, we say its reachability is either "clean" 156 | // or "dirty". Current strategy is that dirty reachability has false positives but 157 | // no false negatives. 158 | 159 | static const auto canreachClean = vertexTypeOne; 160 | static const auto canreachMayHaveFalsePositives = vertexTypeTwo; 161 | 162 | bool ClearReachEdge(VertexID fromVertex, VertexID toVertex) { 163 | // don't want to damage a tristate! 164 | assert(!HasLinkage(fromVertex, toVertex)); 165 | return m_canreach.ClearEdge(fromVertex, toVertex); 166 | } 167 | void RemoveReachEdge(VertexID fromVertex, VertexID toVertex) { 168 | if (!ClearReachEdge(fromVertex, toVertex)) 169 | assert (false); 170 | } 171 | 172 | bool SetReachEdge(VertexID fromVertex, VertexID toVertex) { 173 | // don't want to damage a tristate! 174 | assert(!HasLinkage(fromVertex, toVertex)); 175 | return m_canreach.SetEdge(fromVertex, toVertex); 176 | } 177 | void AddReachEdge(VertexID fromVertex, VertexID toVertex) { 178 | if (!SetReachEdge(fromVertex, toVertex)) 179 | assert (false); 180 | } 181 | 182 | // The "incoming reach" is all incoming edges, and for all non-incoming edges that 183 | // are also not outgoing edges... the data contained in the canreach graph. 184 | // (Note: includes vertex, as being able to "reach" itself) 185 | std::set IncomingReachForVertexIncludingSelf(VertexID vertex) { 186 | std::set incoming = IncomingEdgesForVertex(vertex); 187 | std::set incomingReach = m_canreach.IncomingEdgesForVertex(vertex); 188 | std::set::iterator incomingReachIter = incomingReach.begin(); 189 | while (incomingReachIter != incomingReach.end()) { 190 | VertexID incomingReachVertex = *incomingReachIter; 191 | if (!HasLinkage(vertex, incomingReachVertex)) 192 | incoming.insert(incomingReachVertex); 193 | incomingReachIter++; 194 | } 195 | incoming.insert(vertex); 196 | return incoming; 197 | } 198 | 199 | // The "outgoing reach" is all outgoing edges, and for all non-outgoing edges that 200 | // are also not incoming edges... the data contained in the canreach graph. 201 | // (Note: includes vertex, as being able to "reach" itself) 202 | std::set OutgoingReachForVertexIncludingSelf(VertexID vertex) { 203 | std::set outgoing = OutgoingEdgesForVertex(vertex); 204 | std::set outgoingReach = m_canreach.OutgoingEdgesForVertex(vertex); 205 | std::set::iterator outgoingReachIter = outgoingReach.begin(); 206 | while (outgoingReachIter != outgoingReach.end()) { 207 | VertexID outgoingReachVertex = *outgoingReachIter; 208 | if (!HasLinkage(outgoingReachVertex, vertex)) 209 | outgoing.insert(outgoingReachVertex); 210 | outgoingReachIter++; 211 | } 212 | outgoing.insert(vertex); 213 | return outgoing; 214 | } 215 | 216 | // cleans as much reachability data as needed to answer if from can reach 217 | // to. In theory, can leave the vertex dirty if answer is NO... 218 | void CleanUpReachability(VertexID fromVertex, VertexID toVertex) { 219 | // fromVertex's canreach data is dirty. that means that some of the outgoing 220 | // edges may be false positives. Start by clearing all outgoing reachability 221 | // edges...but remember which ones since those are the only ones that need 222 | // fixing 223 | std::set outgoingReachAndNoise = m_canreach.OutgoingEdgesForVertex(fromVertex); 224 | std::set::iterator outgoingReachAndNoiseIter = outgoingReachAndNoise.begin(); 225 | std::set outgoingReachBeforeClean; 226 | while (outgoingReachAndNoiseIter != outgoingReachAndNoise.end()) { 227 | VertexID outgoingReachAndNoiseVertex = (*outgoingReachAndNoiseIter++); 228 | if (HasLinkage(fromVertex, outgoingReachAndNoiseVertex)) { 229 | // noise, ignore it 230 | } else { 231 | outgoingReachBeforeClean.insert(outgoingReachAndNoiseVertex); 232 | // we don't really need to modify the edge here... 233 | // in fact, it causes us to break the "false positives" invariant 234 | // (hence why I'm saving the outgoingReachBeforeClean) 235 | RemoveReachEdge(fromVertex, outgoingReachAndNoiseVertex); 236 | } 237 | } 238 | 239 | // now go through all the vertices to which there is a physical connection 240 | // if their canreach data is good, then use it 241 | // otherwise, clean it and use it 242 | // (there will be no loops because it's acyclic) 243 | std::set outgoing = OutgoingEdgesForVertex(fromVertex); 244 | std::set::iterator outgoingIter = outgoing.begin(); 245 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 246 | std::map > mapOfOutgoingReachIncludingSelf; 247 | #endif 248 | while (outgoingIter != outgoing.end()) { 249 | OrientedGraph::VertexID outgoingVertex = (*outgoingIter++); 250 | 251 | if (m_canreach.GetVertexType(outgoingVertex) == canreachMayHaveFalsePositives) 252 | CleanUpReachability(outgoingVertex, toVertex); 253 | 254 | std::set outgoingForOutgoing = OutgoingReachForVertexIncludingSelf(outgoingVertex); 255 | 256 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 257 | mapOfOutgoingReachIncludingSelf[outgoingVertex] = outgoingForOutgoing; 258 | #endif 259 | std::set::iterator outgoingForOutgoingIter = outgoingForOutgoing.begin(); 260 | while (outgoingForOutgoingIter != outgoingForOutgoing.end()) { 261 | VertexID outgoingForOutgoingVertex = (*outgoingForOutgoingIter++); 262 | if (outgoingVertex == outgoingForOutgoingVertex) 263 | continue; 264 | 265 | if (!EdgeExists(fromVertex, outgoingForOutgoingVertex)) { 266 | if (m_canreach.EdgeExists(outgoingForOutgoingVertex, fromVertex)) { 267 | VertexType vertexTypeOutgoingForOutgoing = m_canreach.GetVertexType(outgoingForOutgoingVertex); 268 | assert(vertexTypeOutgoingForOutgoing == canreachMayHaveFalsePositives); 269 | RemoveReachEdge(outgoingForOutgoingVertex, fromVertex); 270 | } 271 | SetReachEdge(fromVertex, outgoingForOutgoingVertex); 272 | } 273 | } 274 | } 275 | 276 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 277 | std::map >::iterator mapOfOutgoingReachIncludingSelfIter = mapOfOutgoingReachIncludingSelf.begin(); 278 | while (mapOfOutgoingReachIncludingSelfIter != mapOfOutgoingReachIncludingSelf.end()) { 279 | VertexID linkedVertex = (*mapOfOutgoingReachIncludingSelfIter).first; 280 | if (GetTristateForConnection(fromVertex, linkedVertex) == isReachableWithoutEdge) { 281 | bool foundOtherPath = false; 282 | std::map >::iterator mapOfOutgoingReachIncludingSelfOtherIter = mapOfOutgoingReachIncludingSelf.begin(); 283 | while (!foundOtherPath && (mapOfOutgoingReachIncludingSelfOtherIter != mapOfOutgoingReachIncludingSelf.end())) { 284 | if (mapOfOutgoingReachIncludingSelfIter != mapOfOutgoingReachIncludingSelfOtherIter) { 285 | std::set outgoingOtherReachIncludingOther = (*mapOfOutgoingReachIncludingSelfOtherIter).second; 286 | if (outgoingOtherReachIncludingOther.find(linkedVertex) !=outgoingOtherReachIncludingOther.end()) 287 | foundOtherPath = true; 288 | } 289 | mapOfOutgoingReachIncludingSelfOtherIter++; 290 | } 291 | 292 | if (!foundOtherPath) 293 | SetTristateForConnection(fromVertex, linkedVertex, notReachableWithoutEdge); 294 | } 295 | 296 | mapOfOutgoingReachIncludingSelfIter++; 297 | } 298 | #endif 299 | 300 | m_canreach.SetVertexType(fromVertex, canreachClean); 301 | } 302 | #endif 303 | 304 | public: 305 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 306 | bool CanReach(VertexID fromVertex, VertexID toVertex) { 307 | 308 | // If there is a physical edge, then we are using the canreach data for other purposes 309 | bool forwardEdge, reverseEdge; 310 | if (HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge)) { 311 | 312 | // If a physical edge exists to the target vertex, we can reach it 313 | if (forwardEdge) 314 | return true; 315 | 316 | // If a physical edge exists from the target to us, then if we could reach 317 | // it then that would cause a cycle 318 | if (reverseEdge) 319 | return false; 320 | 321 | assert(false); 322 | return false; 323 | } 324 | 325 | // When there's no physical edge, the canreach data is the transitive closure, except 326 | // for "dirtiness" 327 | 328 | switch (m_canreach.GetVertexType(fromVertex)) { 329 | case canreachClean: 330 | return m_canreach.EdgeExists(fromVertex, toVertex); 331 | 332 | case canreachMayHaveFalsePositives: 333 | if (!m_canreach.EdgeExists(fromVertex, toVertex)) 334 | return false; 335 | CleanUpReachability(fromVertex, toVertex); 336 | return m_canreach.EdgeExists(fromVertex, toVertex); 337 | 338 | default: 339 | assert(false); 340 | return false; 341 | } 342 | 343 | assert(false); 344 | return false; 345 | } 346 | #else 347 | bool CanReach(VertexID fromVertex, VertexID toVertex) { 348 | // Simply do a depth-first search to determine reachability 349 | // Using LIFO stack instead of recursion... 350 | assert(fromVertex != toVertex); 351 | 352 | std::set visitedVertices; 353 | std::stack searchStack; 354 | searchStack.push(fromVertex); 355 | 356 | while (!searchStack.empty()) { 357 | VertexID searchVertex = searchStack.top(); 358 | searchStack.pop(); 359 | 360 | std::set outgoing = OutgoingEdgesForVertex(searchVertex); 361 | std::set::iterator outgoingIter = outgoing.begin(); 362 | while (outgoingIter != outgoing.end()) { 363 | VertexID outgoingVertex = (*outgoingIter++); 364 | if (outgoingVertex == toVertex) 365 | return true; 366 | if (visitedVertices.find(outgoingVertex) != visitedVertices.end()) 367 | continue; 368 | visitedVertices.insert(outgoingVertex); 369 | searchStack.push(outgoingVertex); 370 | } 371 | } 372 | 373 | return false; 374 | } 375 | #endif 376 | 377 | public: 378 | // This expands the buffer vector so that it can accommodate the existence and 379 | // connection data for vertexL. Any new vertices added will not exist yet and not 380 | // have connection data. Any vertices existing above this ID # will 381 | void SetCapacityForMaxValidVertexID(VertexID vertexL) { 382 | OrientedGraph::SetCapacityForMaxValidVertexID(vertexL); 383 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 384 | m_canreach.SetCapacityForMaxValidVertexID(vertexL); 385 | #endif 386 | } 387 | void SetCapacitySoVertexIsFirstInvalidID(VertexID vertexL) { 388 | OrientedGraph::SetCapacitySoVertexIsFirstInvalidID(vertexL); 389 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 390 | m_canreach.SetCapacityForMaxValidVertexID(vertexL); 391 | #endif 392 | } 393 | void GrowCapacityForMaxValidVertexID(VertexID vertexL) { 394 | OrientedGraph::GrowCapacityForMaxValidVertexID(vertexL); 395 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 396 | m_canreach.SetCapacityForMaxValidVertexID(vertexL); 397 | #endif 398 | } 399 | void ShrinkCapacitySoVertexIsFirstInvalidID(VertexID vertexL) { 400 | OrientedGraph::ShrinkCapacitySoVertexIsFirstInvalidID(vertexL); 401 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 402 | m_canreach.ShrinkCapacitySoVertexIsFirstInvalidID(vertexL); 403 | #endif 404 | } 405 | 406 | // 407 | // CREATION OVERRIDES 408 | // 409 | public: 410 | void CreateVertexEx(VertexID vertexE, VertexType vertexType) { 411 | OrientedGraph::CreateVertexEx(vertexE, vertexType); 412 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 413 | m_canreach.CreateVertexEx(vertexE, canreachClean); 414 | #endif 415 | } 416 | inline void CreateVertex(VertexID vertexE) { 417 | return CreateVertexEx(vertexE, vertexTypeOne); 418 | } 419 | 420 | // 421 | // DESTRUCTION OVERRIDES 422 | // 423 | public: 424 | inline void DestroyVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL ) { 425 | OrientedGraph::DestroyVertexEx(vertex, vertexType, compactIfDestroy, incomingEdgeCount, outgoingEdgeCount); 426 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 427 | unsigned incomingEdgeCanreach; 428 | unsigned outgoingEdgeCanreach; 429 | VertexType vertexTypeCanreach; 430 | m_canreach.DestroyVertexEx(vertex, vertexTypeCanreach, compactIfDestroy, &incomingEdgeCanreach, &outgoingEdgeCanreach); 431 | #endif 432 | } 433 | inline void DestroyVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) { 434 | VertexType vertexType; 435 | return DestroyVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount); 436 | } 437 | inline void DestroyVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) { 438 | VertexType vertexType; 439 | return DestroyVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount); 440 | } 441 | // presupposition: no incoming edges, only points to others (if any) 442 | void DestroySourceVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* outgoingEdgeCount = NULL) { 443 | unsigned incomingEdgeCount; 444 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, outgoingEdgeCount); 445 | assert(incomingEdgeCount == 0); 446 | } 447 | inline void DestroySourceVertex(VertexID vertex, unsigned* outgoingEdgeCount = NULL) { 448 | VertexType vertexType; 449 | return DestroySourceVertexEx(vertex, vertexType, true /* compactIfDestroy */, outgoingEdgeCount); 450 | } 451 | inline void DestroySourceVertexDontCompact(VertexID vertex, unsigned* outgoingEdgeCount = NULL) { 452 | VertexType vertexType; 453 | return DestroySourceVertexEx(vertex, vertexType, false /* compactIfDestroy */, outgoingEdgeCount); 454 | } 455 | // presupposition: no outgoing edges, only incoming ones (if any) 456 | void DestroySinkVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL) { 457 | unsigned outgoingEdgeCount; 458 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, incomingEdgeCount, &outgoingEdgeCount); 459 | assert(outgoingEdgeCount == 0); 460 | } 461 | inline void DestroySinkVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL) { 462 | VertexType vertexType; 463 | return DestroySinkVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount); 464 | } 465 | inline void DestroySinkVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL) { 466 | VertexType vertexType; 467 | return DestroySinkVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount); 468 | } 469 | // presupposition: no incoming or outgoing edges 470 | void DestroyIsolatedVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true) { 471 | unsigned incomingEdgeCount; 472 | unsigned outgoingEdgeCount; 473 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, &outgoingEdgeCount); 474 | assert(incomingEdgeCount == 0); 475 | assert(outgoingEdgeCount == 0); 476 | } 477 | inline void DestroyIsolatedVertex(VertexID vertex) { 478 | VertexType vertexType; 479 | DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ ); 480 | } 481 | inline void DestroyIsolatedVertexDontCompact(VertexID vertex) { 482 | VertexType vertexType; 483 | DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ ); 484 | } 485 | 486 | public: 487 | // SetEdge throws exceptions on cycle. To avoid having to write exception handling, 488 | // use this routine before calling SetEdge. 489 | inline bool InsertionWouldCauseCycle(VertexID fromVertex, VertexID toVertex) { 490 | return CanReach(toVertex, fromVertex); 491 | } 492 | 493 | bool SetEdge(VertexID fromVertex, VertexID toVertex) { 494 | #if DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK 495 | ConsistencyCheck cc (*this); 496 | #endif 497 | 498 | if (InsertionWouldCauseCycle(fromVertex, toVertex)) { 499 | bad_cycle bc; 500 | throw bc; 501 | } 502 | 503 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 504 | // this may have false positives, for the moment let's union the "false positive tristate" 505 | // with the rest of the "false positive" reachability data... 506 | bool reachablePriorToEdge = m_canreach.EdgeExists(fromVertex, toVertex); 507 | #endif 508 | 509 | // set the physical edge in the data structure 510 | bool edgeIsNew = OrientedGraph::SetEdge(fromVertex, toVertex); 511 | if (!edgeIsNew) 512 | return false; 513 | 514 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 515 | // save whether the toVertex was reachable prior to the physical connection in the 516 | // extra tristate for this edge 517 | if (reachablePriorToEdge) { 518 | SetTristateForConnection(fromVertex, toVertex, isReachableWithoutEdge); 519 | } else { 520 | SetTristateForConnection(fromVertex, toVertex, notReachableWithoutEdge); 521 | } 522 | #endif 523 | 524 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 525 | // All the vertices that toVertex "canreach", including itself 526 | // (Note: may contain false positives if vertexTypeTo == canreachMayHaveFalsePositives) 527 | std::set toCanreach = OutgoingReachForVertexIncludingSelf(toVertex); 528 | 529 | VertexType vertexTypeTo = m_canreach.GetVertexType(toVertex); 530 | 531 | // All the vertices that "canreach" fromVertex, including itself 532 | // (Note: may contain false positives if the incoming vertices are of type canreachMayHaveFalsePositives) 533 | // (Note2: contains "lies"... if any of these vertices have physical edges, you'll be missing 534 | std::set canreachFrom = IncomingReachForVertexIncludingSelf(fromVertex); 535 | 536 | VertexType vertexTypeFrom = m_canreach.GetVertexType(fromVertex); 537 | 538 | // Now update the reachability. 539 | 540 | // All the vertices that canreach fromVertex (including fromVertex!) can now 541 | // reach toVertex, as well as anything toVertex can reach...worst case O(N^2) "operations" but they 542 | // are fast operations. Dirtiness needs to propagate, too. 543 | 544 | std::set::iterator iterCanreachFrom = canreachFrom.begin(); 545 | while (iterCanreachFrom != canreachFrom.end()) { 546 | 547 | VertexID canreachFromVertex = (*iterCanreachFrom++); 548 | 549 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 550 | // These new reachabilities may lead us to need to bump a physical connection on 551 | // vertices that can reach fromVertex regarding how it can reach a vertex that toVertex canreach. 552 | // This would be from notReachableWithoutEdge to isReachableWithoutEdge. Basically, 553 | // if some vertex to which you are getting a reachability relationship is able to reach 554 | // any of the vertices to which you are physically edgeed to, then you must bump that edge 555 | // if it was notReachableWithoutEdge 556 | 557 | std::set outgoing = OutgoingEdgesForVertex(canreachFromVertex); 558 | std::set::iterator outgoingIter = outgoing.begin(); 559 | while (outgoingIter != outgoing.end()) { 560 | VertexID outgoingVertex = *outgoingIter++; 561 | if ((outgoingVertex == toVertex) && (canreachFromVertex == fromVertex)) 562 | continue; 563 | /* if (GetTristateForConnection(canreachFromVertex, outgoingVertex) == notReachableWithoutEdge) {*/ 564 | if (toCanreach.find(outgoingVertex) != toCanreach.end()) { 565 | SetTristateForConnection(canreachFromVertex, outgoingVertex, isReachableWithoutEdge); 566 | if (vertexTypeTo == canreachMayHaveFalsePositives) 567 | SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives); 568 | } 569 | /* }*/ 570 | } 571 | #endif 572 | 573 | std::set::iterator iterToCanreach = toCanreach.begin(); 574 | while (iterToCanreach != toCanreach.end()) { 575 | 576 | VertexID toCanreachVertex = (*iterToCanreach++); 577 | assert(canreachFromVertex != toCanreachVertex); 578 | 579 | bool forwardEdge, reverseEdge; 580 | HasLinkage(canreachFromVertex, toCanreachVertex, &forwardEdge, &reverseEdge); 581 | if (forwardEdge) { 582 | // one case is that there's a physical edge and this is just a tristate 583 | // that edge would have to be from canreachFromVertex to toCanreachVertex 584 | // leave it alone 585 | } else { 586 | VertexType vertexTypeToCanreach = m_canreach.GetVertexType(toCanreachVertex); 587 | if (vertexTypeToCanreach == canreachMayHaveFalsePositives) { 588 | // there might be stale false positives which could have been 589 | // left behind, and we need to tolerate those. 590 | ClearReachEdge(toCanreachVertex, canreachFromVertex); 591 | } else { 592 | // if it were actually true that a vertex that toVertex canreach could reach 593 | // a vertex that canreach fromVertex, then a connection from toVertex to fromVertex 594 | // would be a cycle. 595 | assert(!m_canreach.EdgeExists(toCanreachVertex, canreachFromVertex)); 596 | } 597 | 598 | if (reverseEdge) { 599 | assert(vertexTypeToCanreach == canreachMayHaveFalsePositives); 600 | // we know for a fact here now that canreachFromVertex *can't* reach toCanreachVertex 601 | // so ignore this and don't propagate it... 602 | } else { 603 | VertexType vertexTypeCanreachFrom = m_canreach.GetVertexType(canreachFromVertex); 604 | if ((vertexTypeCanreachFrom == canreachClean) && (vertexTypeTo == canreachClean) && (vertexTypeFrom == canreachClean)) 605 | m_canreach.SetVertexType(canreachFromVertex, canreachClean); 606 | else 607 | m_canreach.SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives); 608 | SetReachEdge(canreachFromVertex, toCanreachVertex); 609 | } 610 | } 611 | } 612 | } 613 | #endif 614 | 615 | return true; 616 | } 617 | void AddEdge(VertexID fromVertex, VertexID toVertex) { 618 | if (!SetEdge(fromVertex, toVertex)) 619 | assert(false); 620 | } 621 | 622 | bool ClearEdge(VertexID fromVertex, VertexID toVertex) { 623 | #if DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK 624 | ConsistencyCheck cc (*this); 625 | #endif 626 | 627 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 628 | if (!EdgeExists(fromVertex, toVertex)) 629 | return false; 630 | 631 | ExtraTristate extra = static_cast(static_cast(GetTristateForConnection(fromVertex, toVertex))); 632 | SetTristateForConnection(fromVertex, toVertex, 0); // clear out tristate 633 | 634 | OrientedGraph::RemoveEdge(fromVertex, toVertex); 635 | 636 | // we have a short cut. if we are removing a edge, and our invalidation data does not have 637 | // "false positives"... then if the vertex tristate said we were connected prior to the edge 638 | // we still will be after remove it. We do not need to mark anything as having false 639 | // positives. 640 | VertexType vertexTypeFrom = m_canreach.GetVertexType(fromVertex); 641 | if ((vertexTypeFrom == canreachClean) && (extra == isReachableWithoutEdge)) { 642 | m_canreach.AddEdge(fromVertex, toVertex); 643 | return true; 644 | } 645 | #else 646 | if (!OrientedGraph::ClearEdge(fromVertex, toVertex)) 647 | return false; 648 | #endif 649 | 650 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 651 | // removing a edge calls into question all of our canreach vertices, and all the canreach vertices of vertices that 652 | // canreach us... however, anything downstream of us is guaranteed to not have its reachability affected 653 | // due to the lack of cyclic relationships. e.g. anything that we "canreach" is excluded from concern 654 | // of recalculation of its canreach properties 655 | 656 | // because the update can be somewhat costly, we merely dirty ourselves and all the vertices we canreach 657 | // and let a background process take care of the cleaning. this does not affect readers, only writers, 658 | // and insertions on disconnected regions of the graph will not affect each other 659 | 660 | // All the vertices that canreach fromVertex...these have their reachability data coming into question 661 | // (Note: we may be dirtying more than we need to due to "false positives" in the reachability) 662 | std::set canreachFrom = IncomingReachForVertexIncludingSelf(fromVertex); 663 | 664 | std::set::iterator canreachFromIter = canreachFrom.begin(); 665 | while (canreachFromIter != canreachFrom.end()) { 666 | OrientedGraph::VertexID canreachFromVertex = (*canreachFromIter); 667 | m_canreach.SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives); 668 | canreachFromIter++; 669 | } 670 | 671 | // if there was a tristate associated with this edge, we're losing it 672 | // allow for false positives...there may have been a transitive edge! 673 | if (m_canreach.EdgeExists(toVertex, fromVertex)) 674 | m_canreach.RemoveEdge(toVertex, fromVertex); 675 | m_canreach.SetEdge(fromVertex, toVertex); 676 | #endif 677 | return true; 678 | } 679 | void RemoveEdge(VertexID fromVertex, VertexID toVertex) { 680 | if (!ClearEdge(fromVertex, toVertex)) 681 | assert(false); 682 | } 683 | 684 | 685 | // 686 | // DEBUGGING ROUTINES 687 | // 688 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 689 | public: 690 | std::set OutgoingTransitiveVertices(VertexID vertex, const VertexID* vertexIgnoreEdge, bool includeDirectEdges) { 691 | std::set result; 692 | std::stack vertexStack; 693 | 694 | std::set outgoingInit = OutgoingEdgesForVertex(vertex); 695 | std::set::iterator outgoingInitIter = outgoingInit.begin(); 696 | while (outgoingInitIter != outgoingInit.end()) { 697 | VertexID outgoingVertex = (*outgoingInitIter++); 698 | if (!vertexIgnoreEdge || (*vertexIgnoreEdge != outgoingVertex)) { 699 | vertexStack.push(outgoingVertex); 700 | if (includeDirectEdges) 701 | result.insert(outgoingVertex); 702 | } 703 | } 704 | 705 | while (!vertexStack.empty()) { 706 | VertexID vertexCurrent = vertexStack.top(); 707 | vertexStack.pop(); 708 | 709 | std::set outgoing = OutgoingEdgesForVertex(vertexCurrent); 710 | std::set::iterator outgoingIter = outgoing.begin(); 711 | while (outgoingIter != outgoing.end()) { 712 | VertexID outgoingVertex = (*outgoingIter++); 713 | if (result.find(outgoingVertex) == result.end()) { 714 | vertexStack.push(outgoingVertex); 715 | result.insert(outgoingVertex); 716 | } 717 | } 718 | 719 | } 720 | 721 | return result; 722 | } 723 | std::set OutgoingTransitiveVerticesNotDirectlyEdged(VertexID vertex) { 724 | return OutgoingTransitiveVertices(vertex, NULL, false); 725 | } 726 | 727 | bool IsInternallyConsistent() { 728 | for (OrientedGraph::VertexID vertex = 0; vertex < GetFirstInvalidVertexID(); vertex++) { 729 | if (!VertexExists(vertex)) 730 | continue; 731 | 732 | std::set outgoingReach = OutgoingReachForVertexIncludingSelf(vertex); 733 | std::set outgoingTransitive = OutgoingTransitiveVerticesNotDirectlyEdged(vertex); 734 | std::set outgoingTransitiveClosure = outgoingTransitive; 735 | std::set outgoing = OutgoingEdgesForVertex(vertex); 736 | outgoingTransitiveClosure.insert(outgoing.begin(), outgoing.end()); 737 | outgoingTransitiveClosure.insert(vertex); 738 | 739 | switch (m_canreach.GetVertexType(vertex)) { 740 | case canreachClean: { 741 | size_t outgoingReachSize = outgoingReach.size(); 742 | size_t outgoingTransitiveClosureSize = outgoingTransitiveClosure.size(); 743 | 744 | std::set::iterator outgoingTransitiveClosureIter = outgoingTransitiveClosure.begin(); 745 | while (outgoingTransitiveClosureIter != outgoingTransitiveClosure.end()) { 746 | VertexID outgoingTransitiveClosureVertex = (*outgoingTransitiveClosureIter++); 747 | if (outgoingReach.find(outgoingTransitiveClosureVertex) == outgoingReach.end()) 748 | return false; 749 | } 750 | assert(outgoingReach.size() == outgoingTransitiveClosure.size()); 751 | 752 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 753 | std::set::iterator outgoingIter = outgoing.begin(); 754 | while (outgoingIter != outgoing.end()) { 755 | VertexID outgoingVertex = (*outgoingIter++); 756 | std::set outgoingTransitiveWithoutEdge = OutgoingTransitiveVertices(vertex, &outgoingVertex, false); 757 | ExtraTristate extra = static_cast(static_cast(GetTristateForConnection(vertex, outgoingVertex))); 758 | switch (extra) { 759 | case isReachableWithoutEdge: 760 | if (outgoingTransitiveWithoutEdge.find(outgoingVertex) == outgoingTransitiveWithoutEdge.end()) 761 | return false; 762 | break; 763 | 764 | case notReachableWithoutEdge: 765 | if (outgoingTransitiveWithoutEdge.find(outgoingVertex) != outgoingTransitiveWithoutEdge.end()) 766 | return false; 767 | break; 768 | 769 | default: 770 | assert(false); 771 | } 772 | } 773 | #endif 774 | break; } 775 | 776 | case canreachMayHaveFalsePositives: { 777 | std::set::iterator outgoingTransitiveClosureIter = outgoingTransitiveClosure.begin(); 778 | while (outgoingTransitiveClosureIter != outgoingTransitiveClosure.end()) { 779 | VertexID outgoingTransitiveClosureVertex = (*outgoingTransitiveClosureIter++); 780 | if (outgoingReach.find(outgoingTransitiveClosureVertex) == outgoingReach.end()) 781 | return false; 782 | } 783 | assert(outgoingReach.size() >= outgoingTransitiveClosure.size()); 784 | break; } 785 | 786 | default: 787 | assert(false); 788 | } 789 | } 790 | 791 | return true; 792 | } 793 | 794 | class ConsistencyCheck { 795 | private: 796 | DirectedAcyclicGraph& m_dag; 797 | public: 798 | ConsistencyCheck(DirectedAcyclicGraph& dag) : m_dag (dag) { }; 799 | virtual ~ConsistencyCheck() { 800 | assert(m_dag.IsInternallyConsistent()); 801 | } 802 | }; 803 | #endif 804 | 805 | public: 806 | static bool SelfTest(); 807 | }; 808 | 809 | } // end namespace nocycle 810 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /NocycleConfig.hpp.in: -------------------------------------------------------------------------------- 1 | // If filename ends in '.in' then it is a CMake configurable header 2 | // http://www.vtk.org/Wiki/CMake_HowToDoPlatformChecks 3 | 4 | // 5 | // NocycleConfig.hpp(.in) - This is a centralization of flags for 6 | // conditional compilation of the Nocycle...mostly to do with debug 7 | // code. Although the boost library is needed for much of the 8 | // testing, if you aren't doing a test build then it should not 9 | // be necessary to include any boost libraries (at least, not yet). 10 | // 11 | // Copyright (c) 2009 HostileFork.com 12 | // 13 | // Distributed under the Boost Software License, Version 1.0. (See 14 | // accompanying file LICENSE_1_0.txt or copy at 15 | // http://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // See http://hostilefork.com/nocycle for documentation. 18 | // 19 | 20 | #pragma once 21 | 22 | // Note: Regarding the debate of whether to use #define/#undef and #ifdef or 23 | // to use #define 0/1 and #if, I'm in the latter camp. For rationale: 24 | // http://stackoverflow.com/questions/135069/ifdef-vs-if-which-is-bettersafer 25 | 26 | // This means we use '#cmakedefine01' syntax instead of '#cmakedefine' 27 | // http://www.cmake.org/pipermail/cmake/2009-June/030086.html 28 | 29 | // Several pieces of Nocycle have self testing code. These tests may rely 30 | // on the boost library. If you would like to build a version of Nocycle 31 | // without a dependency on boost, make sure all these are set to 0. 32 | #cmakedefine01 NSTATE_SELFTEST 33 | #cmakedefine01 ORIENTEDGRAPH_SELFTEST 34 | #cmakedefine01 DIRECTEDACYCLICGRAPH_SELFTEST 35 | 36 | // Though nocycle distinguishes between vertices that have no connections 37 | // and those which "don't exist", boost's default assumption is that 38 | // all nodes in its capacity "exist". The only way to conceptually delete 39 | // a boost vertex from an adjacency_matrix it is to remove all of its 40 | // incoming and outgoing connections. Though it is possible to inject 41 | // a vertex property map to track existence, the property map adds overhead 42 | // and may not fairly represent boost's performance in scenarios with 43 | // large numbers of nodes when existence tracking is not needed. 44 | #cmakedefine01 BOOSTIMPLEMENTATION_TRACK_EXISTENCE 45 | 46 | // Experimental attempt to cache transitive closure, not for general use 47 | #cmakedefine01 DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 48 | 49 | // If caching the transitive closure... 50 | // There is an "extra tristate" we get in the canreach graph when there is a physical 51 | // edge in the data graph. We can use this to accelerate the invalidation process, 52 | // but for testing purposes it's nice to make sure the algorithms aren't corrupting 53 | // this implicitly when modifying other edges. 54 | #cmakedefine01 DIRECTEDACYCLICGRAPH_USER_TRISTATE 55 | 56 | // If caching the transitive closure... 57 | // ...and the extra tristate per edge is NOT visible to the user... 58 | // Then use it to cache whether a vertex can be reached even after the physical link 59 | // to it is removed from the graph. 60 | #cmakedefine01 DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 61 | 62 | // If caching the transitive closure... 63 | // If 1, then perform heavy consistency checks on the transitive closure sidestructure 64 | // If 0, don't do the checks. 65 | #cmakedefine01 DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK 66 | 67 | 68 | 69 | // 70 | // STATIC ASSERTS 71 | // These check to make sure you didn't specify incompatible options 72 | // http://www.devx.com/tips/Tip/14448 73 | // 74 | // !!! This logic should likely be in CMake. 75 | // 76 | 77 | #if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY 78 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE && DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 79 | #error "Can't use DIRECTEDACYCLICGRAPH_USER_TRISTATE and DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK together" 80 | #endif 81 | #else 82 | #if DIRECTEDACYCLICGRAPH_USER_TRISTATE 83 | #error "Can't use DIRECTEDACYCLICGRAPH_USER_TRISTATE without DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY" 84 | #endif 85 | #if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK 86 | #error "Can't use DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK without DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY" 87 | #endif 88 | #if DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK 89 | #error "Can't use DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK" 90 | #endif 91 | #endif 92 | -------------------------------------------------------------------------------- /Nstate.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Nstate.hpp - Template for variable that can only take on values 3 | // from [0..radix-1], and an array type which is able to 4 | // store a series of these variables and take advantage of 5 | // the knowledge of the constraint in order to compact them 6 | // closer to their fundamentally minimal size. 7 | // 8 | // Copyright (c) 2009-2012 HostileFork.com 9 | // Distributed under the Boost Software License, Version 1.0. 10 | // (See accompanying file LICENSE_1_0.txt or copy at 11 | // http://www.boost.org/LICENSE_1_0.txt) 12 | // 13 | // See http://hostilefork.com/nstate for documentation. 14 | // 15 | 16 | #pragma once 17 | 18 | #include "NocycleConfig.hpp" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // REVIEW: std::numeric_limits? 26 | #include 27 | #include 28 | 29 | // 30 | // NSTATE 31 | // 32 | 33 | namespace nocycle { 34 | 35 | // see: http://www.cplusplus.com/doc/tutorial/exceptions.html 36 | class bad_nstate : public std::exception { 37 | virtual const char* what() const throw() { 38 | return "Invalid Nstate value"; 39 | } 40 | }; 41 | 42 | typedef unsigned PackedTypeForNstate; 43 | 44 | template 45 | class Nstate { 46 | private: 47 | unsigned m_value; // ranges from [0..radix-1] 48 | 49 | private: 50 | void ThrowExceptionIfBadValue() { 51 | if (m_value >= radix) { 52 | bad_nstate bt; 53 | throw bt; 54 | } 55 | } 56 | 57 | public: 58 | // constructor is also implicit cast operator from unsigned 59 | // see: http://www.acm.org/crossroads/xrds3-1/ovp3-1.html 60 | 61 | Nstate(unsigned value) : m_value (value) { 62 | ThrowExceptionIfBadValue(); 63 | } 64 | // default constructor setting value to zero... good idea? Bad idea? 65 | Nstate() : m_value (0) { 66 | ThrowExceptionIfBadValue(); 67 | } 68 | // implicit cast operator to unsigned 69 | operator unsigned() const { 70 | return m_value; 71 | } 72 | 73 | // "A base class destructor should be either public and virtual, 74 | // or protected and nonvirtual." 75 | // http://www.gotw.ca/publications/mill18.htm 76 | 77 | public: 78 | virtual ~Nstate() { } 79 | 80 | #if NSTATE_SELFTEST 81 | public: 82 | static bool SelfTest(); // Class is self-testing for regression 83 | #endif 84 | }; 85 | 86 | 87 | class PowerTable { 88 | private: 89 | std::vector m_table; 90 | public: 91 | PowerTable (unsigned radix) { 92 | unsigned nstatesInUnsigned = static_cast(floor(log(2)/log(radix)*CHAR_BIT*sizeof(unsigned))); 93 | PackedTypeForNstate value = 1; 94 | for (unsigned index = 0; index < nstatesInUnsigned; index++) { 95 | m_table.push_back(value); 96 | value = value * radix; 97 | } 98 | } 99 | inline size_t NstatesInPackedType() const { 100 | return m_table.size(); 101 | } 102 | inline PackedTypeForNstate PowerForDigit(unsigned digit) const { 103 | assert(digit < m_table.size()); 104 | return m_table[digit]; 105 | } 106 | public: 107 | virtual ~PowerTable () { } 108 | }; 109 | 110 | 111 | // 112 | // NSTATE ARRAY 113 | // 114 | 115 | // TODO: 116 | // * iterators? 117 | // * 64-bit packing or something more clever? 118 | // * memory-mapped file implementation, with standardized packing/order 119 | // (to work across platforms)? 120 | template 121 | class NstateArray { 122 | private: 123 | // Note: Typical library limits of the STL for vector lengths 124 | // are things like 1,073,741,823... 125 | std::vector m_buffer; 126 | size_t m_max; 127 | 128 | private: 129 | static const PowerTable& GetPowerTableInstance() { 130 | // We wish to cache the PowerTable so that all NstateArray instances of the same 131 | // radix share the same one. This is hard to do correctly and/or elegantly in 132 | // a way that is befitting a "general" class library. 133 | 134 | // http://stackoverflow.com/questions/9507973/how-to-mitigate-user-facing-api-effect-of-shared-members-in-templated-classes 135 | 136 | // The power tables are not that large, and in C++11 code this is guaranteed to 137 | static const PowerTable instance (radix); 138 | return instance; 139 | } 140 | static size_t NstatesInPackedType() { 141 | return GetPowerTableInstance().NstatesInPackedType(); 142 | } 143 | static PackedTypeForNstate PowerForDigit(unsigned digit) { 144 | return GetPowerTableInstance().PowerForDigit(digit); 145 | } 146 | 147 | private: 148 | Nstate GetDigitInPackedValue(PackedTypeForNstate packed, unsigned digit) const; 149 | PackedTypeForNstate SetDigitInPackedValue(PackedTypeForNstate packed, unsigned digit, Nstate t) const; 150 | 151 | public: 152 | // Derived from boost's dynamic_bitset 153 | // http://www.boost.org/doc/libs/1_36_0/libs/dynamic_bitset/dynamic_bitset.html 154 | class reference; 155 | friend class NstateArray::reference; 156 | class reference { 157 | friend class NstateArray; 158 | 159 | private: 160 | NstateArray& m_na; 161 | unsigned m_indexIntoBuffer; 162 | unsigned m_digit; 163 | reference(NstateArray &na, size_t indexIntoBuffer, unsigned digit) : 164 | m_na (na), 165 | m_indexIntoBuffer (indexIntoBuffer), 166 | m_digit (digit) 167 | { 168 | } 169 | 170 | void operator&(); // not defined 171 | void do_assign(Nstate x) { 172 | m_na.m_buffer[m_indexIntoBuffer] = 173 | m_na.SetDigitInPackedValue(m_na.m_buffer[m_indexIntoBuffer], m_digit, x); 174 | } 175 | public: 176 | // An automatically generated copy constructor. 177 | reference& operator=(Nstate x) { do_assign(x); return *this; } // for b[i] = x 178 | reference& operator=(const reference& rhs) { do_assign(rhs); return *this; } // for b[i] = b[j] 179 | 180 | operator Nstate() const { 181 | return m_na.GetDigitInPackedValue(m_na.m_buffer[m_indexIntoBuffer], m_digit); 182 | } 183 | operator unsigned() const { 184 | return m_na.GetDigitInPackedValue(m_na.m_buffer[m_indexIntoBuffer], m_digit); 185 | } 186 | }; 187 | 188 | reference operator[](size_t pos) { 189 | assert(pos < m_max); // STL will only check bounds on integer boundaries 190 | size_t indexIntoBuffer = pos / NstatesInPackedType(); 191 | unsigned digit = pos % NstatesInPackedType(); 192 | return reference (*this, indexIntoBuffer, digit); 193 | } 194 | 195 | Nstate operator[](size_t pos) const { 196 | assert(pos < m_max); // STL will only check bounds on integer boundaries. 197 | size_t indexIntoBuffer = pos / NstatesInPackedType(); 198 | unsigned digit = pos % NstatesInPackedType(); 199 | return GetDigitInPackedValue(m_buffer[indexIntoBuffer], digit); 200 | } 201 | 202 | // by convention, we resize and fill available space with zeros if expanding 203 | void ResizeWithZeros(size_t max) { 204 | size_t oldBufferSize = m_buffer.size(); 205 | size_t newBufferSize = max / NstatesInPackedType() + 206 | (max % NstatesInPackedType() == 0 ? 0 : 1); 207 | 208 | unsigned oldMaxDigitNeeded = m_max % NstatesInPackedType(); 209 | if ((oldMaxDigitNeeded == 0) && (m_max > 0)) 210 | oldMaxDigitNeeded = NstatesInPackedType(); 211 | 212 | unsigned newMaxDigitNeeded = max % NstatesInPackedType(); 213 | if ((newMaxDigitNeeded == 0) && (max > 0)) 214 | newMaxDigitNeeded = NstatesInPackedType(); 215 | 216 | m_buffer.resize(newBufferSize, 0 /* fill value */); 217 | m_max = max; 218 | 219 | if ((newBufferSize == oldBufferSize) && (newMaxDigitNeeded < oldMaxDigitNeeded)) { 220 | 221 | // If we did not change the size of the buffer but merely the number of 222 | // nstates we are fitting in the lastmost packed value, we must set 223 | // the trailing unused nstates to zero if we are using fewer nstates 224 | // than we were before 225 | for (auto eraseDigit = newMaxDigitNeeded; eraseDigit < oldMaxDigitNeeded; eraseDigit++) { 226 | m_buffer[newBufferSize - 1] = 227 | SetDigitInPackedValue(m_buffer[newBufferSize - 1], eraseDigit, 0); 228 | } 229 | 230 | } else if ((newBufferSize < oldBufferSize) && (newMaxDigitNeeded > 0)) { 231 | 232 | // If the number of tristates we are using isn't an even multiple of the 233 | // # of states that fit in a packed type, then shrinking will leave some 234 | // residual values we need to reset to zero in the last element of the vector. 235 | for (auto eraseDigit = newMaxDigitNeeded; eraseDigit < NstatesInPackedType(); eraseDigit++) { 236 | m_buffer[newBufferSize - 1] = 237 | SetDigitInPackedValue(m_buffer[newBufferSize - 1], eraseDigit, 0); 238 | } 239 | } 240 | } 241 | 242 | size_t Length() const { 243 | return m_max; 244 | } 245 | 246 | // Constructors and destructors 247 | 248 | public: 249 | NstateArray(const size_t initial_size) : 250 | m_max (0) 251 | { 252 | ResizeWithZeros(initial_size); 253 | } 254 | virtual ~NstateArray () 255 | { 256 | } 257 | 258 | #if NSTATE_SELFTEST 259 | public: 260 | static bool SelfTest(); // Class is self-testing for regression 261 | #endif 262 | }; 263 | 264 | 265 | // 266 | // Static member functions 267 | // 268 | 269 | template 270 | Nstate NstateArray::GetDigitInPackedValue(PackedTypeForNstate packed, unsigned digit) const { 271 | 272 | // Generalized from Mark Bessey's post in the Joel on Software forum 273 | // http://discuss.joelonsoftware.com/default.asp?joel.3.205331.14 274 | 275 | assert(digit < NstatesInPackedType()); 276 | 277 | // lop off unused top digits - you can skip this 278 | // for the most-significant digit 279 | PackedTypeForNstate value = packed; 280 | if (digit < (NstatesInPackedType()-1)) { 281 | value = value % PowerForDigit(digit+1); 282 | } 283 | // truncate lower digit 284 | value = value / PowerForDigit(digit); 285 | return value; 286 | } 287 | 288 | template 289 | PackedTypeForNstate NstateArray::SetDigitInPackedValue(PackedTypeForNstate packed, unsigned digit, Nstate t) const { 290 | assert(digit < NstatesInPackedType()); 291 | 292 | PackedTypeForNstate powForDigit = PowerForDigit(digit); 293 | 294 | PackedTypeForNstate upperPart = 0; 295 | if (digit < (NstatesInPackedType() - 1)) { 296 | PackedTypeForNstate powForDigitPlusOne = PowerForDigit(digit + 1); 297 | upperPart = (packed / powForDigitPlusOne) * powForDigitPlusOne; 298 | } 299 | 300 | PackedTypeForNstate setPart = t * powForDigit; 301 | 302 | PackedTypeForNstate lowerPart = 0; 303 | if (digit > 0) 304 | lowerPart = packed % powForDigit; 305 | 306 | return upperPart + setPart + lowerPart; 307 | } 308 | 309 | } // temporary end of namespace nocycle 310 | 311 | 312 | #if NSTATE_SELFTEST 313 | 314 | // 315 | // CODE FOR SELF-TESTS 316 | // 317 | // See: http://gcc.gnu.org/ml/gcc-bugs/2000-12/msg00168.html 318 | // "If the definitions of member functions of a template aren't visible 319 | // when the template is instantiated, they won't be instantiated. You 320 | // must define template member functions in the header file itself, at 321 | // least until the `export' keyword is implemented." 322 | // 323 | 324 | #include 325 | #include 326 | 327 | namespace nocycle { 328 | 329 | template 330 | bool Nstate::SelfTest() { 331 | for (unsigned test = 0; test < radix; test++) { 332 | Nstate t (test); 333 | if (t != test) { 334 | std::cout << "FAILURE: Nstates did not preserve values properly." << std::endl; 335 | return false; 336 | } 337 | } 338 | 339 | try { 340 | Nstate t3 (radix); 341 | std::cout << "FAILURE: Did not detect bad Nstate construction." << std::endl; 342 | return false; 343 | } catch (bad_nstate& e) { 344 | } 345 | 346 | try { 347 | Nstate tSet (0); 348 | tSet = radix; 349 | std::cout << "FAILURE: Did not detect bad Nstate SetValue." << std::endl; 350 | return false; 351 | } catch (bad_nstate& e) { 352 | } 353 | 354 | return true; 355 | } 356 | 357 | template 358 | bool NstateArray::SelfTest() { 359 | // Basic allocation and set test 360 | for (size_t initialSize = 0; initialSize < 1024; initialSize++) { 361 | NstateArray nv (initialSize); 362 | std::vector v (initialSize); 363 | for (size_t index = 0; index < initialSize; index++) { 364 | Nstate tRand (rand() % radix); 365 | nv[index] = tRand; 366 | v[index] = tRand; 367 | } 368 | for (size_t index = 0; index < initialSize; index++) { 369 | if (nv[index] != v[index]) { 370 | std::cout << "FAILURE: On NstateArray[" << initialSize << "] for size " << initialSize 371 | << ", a value was recorded as " << static_cast(nv[index]) 372 | << " when it should have been " << static_cast(v[index]) << std::endl; 373 | return false; 374 | } 375 | } 376 | 377 | // Try resizing it to some smaller size 378 | size_t newSmallerSize; 379 | if (initialSize == 0) 380 | newSmallerSize = 0; 381 | else { 382 | newSmallerSize = (rand() % initialSize); 383 | nv.ResizeWithZeros(newSmallerSize); 384 | v.resize(newSmallerSize, 0); 385 | assert(nv.Length() == v.size()); 386 | 387 | for (size_t index = 0; index < newSmallerSize; index++) { 388 | if (nv[index] != v[index]) { 389 | std::cout << "FAILURE: On NstateArray[" << initialSize << "] after resize from " << initialSize 390 | << " to " << newSmallerSize 391 | << ", a value was recorded as " << static_cast(nv[index]) 392 | << " when it should have been " << static_cast(v[index]) << std::endl; 393 | return false; 394 | } 395 | } 396 | } 397 | 398 | // Try resizing it to some larger size 399 | size_t newLargerSize = newSmallerSize + (rand() % 128); 400 | nv.ResizeWithZeros(newLargerSize); 401 | v.resize(newLargerSize, 0); 402 | assert(nv.Length() == v.size()); 403 | 404 | for (size_t index = 0; index < newLargerSize; index++) { 405 | if (nv[index] != v[index]) { 406 | std::cout << "FAILURE: On NstateArray[" << initialSize << "] after resize from " << newSmallerSize 407 | << " to " << newLargerSize 408 | << ", a value was recorded as " << static_cast(nv[index]) 409 | << " when it should have been " << static_cast(v[index]) << std::endl; 410 | return false; 411 | } 412 | } 413 | } 414 | 415 | return true; 416 | } 417 | 418 | } // end namespace nocycle 419 | 420 | #endif 421 | -------------------------------------------------------------------------------- /OrientedGraph.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // OrientedGraph.cpp - Class which compactly stores an adjacency matrix 3 | // for a graph, in which each vertex may have a maximum of one 4 | // directed connection to any other vertex. It is built upon the 5 | // Nstate class for efficiently building arrays of tristates, and 6 | // the memory layout is optimized for dynamically growing and 7 | // shrinking the graph capacity. 8 | // 9 | // Copyright (c) 2009 HostileFork.com 10 | // 11 | // Distributed under the Boost Software License, Version 1.0. (See 12 | // accompanying file LICENSE_1_0.txt or copy at 13 | // http://www.boost.org/LICENSE_1_0.txt) 14 | // 15 | // See http://hostilefork.com/nocycle for documentation. 16 | // 17 | 18 | #include "OrientedGraph.hpp" 19 | 20 | #if ORIENTEDGRAPH_SELFTEST 21 | 22 | #include 23 | #include "BoostImplementation.hpp" 24 | #include "RandomEdgePicker.hpp" 25 | 26 | namespace nocycle { 27 | 28 | bool OrientedGraph::SelfTest() { 29 | 30 | const unsigned NUM_TEST_NODES = 128; 31 | 32 | typedef RandomEdgePicker OGType; 33 | 34 | OGType og (NUM_TEST_NODES); 35 | BoostOrientedGraph bog (NUM_TEST_NODES); 36 | 37 | for (OGType::VertexID vertex = 0; vertex < NUM_TEST_NODES; vertex++) { 38 | if (og.VertexExists(vertex)) { 39 | std::cout << "FAILURE: Vertex #" << vertex << 40 | " should not exist after constructing empty OrientedGraph" << std::endl; 41 | return false; 42 | } 43 | } 44 | 45 | for (OGType::VertexID vertex = 0; vertex < NUM_TEST_NODES; vertex++) { 46 | if ((rand() % 4) != 0) { // use 75% of available VertexIDs 47 | og.CreateVertex(vertex); 48 | bog.CreateVertex(vertex); 49 | } 50 | } 51 | 52 | #if BOOSTIMPLEMENTATION_TRACK_EXISTENCE 53 | for (OGType::VertexID vertex = 0; vertex < NUM_TEST_NODES; vertex++) { 54 | bool shouldExist = bog.VertexExists(vertex); 55 | if (og.VertexExists(vertex) != shouldExist) { 56 | std::cout << "FAILURE: OrientedGraph Vertex #" << vertex << (shouldExist ? " should exist and does not " : 57 | " should not exist, and it does. ") << std::endl; 58 | return false; 59 | } 60 | } 61 | #endif 62 | 63 | // add a smattering of connections to both graphs 64 | for (unsigned index = 0; index < (NUM_TEST_NODES * NUM_TEST_NODES) / 4; index++) { 65 | OGType::VertexID vertexSource; 66 | OGType::VertexID vertexDest; 67 | 68 | og.GetRandomNonEdge(vertexSource, vertexDest); 69 | 70 | bog.AddEdge(vertexSource, vertexDest); 71 | og.AddEdge(vertexSource, vertexDest); 72 | } 73 | 74 | // Graphs should be identical after this 75 | if (bog != og) { 76 | std::cout << "FAILURE: OrientedGraph not equivalent to version of OrientedGraph implemented via Boost Graph library." << std::endl; 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | } // end namespace nocycle 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /OrientedGraph.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // OrientedGraph.hpp - Class which compactly stores an adjacency matrix 3 | // for a graph, in which each vertex may have a maximum of one 4 | // directed connection to any other vertex. It is built upon the 5 | // Nstate class for efficiently building arrays of tristates, and 6 | // the memory layout is optimized for dynamically growing and 7 | // shrinking the graph capacity. 8 | // 9 | // Copyright (c) 2009 HostileFork.com 10 | // 11 | // Distributed under the Boost Software License, Version 1.0. (See 12 | // accompanying file LICENSE_1_0.txt or copy at 13 | // http://www.boost.org/LICENSE_1_0.txt) 14 | // 15 | // See http://hostilefork.com/nocycle for documentation. 16 | // 17 | 18 | #pragma once 19 | 20 | #include "NocycleConfig.hpp" 21 | 22 | #include // numeric_limits 23 | #include 24 | #include 25 | 26 | #include "Nstate.hpp" 27 | //#include "nstate/Nstate.hpp" 28 | 29 | namespace nocycle { 30 | 31 | // The graph will grow dynamically to accomodate 32 | class OrientedGraph { 33 | public: 34 | typedef unsigned VertexID; 35 | class EdgeIter; 36 | friend class EdgeIter; 37 | 38 | private: 39 | enum VertexExistenceTristate { 40 | doesNotExist = 0, 41 | existsAsTypeOne = 1, 42 | existsAsTypeTwo = 2 43 | }; 44 | enum VertexConnectionTristate { 45 | notConnected = 0, 46 | lowPointsToHigh = 1, 47 | highPointsToLow = 2 48 | }; 49 | 50 | public: 51 | enum VertexType { 52 | vertexTypeOne, 53 | vertexTypeTwo 54 | }; 55 | 56 | private: 57 | NstateArray<3> m_buffer; 58 | 59 | private: 60 | // E(N) => N*(N-1)/2 61 | // Explained at http://hostilefork.com/nocycle/ 62 | inline size_t TristateIndexForExistence(VertexID vertexE) const { 63 | assert(vertexE < std::numeric_limits::max()); 64 | return (vertexE * static_cast(vertexE + 1)) / 2; 65 | } 66 | 67 | // C(S,L) => E(L) + (L - S) 68 | // Explained at http://hostilefork.com/nocycle/ 69 | inline size_t TristateIndexForConnection(VertexID vertexS, VertexID vertexL) const { 70 | assert(vertexL < std::numeric_limits::max()); 71 | assert(vertexS < vertexL); 72 | 73 | return TristateIndexForExistence(vertexL) + (vertexL - vertexS); 74 | } 75 | 76 | void VertexFromExistenceTristateIndex(size_t pos, VertexID& vertexE) const { 77 | // The index into the NstateArray can be *big*, and multiplying by 8 can exceed size_t 78 | // Must cast to an unsigned long long to do this calculation 79 | vertexE = static_cast((sqrt(1 + 8 * static_cast(pos)) - 1) / 2); 80 | assert(TristateIndexForExistence(vertexE) == pos); // should be *exact*, not rounded down!!!! 81 | } 82 | 83 | void VerticesFromConnectionTristateIndex(size_t pos, VertexID& vertexS, VertexID& vertexL) const { 84 | vertexL = static_cast((sqrt(1 + 8 * static_cast(pos)) - 1) / 2); 85 | size_t tife = TristateIndexForExistence(vertexL); 86 | assert(tife != pos); // should be rounded down, not *exact*!!! 87 | /* vertexL - vertexS = pos - tife; */ 88 | assert (tife + vertexL > pos); 89 | vertexS = (tife + vertexL) - pos; 90 | assert(vertexL > vertexS); 91 | assert(TristateIndexForConnection(vertexS, vertexL) == pos); 92 | } 93 | 94 | bool IsExistenceTristateIndex(size_t pos) const { 95 | return TristateIndexForExistence(static_cast((sqrt(1 + 8 * static_cast(pos)) - 1) / 2)) == pos; 96 | } 97 | 98 | bool IsConnectionTristateIndex(size_t pos) const { 99 | return !IsExistenceTristateIndex(pos); 100 | } 101 | 102 | public: 103 | // We could cache this, but it can be computed from 104 | // the NstateArray length. 105 | // 106 | // Since E(N) => N*(N+1)/2... 107 | // solve for N we get 0 = N^2 + N - 2E(N) 108 | // quadratic equation tells us (-b +/- sqrt( b^2 - 4ac)) / 2a 109 | // so ruling out negative N values... (-1 + sqrt(1 + 4*2E(N)))/2 110 | // = (sqrt(1 + 8E(N)) - 1)/2 111 | VertexID GetFirstInvalidVertexID() const { 112 | if (m_buffer.Length() == 0) 113 | return 0; // Zero is not valid, we can't track its existence 114 | 115 | VertexID ret; 116 | VertexFromExistenceTristateIndex(m_buffer.Length(), ret); 117 | return ret; // this will be the number of the first invalid vertex 118 | } 119 | 120 | // Variant of GetFirstInvalidVertexID(). A little confusing interface, since we may have an empty graph and 121 | // we may also have a graph containing nothing but vertex 0... 122 | unsigned GetMaxValidVertexID(bool& noValidID) const { 123 | unsigned ret = GetFirstInvalidVertexID(); 124 | if (ret == 0) { 125 | noValidID = true; 126 | return std::numeric_limits::max(); 127 | } 128 | noValidID = false; 129 | return (ret - 1); 130 | } 131 | 132 | // This expands the buffer vector so that it can accommodate the existence and 133 | // connection data for vertexL. Any new vertices added will not exist yet and not 134 | // have connection data. Any vertices existing above this ID # will 135 | void SetCapacityForMaxValidVertexID(VertexID vertexL) { 136 | assert(vertexL < std::numeric_limits::max()); // max is reserved for max invalid vertex ID 137 | m_buffer.ResizeWithZeros(TristateIndexForExistence(vertexL + 1)); 138 | } 139 | void SetCapacitySoVertexIsFirstInvalidID(VertexID vertexL) { 140 | if (vertexL == 0) 141 | m_buffer.ResizeWithZeros(0); 142 | else 143 | m_buffer.ResizeWithZeros(TristateIndexForExistence(vertexL)); 144 | } 145 | void GrowCapacityForMaxValidVertexID(VertexID vertexL) { 146 | assert(vertexL >= GetFirstInvalidVertexID()); 147 | SetCapacityForMaxValidVertexID(vertexL); 148 | } 149 | void ShrinkCapacitySoVertexIsFirstInvalidID(VertexID vertexL) { 150 | assert(vertexL < GetFirstInvalidVertexID()); 151 | SetCapacitySoVertexIsFirstInvalidID(vertexL); 152 | } 153 | 154 | // This core routine is used to get vertex information, and it can also delete vertices and their connections while doing so 155 | private: 156 | void GetVertexInfoMaybeDestroy( 157 | VertexID vertexE, 158 | bool &exists, 159 | VertexType& vertexType, 160 | unsigned* incomingEdgeCount = NULL, 161 | unsigned* outgoingEdgeCount = NULL, 162 | std::set* incomingEdges = NULL, 163 | std::set* outgoingEdges = NULL, 164 | bool destroyIfExists = false, 165 | bool compactIfDestroy = true 166 | ){ 167 | switch (m_buffer[TristateIndexForExistence(vertexE)]) { 168 | case doesNotExist: 169 | exists = false; 170 | break; 171 | 172 | case existsAsTypeOne: 173 | exists = true; 174 | vertexType = vertexTypeOne; 175 | break; 176 | 177 | case existsAsTypeTwo: 178 | exists = true; 179 | vertexType = vertexTypeTwo; 180 | break; 181 | 182 | default: 183 | assert(false); 184 | } 185 | 186 | if (outgoingEdgeCount != NULL) 187 | *outgoingEdgeCount = 0; 188 | if (incomingEdgeCount != NULL) 189 | *incomingEdgeCount = 0; 190 | 191 | if (!exists) 192 | return; 193 | 194 | // check connections, if requested 195 | if ((incomingEdgeCount != NULL) || (outgoingEdgeCount != NULL) || (incomingEdges != NULL) || (outgoingEdges != NULL)) { 196 | for (VertexID vertexT = 0; vertexT < GetFirstInvalidVertexID(); vertexT++) { 197 | if (vertexT == vertexE) 198 | continue; 199 | 200 | VertexID vertexS = vertexT < vertexE ? vertexT : vertexE; 201 | VertexID vertexL = vertexT > vertexE ? vertexT : vertexE; 202 | 203 | switch (m_buffer[TristateIndexForConnection(vertexS, vertexL)]) { 204 | case notConnected: 205 | continue; 206 | 207 | case lowPointsToHigh: 208 | if (vertexE < vertexT) { 209 | if (outgoingEdgeCount != NULL) 210 | (*outgoingEdgeCount)++; 211 | if (outgoingEdges != NULL) 212 | outgoingEdges->insert(vertexT); 213 | } else { 214 | if (incomingEdgeCount != NULL) 215 | (*incomingEdgeCount)++; 216 | if (incomingEdges != NULL) 217 | incomingEdges->insert(vertexT); 218 | } 219 | break; 220 | 221 | case highPointsToLow: 222 | if (vertexE > vertexT) { 223 | if (outgoingEdgeCount != NULL) 224 | (*outgoingEdgeCount)++; 225 | if (outgoingEdges != NULL) 226 | outgoingEdges->insert(vertexT); 227 | } else { 228 | if (incomingEdgeCount != NULL) 229 | (*incomingEdgeCount)++; 230 | if (incomingEdges != NULL) 231 | incomingEdges->insert(vertexT); 232 | } 233 | break; 234 | 235 | default: 236 | assert(false); 237 | } 238 | 239 | // Destroying a vertex's existence also destroys all incoming and outgoing connections for that vertex 240 | if (destroyIfExists) 241 | m_buffer[TristateIndexForConnection(vertexS, vertexL)] = notConnected; 242 | } 243 | } 244 | 245 | if (destroyIfExists && exists) { 246 | m_buffer[TristateIndexForExistence(vertexE)] = doesNotExist; 247 | 248 | // caller can tell us to make a destruction do a compaction 249 | // (because not all destroys compact, we may have trailing data...) 250 | // we can potentially save on the destroy loops above if we know we're going to shrink! 251 | if (compactIfDestroy) { 252 | bool noValidID; 253 | unsigned vertexT = GetMaxValidVertexID(noValidID); 254 | assert(!noValidID); 255 | 256 | if (m_buffer[TristateIndexForExistence(vertexT)] == doesNotExist) { 257 | VertexID vertexFirstUnused = vertexT; 258 | while (vertexFirstUnused > 0) { 259 | if (m_buffer[TristateIndexForExistence(vertexFirstUnused - 1)] != doesNotExist) 260 | break; 261 | vertexFirstUnused = vertexFirstUnused - 1; 262 | } 263 | ShrinkCapacitySoVertexIsFirstInvalidID(vertexFirstUnused); 264 | } 265 | } 266 | } 267 | } 268 | void GetVertexInfo(VertexID vertexE, bool &exists, VertexType& vertexType) const { 269 | return const_cast(this)->GetVertexInfoMaybeDestroy(vertexE, exists, vertexType); 270 | } 271 | void GetVertexInfoMaybeCounts(VertexID vertexE, bool &exists, VertexType& vertexType, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) const { 272 | return const_cast(this)->GetVertexInfoMaybeDestroy(vertexE, exists, vertexType, incomingEdgeCount, outgoingEdgeCount); 273 | } 274 | 275 | public: 276 | 277 | // 278 | // NODE EXISTENCE ROUTINES 279 | // 280 | 281 | // Sees if a vertex exists, and tells you if it exists in 'state one' or 'state two' 282 | inline bool VertexExistsEx(VertexID vertexE, VertexType& vertexType) const { 283 | bool exists; 284 | const_cast(this)->GetVertexInfoMaybeDestroy(vertexE, exists, vertexType); 285 | return exists; 286 | } 287 | inline bool VertexExists(VertexID vertexE) const { 288 | VertexType vertexType; 289 | return VertexExistsEx(vertexE, vertexType); 290 | } 291 | 292 | // 293 | // NODE CREATION 294 | // 295 | 296 | void CreateVertexEx(VertexID vertexE, VertexType vertexType) { 297 | assert(!VertexExists(vertexE)); 298 | if (vertexType == vertexTypeOne) { 299 | m_buffer[TristateIndexForExistence(vertexE)] = existsAsTypeOne; 300 | } else { 301 | m_buffer[TristateIndexForExistence(vertexE)] = existsAsTypeTwo; 302 | } 303 | } 304 | inline void CreateVertex(VertexID vertexE) { 305 | return CreateVertexEx(vertexE, vertexTypeOne); 306 | } 307 | void SetVertexType(VertexID vertexE, VertexType vertexType) { 308 | assert(VertexExists(vertexE)); 309 | if (vertexType == vertexTypeOne) { 310 | m_buffer[TristateIndexForExistence(vertexE)] = existsAsTypeOne; 311 | } else { 312 | m_buffer[TristateIndexForExistence(vertexE)] = existsAsTypeTwo; 313 | } 314 | } 315 | VertexType GetVertexType(VertexID vertexE) { 316 | bool exists; 317 | VertexType vertexType; 318 | GetVertexInfo(vertexE, exists, vertexType); 319 | assert(exists); 320 | return vertexType; 321 | } 322 | void FlipVertexType(VertexID vertexE) { 323 | bool exists; 324 | VertexType vertexType; 325 | GetVertexInfo(vertexE, exists, vertexType); 326 | assert(exists); 327 | if (vertexType == vertexTypeOne) 328 | SetVertexType(vertexE, vertexTypeTwo); 329 | else 330 | SetVertexType(vertexE, vertexTypeOne); 331 | } 332 | 333 | // 334 | // NODE DESTRUCTION ROUTINES 335 | // 336 | 337 | inline void DestroyVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL ) { 338 | bool exists; 339 | GetVertexInfoMaybeDestroy(vertex, exists, vertexType, incomingEdgeCount, outgoingEdgeCount, NULL, NULL, true /* destroyIfExists */, compactIfDestroy); 340 | assert(exists); // it doesn't exist any more! 341 | } 342 | inline void DestroyVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) { 343 | VertexType vertexType; 344 | return DestroyVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount); 345 | } 346 | inline void DestroyVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) { 347 | VertexType vertexType; 348 | return DestroyVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount); 349 | } 350 | // presupposition: no incoming edges, only points to others (if any) 351 | void DestroySourceVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* outgoingEdgeCount = NULL) { 352 | unsigned incomingEdgeCount; 353 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, outgoingEdgeCount); 354 | assert(incomingEdgeCount == 0); 355 | } 356 | inline void DestroySourceVertex(VertexID vertex, unsigned* outgoingEdgeCount = NULL) { 357 | VertexType vertexType; 358 | return DestroySourceVertexEx(vertex, vertexType, true /* compactIfDestroy */, outgoingEdgeCount); 359 | } 360 | inline void DestroySourceVertexDontCompact(VertexID vertex, unsigned* outgoingEdgeCount = NULL) { 361 | VertexType vertexType; 362 | return DestroySourceVertexEx(vertex, vertexType, false /* compactIfDestroy */, outgoingEdgeCount); 363 | } 364 | // presupposition: no outgoing edges, only incoming ones (if any) 365 | void DestroySinkVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL) { 366 | unsigned outgoingEdgeCount; 367 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, incomingEdgeCount, &outgoingEdgeCount); 368 | assert(outgoingEdgeCount == 0); 369 | } 370 | inline void DestroySinkVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL) { 371 | VertexType vertexType; 372 | return DestroySinkVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount); 373 | } 374 | inline void DestroySinkVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL) { 375 | VertexType vertexType; 376 | return DestroySinkVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount); 377 | } 378 | // presupposition: no incoming or outgoing edges 379 | void DestroyIsolatedVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true) { 380 | unsigned incomingEdgeCount; 381 | unsigned outgoingEdgeCount; 382 | DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, &outgoingEdgeCount); 383 | assert(incomingEdgeCount == 0); 384 | assert(outgoingEdgeCount == 0); 385 | } 386 | inline void DestroyIsolatedVertex(VertexID vertex) { 387 | VertexType vertexType; 388 | DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ ); 389 | } 390 | inline void DestroyIsolatedVertexDontCompact(VertexID vertex) { 391 | VertexType vertexType; 392 | DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ ); 393 | } 394 | 395 | // 396 | // ITERATION ROUTINES 397 | // 398 | 399 | std::set OutgoingEdgesForVertex(VertexID vertex) const { 400 | bool exists; 401 | VertexType vertexType; 402 | std::set outgoingEdges; 403 | const_cast(this)->GetVertexInfoMaybeDestroy(vertex, exists, vertexType, NULL, NULL, NULL, &outgoingEdges); 404 | assert(exists); 405 | return outgoingEdges; 406 | } 407 | 408 | std::set IncomingEdgesForVertex(VertexID vertex) const { 409 | bool exists; 410 | VertexType vertexType; 411 | std::set incomingEdges; 412 | const_cast(this)->GetVertexInfoMaybeDestroy(vertex, exists, vertexType, NULL, NULL, &incomingEdges, NULL); 413 | assert(exists); 414 | return incomingEdges; 415 | } 416 | 417 | public: 418 | bool HasLinkage(VertexID fromVertex, VertexID toVertex, bool* forwardEdge = NULL, bool* reverseEdge = NULL) const { 419 | assert(fromVertex != toVertex); 420 | assert(VertexExists(fromVertex)); 421 | assert(VertexExists(toVertex)); 422 | 423 | VertexID vertexL = fromVertex > toVertex ? fromVertex : toVertex; 424 | VertexID vertexS = fromVertex > toVertex ? toVertex : fromVertex; 425 | 426 | VertexConnectionTristate nct = static_cast( 427 | static_cast(m_buffer[TristateIndexForConnection(vertexS, vertexL)])); 428 | 429 | if (nct != notConnected) { 430 | if (toVertex > fromVertex) { 431 | if (forwardEdge) 432 | *forwardEdge = (nct == lowPointsToHigh); 433 | if (reverseEdge) 434 | *reverseEdge = (nct == highPointsToLow); 435 | } else { 436 | if (forwardEdge) 437 | *forwardEdge = (nct == highPointsToLow); 438 | if (reverseEdge) 439 | *reverseEdge = (nct == lowPointsToHigh); 440 | } 441 | return true; 442 | } 443 | 444 | if (forwardEdge) 445 | *forwardEdge = false; 446 | if (reverseEdge) 447 | *reverseEdge = false; 448 | return false; 449 | } 450 | bool EdgeExists(VertexID fromVertex, VertexID toVertex) const { 451 | bool forwardEdge; 452 | if (HasLinkage(fromVertex, toVertex, &forwardEdge)) 453 | return forwardEdge; 454 | return false; 455 | } 456 | 457 | public: 458 | bool SetEdge(VertexID fromVertex, VertexID toVertex) { 459 | assert(fromVertex != toVertex); 460 | assert(VertexExists(fromVertex)); 461 | assert(VertexExists(toVertex)); 462 | 463 | VertexID vertexL = fromVertex > toVertex ? fromVertex : toVertex; 464 | VertexID vertexS = fromVertex > toVertex ? toVertex : fromVertex; 465 | 466 | size_t tifc = TristateIndexForConnection(vertexS, vertexL); 467 | if (toVertex > fromVertex) { 468 | switch (m_buffer[tifc]) { 469 | case lowPointsToHigh: 470 | return false; 471 | 472 | case notConnected: 473 | m_buffer[tifc] = lowPointsToHigh; 474 | return true; 475 | 476 | case highPointsToLow: 477 | assert(false); 478 | return false; 479 | 480 | default: 481 | assert(false); 482 | return false; 483 | } 484 | } else { 485 | switch (m_buffer[tifc]) { 486 | case highPointsToLow: 487 | return false; 488 | 489 | case notConnected: 490 | m_buffer[tifc] = highPointsToLow; 491 | return true; 492 | 493 | case lowPointsToHigh: 494 | assert(false); 495 | return false; 496 | 497 | default: 498 | assert(false); 499 | return false; 500 | } 501 | } 502 | } 503 | void AddEdge(VertexID fromVertex, VertexID toVertex) { 504 | if (!SetEdge(fromVertex, toVertex)) 505 | assert(false); 506 | } 507 | bool ClearEdge(VertexID fromVertex, VertexID toVertex) { 508 | assert(fromVertex != toVertex); 509 | 510 | bool fromExists; 511 | VertexType fromVertexType; 512 | GetVertexInfo(fromVertex, fromExists, fromVertexType); 513 | assert(fromExists); 514 | bool toExists; 515 | VertexType toVertexType; 516 | GetVertexInfo(toVertex, toExists, toVertexType); 517 | assert(toExists); 518 | 519 | VertexID vertexL = fromVertex > toVertex ? fromVertex : toVertex; 520 | VertexID vertexS = fromVertex > toVertex ? toVertex : fromVertex; 521 | 522 | size_t tifc = TristateIndexForConnection(vertexS, vertexL); 523 | if (toVertex > fromVertex) { 524 | switch (m_buffer[tifc]) { 525 | case lowPointsToHigh: 526 | m_buffer[tifc] = notConnected; 527 | return true; 528 | 529 | case notConnected: 530 | case highPointsToLow: 531 | return false; 532 | 533 | default: 534 | assert(false); 535 | return false; 536 | } 537 | } else { 538 | switch (m_buffer[tifc]) { 539 | case highPointsToLow: 540 | m_buffer[tifc] = notConnected; 541 | return true; 542 | 543 | case notConnected: 544 | case lowPointsToHigh: 545 | return false; 546 | 547 | default: 548 | assert(false); 549 | return false; 550 | } 551 | } 552 | } 553 | void RemoveEdge(VertexID fromVertex, VertexID toVertex) { 554 | if (!ClearEdge(fromVertex, toVertex)) 555 | assert(false); 556 | } 557 | 558 | // Construction and destruction 559 | public: 560 | OrientedGraph(const size_t initial_size) : 561 | m_buffer (0) // fills with zeros 562 | { 563 | SetCapacitySoVertexIsFirstInvalidID(initial_size); 564 | } 565 | 566 | virtual ~OrientedGraph() { 567 | } 568 | 569 | #if ORIENTEDGRAPH_SELFTEST 570 | public: 571 | static bool SelfTest(); 572 | #endif 573 | }; 574 | 575 | } // end namespace nocycle 576 | -------------------------------------------------------------------------------- /PerformanceTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // PerformanceTest.cpp - Simple random performance test which can be 3 | // run against the DirectedAcyclicGraph or its boost wrapper 4 | // equivalent. Uses the boost POSIX time library because 5 | // can't add time intervals. 6 | // 7 | // Copyright (c) 2009 HostileFork.com 8 | // 9 | // Distributed under the Boost Software License, Version 1.0. (See 10 | // accompanying file LICENSE_1_0.txt or copy at 11 | // http://www.boost.org/LICENSE_1_0.txt) 12 | // 13 | // See http://hostilefork.com/nocycle for documentation. 14 | // 15 | 16 | // I use boost::posix_time for timing functions, if you don't want to use 17 | // boost then this has to be turned off 18 | #define RECORD_TIME_DURATIONS 0 19 | 20 | // If you want to use the boost adjacency_matrix implementation instead of 21 | // Nocycle's OrientedGraph-based implementation, set to 1. Probably won't 22 | // work for > 12K nodes 23 | #define USE_BOOST_GRAPH_IMPLEMENTATION 0 24 | 25 | // Some regression tests require boost to be included in your library path 26 | // so make sure you've turned off the self tests (NSTATE_SELFTEST, 27 | // ORIENTEDGRAPH_SELFTEST, DIRECTEDACYCLICGRAPH_SELFTEST) if you don't want 28 | // to build with boost 29 | #define REGRESSION_TESTS 0 30 | 31 | const unsigned NUM_TEST_NODES = /* 65536 + 1024 */ 1024; 32 | const unsigned NUM_TEST_ITERATIONS = NUM_TEST_NODES*2; 33 | const float REMOVE_PROBABILITY = 1.0/8.0; 34 | 35 | #include 36 | 37 | #include "Nstate.hpp" 38 | //#include "nstate/Nstate.hpp" 39 | #include "OrientedGraph.hpp" 40 | #include "DirectedAcyclicGraph.hpp" 41 | 42 | #include "RandomEdgePicker.hpp" 43 | 44 | #include 45 | #include 46 | 47 | #if RECORD_TIME_DURATIONS 48 | #include "boost/date_time/posix_time/posix_time.hpp" 49 | #endif 50 | 51 | #if USE_BOOST_GRAPH_IMPLEMENTATION 52 | #include "BoostImplementation.hpp" 53 | typedef nocycle::RandomEdgePicker DAGType; 54 | #else 55 | typedef nocycle::RandomEdgePicker DAGType; 56 | #endif 57 | 58 | int main (int argc, char * const argv[]) { 59 | 60 | #if RECORD_TIME_DURATIONS 61 | boost::posix_time::time_duration addTime = boost::posix_time::seconds(0); 62 | boost::posix_time::time_duration removeTime = boost::posix_time::seconds(0); 63 | #endif 64 | 65 | #if REGRESSION_TESTS && NSTATE_SELFTEST 66 | if (nocycle::Nstate<3>::SelfTest()) { 67 | std::cout << "SUCCESS: All Nstate SelfTest() passed regression." << std::endl; 68 | } else { 69 | return 1; 70 | } 71 | 72 | if (nocycle::NstateArray<3>::SelfTest()) { 73 | std::cout << "SUCCESS: All NstateArray SelfTest() passed regression." << std::endl; 74 | } else { 75 | return 1; 76 | } 77 | #endif 78 | 79 | #if REGRESSION_TESTS && ORIENTEDGRAPH_SELFTEST 80 | if (nocycle::OrientedGraph::SelfTest()) { 81 | std::cout << "SUCCESS: All OrientedGraph SelfTest() passed regression." << std::endl; 82 | } else { 83 | return 1; 84 | } 85 | #endif 86 | 87 | #if REGRESSION_TESTS && DIRECTEDACYCLICGRAPH_SELFTEST 88 | if (nocycle::DirectedAcyclicGraph::SelfTest()) { 89 | std::cout << "SUCCESS: All DirectedAcyclicGraph SelfTest() passed regression." << std::endl; 90 | } else { 91 | return 1; 92 | } 93 | #endif 94 | 95 | unsigned numCyclesCaught = 0; 96 | unsigned numInsertions = 0; 97 | unsigned numDeletions = 0; 98 | 99 | DAGType dag (NUM_TEST_NODES); 100 | 101 | for (DAGType::VertexID vertex = 0; vertex < NUM_TEST_NODES; vertex++) { 102 | dag.CreateVertex(vertex); 103 | } 104 | 105 | // keep adding random vertices and make sure that if it causes a cycle exception on the boost 106 | // implemented class, that it also causes a cycle exception on the non-boost class 107 | for (unsigned index = 0; index < NUM_TEST_ITERATIONS; index++) { 108 | DAGType::VertexID vertexSource; 109 | DAGType::VertexID vertexDest; 110 | 111 | bool removeEdge = (dag.NumEdges() > 0) && ((rand() % 10000) < (REMOVE_PROBABILITY * 10000)); 112 | 113 | if (removeEdge) { 114 | 115 | // We don't count the time for picking the vertex in the performance evaluation 116 | dag.GetRandomEdge(vertexSource, vertexDest); 117 | std::cout << "dag.RemoveEdge(" << vertexSource << ", " << vertexDest << ")"; 118 | 119 | // We want to be twice as likely to pick a vertex with 2 outgoing connections as 1, 120 | // and three times as likely to pick a vertex with 3 outgoing connections... etc. 121 | // Vertices with 0 outgoing connections will not be picked! 122 | #if RECORD_TIME_DURATIONS 123 | boost::posix_time::ptime timeStart = boost::posix_time::microsec_clock::local_time(); 124 | #endif 125 | 126 | dag.RemoveEdge(vertexSource, vertexDest); 127 | 128 | #if RECORD_TIME_DURATIONS 129 | boost::posix_time::time_duration timeDuration = (boost::posix_time::microsec_clock::local_time() - timeStart); 130 | removeTime += timeDuration; 131 | std::cout << " : " << timeDuration; 132 | #endif 133 | 134 | std::cout << std::endl; 135 | numDeletions++; 136 | 137 | } else { 138 | dag.GetRandomNonEdge(vertexSource, vertexDest); 139 | std::cout << "dag.AddEdge(" << vertexSource << ", " << vertexDest << ")"; 140 | 141 | #if RECORD_TIME_DURATIONS 142 | boost::posix_time::ptime timeStart = boost::posix_time::microsec_clock::local_time(); 143 | #endif 144 | 145 | bool causedCycle = false; 146 | try { 147 | dag.AddEdge(vertexSource, vertexDest); 148 | } catch (nocycle::bad_cycle& e) { 149 | causedCycle = true; 150 | } 151 | 152 | #if RECORD_TIME_DURATIONS 153 | boost::posix_time::time_duration timeDuration = (boost::posix_time::microsec_clock::local_time() - timeStart); 154 | addTime += timeDuration; 155 | std::cout << " : " << timeDuration; 156 | #endif 157 | 158 | if (causedCycle) { 159 | std::cout << " ==> !!!CYCLE!!! "; 160 | numCyclesCaught++; 161 | } else { 162 | numInsertions++; 163 | } 164 | 165 | std::cout << std::endl; 166 | } 167 | } 168 | std::cout << "NOTE: Inserted " << numInsertions << ", Deleted " << numDeletions << ", and Caught " << numCyclesCaught << " cycles." << std::endl; 169 | 170 | #if RECORD_TIME_DURATIONS 171 | std::cout << "NOTE: Total AddEdge time = " << addTime << std::endl; 172 | std::cout << "NOTE: Total RemoveEdge time = " << removeTime << std::endl; 173 | #endif 174 | 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### NoCycle 2 | 3 | See http://hostilefork.com/nocycle for information about this project. 4 | 5 | Note: This code was originally done as a thought-experiment in 2009. At the 6 | time I didn't know much about the state of C++11 (which was then known as 7 | "C++0x"). There weren't a lot of good internet resources for working with 8 | the spec that had been evolving, so I just did it in plain C++98. I was not 9 | very knowledgable about templates at the time, and using Boost.Graph for the 10 | first time was a bit of a challenge. 11 | 12 | In the interest of making it a bit more presentable a decade later, I'm doing 13 | some idle cleanup. 14 | -------------------------------------------------------------------------------- /RandomEdgePicker.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // RandomEdgePicker.hpp - Template class that can be applied to an 3 | // OrientedGraph, DirectedGraph, or boost wrapper in order to get 4 | // better performance at picking a random edge in a graph 5 | // implemented with an adjacency matrix than picking pairs 6 | // of nodes at random and hoping they will be connected. 7 | // 8 | // Copyright (c) 2009 HostileFork.com 9 | // 10 | // Distributed under the Boost Software License, Version 1.0. (See 11 | // accompanying file LICENSE_1_0.txt or copy at 12 | // http://www.boost.org/LICENSE_1_0.txt) 13 | // 14 | // See http://hostilefork.com/nocycle for documentation. 15 | // 16 | 17 | #pragma once 18 | 19 | #include "NocycleConfig.hpp" 20 | 21 | #include // needed for rand() 22 | 23 | namespace nocycle { 24 | 25 | // I wanted to be able to quickly remove a random edge from the graph. Unfortunately, in 26 | // a dense matrix representation in which there can be many missing edges... the edges can 27 | // be difficult to impose an enumeration on that can be chosen from a random index. 28 | // 29 | // This adds some tracking on top of the graph which keeps track of all the vertex IDs 30 | // and how many outgoing connections they have. Armed with this information, you can 31 | // pick a link at random! 32 | // 33 | template 34 | class RandomEdgePicker : public Base { 35 | public: 36 | typedef typename Base::VertexID VertexID; 37 | 38 | private: 39 | std::map > m_verticesByOutgoingEdgeCount; 40 | unsigned m_numEdges; 41 | 42 | public: 43 | // NOTE: There are variations of these functions, need to mirror them all! 44 | void CreateVertex(VertexID vertex) { 45 | m_verticesByOutgoingEdgeCount[0].insert(vertex); 46 | Base::CreateVertex(vertex); 47 | } 48 | void DestroyVertex(VertexID vertex) { 49 | assert(m_verticesByOutgoingEdgeCount[0].find(vertex) != m_verticesByOutgoingEdgeCount[0].end()); 50 | m_verticesByOutgoingEdgeCount[0].erase(vertex); 51 | Base::DestroyVertex(vertex); 52 | } 53 | bool SetEdge(VertexID fromVertex, VertexID toVertex) { 54 | if (Base::SetEdge(fromVertex, toVertex)) { 55 | m_numEdges++; 56 | unsigned numOutgoing = Base::OutgoingEdgesForVertex(fromVertex).size(); 57 | m_verticesByOutgoingEdgeCount[numOutgoing-1].erase(fromVertex); 58 | m_verticesByOutgoingEdgeCount[numOutgoing].insert(fromVertex); 59 | return true; 60 | } 61 | return false; 62 | } 63 | void AddEdge(VertexID fromVertex, VertexID toVertex) { 64 | if (!RandomEdgePicker::SetEdge(fromVertex, toVertex)) 65 | assert(false); 66 | } 67 | bool ClearEdge(VertexID fromVertex, VertexID toVertex) { 68 | if (Base::ClearEdge(fromVertex, toVertex)) { 69 | assert(m_numEdges > 0); 70 | m_numEdges--; 71 | unsigned numOutgoing = Base::OutgoingEdgesForVertex(fromVertex).size(); 72 | m_verticesByOutgoingEdgeCount[numOutgoing+1].erase(fromVertex); 73 | m_verticesByOutgoingEdgeCount[numOutgoing].insert(fromVertex); 74 | return true; 75 | } 76 | return false; 77 | } 78 | void RemoveEdge(VertexID fromVertex, VertexID toVertex) { 79 | if (!RandomEdgePicker::ClearEdge(fromVertex, toVertex)) 80 | assert(false); 81 | } 82 | 83 | public: 84 | size_t NumEdges() const { 85 | return m_numEdges; 86 | } 87 | 88 | // The algorithm for picking a random edge is to start with a random number that ranges 89 | // from [0..numEdges-1], and then weight the probability of picking a link from a node 90 | // based on its outgoing number of connections. 91 | // 92 | // Imagine you have 100 nodes, and 100 edges between them: 93 | // 40 nodes with 0 outgoing edges 94 | // 30 nodes with 1 outgoing edge 95 | // 20 nodes with 2 outgoing edges 96 | // 10 nodes with 3 outgoing edges 97 | // 98 | // You pick a random number between [0..99]. You want to be three times as likely to 99 | // pick a node with 3 outgoing edges as you are to pick a node with 1 outgoing edges, 100 | // and twice as likely to pick a node with 2 outgoing edges as 1 outgoing. So let's 101 | // say your random pick was 90. 102 | // 103 | // * Skip all the nodes with 0 outgoing edges, since they have no links to take 104 | // * 90 > 30*1, so skip past the nodes with one outgoing edge and subtract 30*1 (60) 105 | // * 60 > 20*2, so skip past the nodes with two outgoing edges and subtract 20*2 (20) 106 | // * 20 < 10*3... so we div 20 by 3 to realize we need to pick the 6th node 107 | // with 3 outgoing edges... and mod 20 by 3 to realize we need to pick the 3rd 108 | // edge of that sixth node (index 2) 109 | // 110 | // Really, it makes sense. :) Leaving it a little messy for the moment since it's 111 | // just used for testing. 112 | void GetRandomEdge(VertexID& fromVertex, VertexID& toVertex) const { 113 | assert(m_numEdges > 0); 114 | if (0) { // test code, useful if you suspect the sets aren't accurate 115 | unsigned numEdgesCheck = 0; 116 | typename std::map >::const_iterator verticesByOutgoingEdgeCountCheckIter = 117 | m_verticesByOutgoingEdgeCount.begin(); 118 | 119 | while (verticesByOutgoingEdgeCountCheckIter != m_verticesByOutgoingEdgeCount.end()) { 120 | numEdgesCheck += (*verticesByOutgoingEdgeCountCheckIter).first * (*verticesByOutgoingEdgeCountCheckIter).second.size(); 121 | verticesByOutgoingEdgeCountCheckIter++; 122 | } 123 | assert(numEdgesCheck == m_numEdges); 124 | } 125 | 126 | unsigned edgeIndexToRemove = static_cast(rand()) % m_numEdges; 127 | unsigned edgeIndex = edgeIndexToRemove; 128 | typename std::map >::const_iterator verticesByOutgoingEdgeCountIter = 129 | m_verticesByOutgoingEdgeCount.begin(); 130 | 131 | unsigned numOutgoing = (*verticesByOutgoingEdgeCountIter).first; 132 | unsigned numEdgesWithThisOutgoingCount = (*verticesByOutgoingEdgeCountIter).second.size(); 133 | 134 | while (edgeIndex >= numOutgoing * numEdgesWithThisOutgoingCount) { 135 | // the edge "index" we are looking for wouldn't be in the current set 136 | edgeIndex -= numOutgoing * numEdgesWithThisOutgoingCount; 137 | verticesByOutgoingEdgeCountIter++; 138 | numOutgoing = (*verticesByOutgoingEdgeCountIter).first; 139 | numEdgesWithThisOutgoingCount = (*verticesByOutgoingEdgeCountIter).second.size(); 140 | assert(verticesByOutgoingEdgeCountIter != m_verticesByOutgoingEdgeCount.end()); 141 | } 142 | 143 | typename std::set::const_iterator setOfVerticesIter = (*verticesByOutgoingEdgeCountIter).second.begin(); 144 | while (edgeIndex >= numOutgoing) { 145 | edgeIndex -= numOutgoing; 146 | setOfVerticesIter++; 147 | assert(setOfVerticesIter != (*verticesByOutgoingEdgeCountIter).second.end()); 148 | } 149 | 150 | fromVertex = *setOfVerticesIter; 151 | 152 | // Now we pick the edge from the outgoing set based on what's left of our index 153 | std::set outgoing = Base::OutgoingEdgesForVertex(fromVertex); 154 | assert(outgoing.size() == numOutgoing); 155 | typename std::set::const_iterator outgoingIter = outgoing.begin(); 156 | while (edgeIndex > 0) { 157 | edgeIndex--; 158 | outgoingIter++; 159 | assert(outgoingIter != outgoing.end()); 160 | } 161 | 162 | toVertex = *outgoingIter; 163 | 164 | assert(verticesByOutgoingEdgeCountIter != m_verticesByOutgoingEdgeCount.end()); 165 | assert(numOutgoing > 0); 166 | } 167 | void GetRandomNonEdge(VertexID& fromVertex, VertexID& toVertex) const { 168 | VertexID maxID = Base::GetFirstInvalidVertexID(); 169 | 170 | // For the moment, we assume the graph is not so dense that this will 171 | // take an inordinate amount of time, but we could in theory use a 172 | // reverse of the above approach. 173 | // 174 | // !!! Should C++ std::uniform_int_distribution() be used instead? 175 | do { 176 | do { 177 | fromVertex = static_cast(rand()) % maxID; 178 | } while (!Base::VertexExists(fromVertex)); 179 | do { 180 | toVertex = static_cast(rand()) % maxID; 181 | } while ((fromVertex == toVertex) || (!Base::VertexExists(toVertex))); 182 | } while (Base::HasLinkage(fromVertex, toVertex)); 183 | } 184 | 185 | public: 186 | RandomEdgePicker (const size_t initial_size) : 187 | Base (initial_size), 188 | m_numEdges (0) 189 | { 190 | } 191 | virtual ~RandomEdgePicker() { 192 | } 193 | }; 194 | 195 | } // end namespace nocycle 196 | -------------------------------------------------------------------------------- /nocycle-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hostilefork/nocycle/71e48484ae6eef74ba92d72ed4f0d62bff5a7e4e/nocycle-logo.png -------------------------------------------------------------------------------- /research/tarjan-algorithm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hostilefork/nocycle/71e48484ae6eef74ba92d72ed4f0d62bff5a7e4e/research/tarjan-algorithm.pdf --------------------------------------------------------------------------------