-
Notifications
You must be signed in to change notification settings - Fork 4
/
DirectedAcyclicGraph.hpp
809 lines (703 loc) · 35.7 KB
/
DirectedAcyclicGraph.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
//
// DirectedAcyclicGraph.hpp - Experimental DAG graph class with a
// sidestructure of its transitive closure. Implemented as two
// OrientedGraph objects, and various enhancements to the
// sidestructure can be tried out.
//
// Copyright (c) 2009 HostileFork.com
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://hostilefork.com/nocycle for documentation.
//
#pragma once
#include "NocycleConfig.hpp"
#include "OrientedGraph.hpp"
#include <set>
#include <stack>
namespace nocycle {
//
// EXCEPTIONS
// See: http://www.cplusplus.com/doc/tutorial/exceptions.html
//
class bad_cycle : public std::exception {
virtual const char* what() const throw() {
return "Attempt to insert a cycle into a DirectedAcyclicGraph";
}
};
// Each class that uses another in its implementation should insulate its
// clients from that fact. A lazy man's composition pattern can be achieved with
// private inheritance: http://www.parashift.com/c++-faq-lite/private-inheritance.html
//
// Public inheritance lets users coerce pointers from the derived class to the
// base class. Without virtual methods, this is bad because it disregards the
// overriden methods. As Nocycle is refined I will make decisions about this, but I
// use public inheritance for expedience while the library is still in flux.
class DirectedAcyclicGraph : public OrientedGraph {
public:
typedef OrientedGraph::VertexID VertexID;
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
// Sidestructure for fast O(1) acyclic insertion
//
// IF no edge A->B or A<-B is in the main data, then m_canreach is the
// transitive closure relationship. e.g.:
//
// * no edge between A and B indicates any edge is permitted. e.g.,
// (B cantreach A) and (A cantreach B)
//
// * A->B indicates that (A canreach B) and (B cantreach A)
// hence edges from A->B are ok, but B->A would create a cycle
//
// * A<-B indicates that (A cantreach B) and (B canreach A)
// hence edges from B->A are ok, but A->B would create a cycle
//
// IF there is a edge in the main data, then the canreach data for
// that edge is somewhat redundant, and it is possible to derive
// an independent tristate for each such connection. It may be
// possible to use that information to enhance the behavior of
// the transitive closure sidestructure, so there's some experiments
// to that effect...such as caching whether the pointed to vertex
// would be reachable if the physical edge were removed.
private:
OrientedGraph m_canreach;
#endif
public:
DirectedAcyclicGraph(const size_t initial_size) :
OrientedGraph(initial_size)
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
, m_canreach (initial_size)
#endif
{
}
virtual ~DirectedAcyclicGraph() {
}
// Special features of our DAG's sidestructure
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
// If a physical connection exists between fromVertex and toVertex, we can use its
// reachability data for other purposes...effectively, a tristate!
// public for testing, but will probably go private
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
private:
enum ExtraTristate {
isReachableWithoutEdge = 0,
notReachableWithoutEdge = 1,
thirdStateNotSureWhatToDoWithIt = 2
};
#endif
#if DIRECTEDACYCLICGRAPH_USER_TRISTATE
public:
#else
private:
#endif
Nstate<3> GetTristateForConnection(VertexID fromVertex, VertexID toVertex) const {
assert(EdgeExists(fromVertex, toVertex));
bool forwardEdge, reverseEdge;
if (m_canreach.HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge)) {
if (forwardEdge)
return 1;
if (reverseEdge)
return 2;
assert(false);
}
return 0;
}
void SetTristateForConnection(VertexID fromVertex, VertexID toVertex, Nstate<3> tristate) {
assert(EdgeExists(fromVertex, toVertex));
bool forwardEdge, reverseEdge;
m_canreach.HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge);
switch (tristate) {
case 0:
if (forwardEdge)
m_canreach.RemoveEdge(fromVertex, toVertex);
if (reverseEdge)
m_canreach.RemoveEdge(toVertex, fromVertex);
break;
case 1:
if (reverseEdge)
m_canreach.RemoveEdge(toVertex, fromVertex);
m_canreach.SetEdge(fromVertex, toVertex);
break;
case 2:
if (forwardEdge)
m_canreach.RemoveEdge(fromVertex, toVertex);
m_canreach.SetEdge(toVertex, fromVertex);
break;
default:
assert(false);
}
}
private:
// For each vertex in the canreach data, we say its reachability is either "clean"
// or "dirty". Current strategy is that dirty reachability has false positives but
// no false negatives.
static const auto canreachClean = vertexTypeOne;
static const auto canreachMayHaveFalsePositives = vertexTypeTwo;
bool ClearReachEdge(VertexID fromVertex, VertexID toVertex) {
// don't want to damage a tristate!
assert(!HasLinkage(fromVertex, toVertex));
return m_canreach.ClearEdge(fromVertex, toVertex);
}
void RemoveReachEdge(VertexID fromVertex, VertexID toVertex) {
if (!ClearReachEdge(fromVertex, toVertex))
assert (false);
}
bool SetReachEdge(VertexID fromVertex, VertexID toVertex) {
// don't want to damage a tristate!
assert(!HasLinkage(fromVertex, toVertex));
return m_canreach.SetEdge(fromVertex, toVertex);
}
void AddReachEdge(VertexID fromVertex, VertexID toVertex) {
if (!SetReachEdge(fromVertex, toVertex))
assert (false);
}
// The "incoming reach" is all incoming edges, and for all non-incoming edges that
// are also not outgoing edges... the data contained in the canreach graph.
// (Note: includes vertex, as being able to "reach" itself)
std::set<VertexID> IncomingReachForVertexIncludingSelf(VertexID vertex) {
std::set<VertexID> incoming = IncomingEdgesForVertex(vertex);
std::set<VertexID> incomingReach = m_canreach.IncomingEdgesForVertex(vertex);
std::set<VertexID>::iterator incomingReachIter = incomingReach.begin();
while (incomingReachIter != incomingReach.end()) {
VertexID incomingReachVertex = *incomingReachIter;
if (!HasLinkage(vertex, incomingReachVertex))
incoming.insert(incomingReachVertex);
incomingReachIter++;
}
incoming.insert(vertex);
return incoming;
}
// The "outgoing reach" is all outgoing edges, and for all non-outgoing edges that
// are also not incoming edges... the data contained in the canreach graph.
// (Note: includes vertex, as being able to "reach" itself)
std::set<VertexID> OutgoingReachForVertexIncludingSelf(VertexID vertex) {
std::set<VertexID> outgoing = OutgoingEdgesForVertex(vertex);
std::set<VertexID> outgoingReach = m_canreach.OutgoingEdgesForVertex(vertex);
std::set<VertexID>::iterator outgoingReachIter = outgoingReach.begin();
while (outgoingReachIter != outgoingReach.end()) {
VertexID outgoingReachVertex = *outgoingReachIter;
if (!HasLinkage(outgoingReachVertex, vertex))
outgoing.insert(outgoingReachVertex);
outgoingReachIter++;
}
outgoing.insert(vertex);
return outgoing;
}
// cleans as much reachability data as needed to answer if from can reach
// to. In theory, can leave the vertex dirty if answer is NO...
void CleanUpReachability(VertexID fromVertex, VertexID toVertex) {
// fromVertex's canreach data is dirty. that means that some of the outgoing
// edges may be false positives. Start by clearing all outgoing reachability
// edges...but remember which ones since those are the only ones that need
// fixing
std::set<VertexID> outgoingReachAndNoise = m_canreach.OutgoingEdgesForVertex(fromVertex);
std::set<VertexID>::iterator outgoingReachAndNoiseIter = outgoingReachAndNoise.begin();
std::set<VertexID> outgoingReachBeforeClean;
while (outgoingReachAndNoiseIter != outgoingReachAndNoise.end()) {
VertexID outgoingReachAndNoiseVertex = (*outgoingReachAndNoiseIter++);
if (HasLinkage(fromVertex, outgoingReachAndNoiseVertex)) {
// noise, ignore it
} else {
outgoingReachBeforeClean.insert(outgoingReachAndNoiseVertex);
// we don't really need to modify the edge here...
// in fact, it causes us to break the "false positives" invariant
// (hence why I'm saving the outgoingReachBeforeClean)
RemoveReachEdge(fromVertex, outgoingReachAndNoiseVertex);
}
}
// now go through all the vertices to which there is a physical connection
// if their canreach data is good, then use it
// otherwise, clean it and use it
// (there will be no loops because it's acyclic)
std::set<VertexID> outgoing = OutgoingEdgesForVertex(fromVertex);
std::set<VertexID>::iterator outgoingIter = outgoing.begin();
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
std::map<VertexID, std::set<VertexID> > mapOfOutgoingReachIncludingSelf;
#endif
while (outgoingIter != outgoing.end()) {
OrientedGraph::VertexID outgoingVertex = (*outgoingIter++);
if (m_canreach.GetVertexType(outgoingVertex) == canreachMayHaveFalsePositives)
CleanUpReachability(outgoingVertex, toVertex);
std::set<VertexID> outgoingForOutgoing = OutgoingReachForVertexIncludingSelf(outgoingVertex);
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
mapOfOutgoingReachIncludingSelf[outgoingVertex] = outgoingForOutgoing;
#endif
std::set<VertexID>::iterator outgoingForOutgoingIter = outgoingForOutgoing.begin();
while (outgoingForOutgoingIter != outgoingForOutgoing.end()) {
VertexID outgoingForOutgoingVertex = (*outgoingForOutgoingIter++);
if (outgoingVertex == outgoingForOutgoingVertex)
continue;
if (!EdgeExists(fromVertex, outgoingForOutgoingVertex)) {
if (m_canreach.EdgeExists(outgoingForOutgoingVertex, fromVertex)) {
VertexType vertexTypeOutgoingForOutgoing = m_canreach.GetVertexType(outgoingForOutgoingVertex);
assert(vertexTypeOutgoingForOutgoing == canreachMayHaveFalsePositives);
RemoveReachEdge(outgoingForOutgoingVertex, fromVertex);
}
SetReachEdge(fromVertex, outgoingForOutgoingVertex);
}
}
}
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
std::map<VertexID, std::set<VertexID> >::iterator mapOfOutgoingReachIncludingSelfIter = mapOfOutgoingReachIncludingSelf.begin();
while (mapOfOutgoingReachIncludingSelfIter != mapOfOutgoingReachIncludingSelf.end()) {
VertexID linkedVertex = (*mapOfOutgoingReachIncludingSelfIter).first;
if (GetTristateForConnection(fromVertex, linkedVertex) == isReachableWithoutEdge) {
bool foundOtherPath = false;
std::map<VertexID, std::set<VertexID> >::iterator mapOfOutgoingReachIncludingSelfOtherIter = mapOfOutgoingReachIncludingSelf.begin();
while (!foundOtherPath && (mapOfOutgoingReachIncludingSelfOtherIter != mapOfOutgoingReachIncludingSelf.end())) {
if (mapOfOutgoingReachIncludingSelfIter != mapOfOutgoingReachIncludingSelfOtherIter) {
std::set<VertexID> outgoingOtherReachIncludingOther = (*mapOfOutgoingReachIncludingSelfOtherIter).second;
if (outgoingOtherReachIncludingOther.find(linkedVertex) !=outgoingOtherReachIncludingOther.end())
foundOtherPath = true;
}
mapOfOutgoingReachIncludingSelfOtherIter++;
}
if (!foundOtherPath)
SetTristateForConnection(fromVertex, linkedVertex, notReachableWithoutEdge);
}
mapOfOutgoingReachIncludingSelfIter++;
}
#endif
m_canreach.SetVertexType(fromVertex, canreachClean);
}
#endif
public:
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
bool CanReach(VertexID fromVertex, VertexID toVertex) {
// If there is a physical edge, then we are using the canreach data for other purposes
bool forwardEdge, reverseEdge;
if (HasLinkage(fromVertex, toVertex, &forwardEdge, &reverseEdge)) {
// If a physical edge exists to the target vertex, we can reach it
if (forwardEdge)
return true;
// If a physical edge exists from the target to us, then if we could reach
// it then that would cause a cycle
if (reverseEdge)
return false;
assert(false);
return false;
}
// When there's no physical edge, the canreach data is the transitive closure, except
// for "dirtiness"
switch (m_canreach.GetVertexType(fromVertex)) {
case canreachClean:
return m_canreach.EdgeExists(fromVertex, toVertex);
case canreachMayHaveFalsePositives:
if (!m_canreach.EdgeExists(fromVertex, toVertex))
return false;
CleanUpReachability(fromVertex, toVertex);
return m_canreach.EdgeExists(fromVertex, toVertex);
default:
assert(false);
return false;
}
assert(false);
return false;
}
#else
bool CanReach(VertexID fromVertex, VertexID toVertex) {
// Simply do a depth-first search to determine reachability
// Using LIFO stack instead of recursion...
assert(fromVertex != toVertex);
std::set<VertexID> visitedVertices;
std::stack<VertexID> searchStack;
searchStack.push(fromVertex);
while (!searchStack.empty()) {
VertexID searchVertex = searchStack.top();
searchStack.pop();
std::set<VertexID> outgoing = OutgoingEdgesForVertex(searchVertex);
std::set<VertexID>::iterator outgoingIter = outgoing.begin();
while (outgoingIter != outgoing.end()) {
VertexID outgoingVertex = (*outgoingIter++);
if (outgoingVertex == toVertex)
return true;
if (visitedVertices.find(outgoingVertex) != visitedVertices.end())
continue;
visitedVertices.insert(outgoingVertex);
searchStack.push(outgoingVertex);
}
}
return false;
}
#endif
public:
// This expands the buffer vector so that it can accommodate the existence and
// connection data for vertexL. Any new vertices added will not exist yet and not
// have connection data. Any vertices existing above this ID # will
void SetCapacityForMaxValidVertexID(VertexID vertexL) {
OrientedGraph::SetCapacityForMaxValidVertexID(vertexL);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
m_canreach.SetCapacityForMaxValidVertexID(vertexL);
#endif
}
void SetCapacitySoVertexIsFirstInvalidID(VertexID vertexL) {
OrientedGraph::SetCapacitySoVertexIsFirstInvalidID(vertexL);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
m_canreach.SetCapacityForMaxValidVertexID(vertexL);
#endif
}
void GrowCapacityForMaxValidVertexID(VertexID vertexL) {
OrientedGraph::GrowCapacityForMaxValidVertexID(vertexL);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
m_canreach.SetCapacityForMaxValidVertexID(vertexL);
#endif
}
void ShrinkCapacitySoVertexIsFirstInvalidID(VertexID vertexL) {
OrientedGraph::ShrinkCapacitySoVertexIsFirstInvalidID(vertexL);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
m_canreach.ShrinkCapacitySoVertexIsFirstInvalidID(vertexL);
#endif
}
//
// CREATION OVERRIDES
//
public:
void CreateVertexEx(VertexID vertexE, VertexType vertexType) {
OrientedGraph::CreateVertexEx(vertexE, vertexType);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
m_canreach.CreateVertexEx(vertexE, canreachClean);
#endif
}
inline void CreateVertex(VertexID vertexE) {
return CreateVertexEx(vertexE, vertexTypeOne);
}
//
// DESTRUCTION OVERRIDES
//
public:
inline void DestroyVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL ) {
OrientedGraph::DestroyVertexEx(vertex, vertexType, compactIfDestroy, incomingEdgeCount, outgoingEdgeCount);
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
unsigned incomingEdgeCanreach;
unsigned outgoingEdgeCanreach;
VertexType vertexTypeCanreach;
m_canreach.DestroyVertexEx(vertex, vertexTypeCanreach, compactIfDestroy, &incomingEdgeCanreach, &outgoingEdgeCanreach);
#endif
}
inline void DestroyVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) {
VertexType vertexType;
return DestroyVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount);
}
inline void DestroyVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL, unsigned* outgoingEdgeCount = NULL) {
VertexType vertexType;
return DestroyVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount, outgoingEdgeCount);
}
// presupposition: no incoming edges, only points to others (if any)
void DestroySourceVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* outgoingEdgeCount = NULL) {
unsigned incomingEdgeCount;
DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, outgoingEdgeCount);
assert(incomingEdgeCount == 0);
}
inline void DestroySourceVertex(VertexID vertex, unsigned* outgoingEdgeCount = NULL) {
VertexType vertexType;
return DestroySourceVertexEx(vertex, vertexType, true /* compactIfDestroy */, outgoingEdgeCount);
}
inline void DestroySourceVertexDontCompact(VertexID vertex, unsigned* outgoingEdgeCount = NULL) {
VertexType vertexType;
return DestroySourceVertexEx(vertex, vertexType, false /* compactIfDestroy */, outgoingEdgeCount);
}
// presupposition: no outgoing edges, only incoming ones (if any)
void DestroySinkVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true, unsigned* incomingEdgeCount = NULL) {
unsigned outgoingEdgeCount;
DestroyVertexEx(vertex, vertexType, compactIfDestroy, incomingEdgeCount, &outgoingEdgeCount);
assert(outgoingEdgeCount == 0);
}
inline void DestroySinkVertex(VertexID vertex, unsigned* incomingEdgeCount = NULL) {
VertexType vertexType;
return DestroySinkVertexEx(vertex, vertexType, true /* compactIfDestroy */, incomingEdgeCount);
}
inline void DestroySinkVertexDontCompact(VertexID vertex, unsigned* incomingEdgeCount = NULL) {
VertexType vertexType;
return DestroySinkVertexEx(vertex, vertexType, false /* compactIfDestroy */, incomingEdgeCount);
}
// presupposition: no incoming or outgoing edges
void DestroyIsolatedVertexEx(VertexID vertex, VertexType& vertexType, bool compactIfDestroy = true) {
unsigned incomingEdgeCount;
unsigned outgoingEdgeCount;
DestroyVertexEx(vertex, vertexType, compactIfDestroy, &incomingEdgeCount, &outgoingEdgeCount);
assert(incomingEdgeCount == 0);
assert(outgoingEdgeCount == 0);
}
inline void DestroyIsolatedVertex(VertexID vertex) {
VertexType vertexType;
DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ );
}
inline void DestroyIsolatedVertexDontCompact(VertexID vertex) {
VertexType vertexType;
DestroyIsolatedVertexEx(vertex, vertexType, true /* compactIfDestroy */ );
}
public:
// SetEdge throws exceptions on cycle. To avoid having to write exception handling,
// use this routine before calling SetEdge.
inline bool InsertionWouldCauseCycle(VertexID fromVertex, VertexID toVertex) {
return CanReach(toVertex, fromVertex);
}
bool SetEdge(VertexID fromVertex, VertexID toVertex) {
#if DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK
ConsistencyCheck cc (*this);
#endif
if (InsertionWouldCauseCycle(fromVertex, toVertex)) {
bad_cycle bc;
throw bc;
}
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
// this may have false positives, for the moment let's union the "false positive tristate"
// with the rest of the "false positive" reachability data...
bool reachablePriorToEdge = m_canreach.EdgeExists(fromVertex, toVertex);
#endif
// set the physical edge in the data structure
bool edgeIsNew = OrientedGraph::SetEdge(fromVertex, toVertex);
if (!edgeIsNew)
return false;
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
// save whether the toVertex was reachable prior to the physical connection in the
// extra tristate for this edge
if (reachablePriorToEdge) {
SetTristateForConnection(fromVertex, toVertex, isReachableWithoutEdge);
} else {
SetTristateForConnection(fromVertex, toVertex, notReachableWithoutEdge);
}
#endif
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
// All the vertices that toVertex "canreach", including itself
// (Note: may contain false positives if vertexTypeTo == canreachMayHaveFalsePositives)
std::set<OrientedGraph::VertexID> toCanreach = OutgoingReachForVertexIncludingSelf(toVertex);
VertexType vertexTypeTo = m_canreach.GetVertexType(toVertex);
// All the vertices that "canreach" fromVertex, including itself
// (Note: may contain false positives if the incoming vertices are of type canreachMayHaveFalsePositives)
// (Note2: contains "lies"... if any of these vertices have physical edges, you'll be missing
std::set<OrientedGraph::VertexID> canreachFrom = IncomingReachForVertexIncludingSelf(fromVertex);
VertexType vertexTypeFrom = m_canreach.GetVertexType(fromVertex);
// Now update the reachability.
// All the vertices that canreach fromVertex (including fromVertex!) can now
// reach toVertex, as well as anything toVertex can reach...worst case O(N^2) "operations" but they
// are fast operations. Dirtiness needs to propagate, too.
std::set<OrientedGraph::VertexID>::iterator iterCanreachFrom = canreachFrom.begin();
while (iterCanreachFrom != canreachFrom.end()) {
VertexID canreachFromVertex = (*iterCanreachFrom++);
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
// These new reachabilities may lead us to need to bump a physical connection on
// vertices that can reach fromVertex regarding how it can reach a vertex that toVertex canreach.
// This would be from notReachableWithoutEdge to isReachableWithoutEdge. Basically,
// if some vertex to which you are getting a reachability relationship is able to reach
// any of the vertices to which you are physically edgeed to, then you must bump that edge
// if it was notReachableWithoutEdge
std::set<VertexID> outgoing = OutgoingEdgesForVertex(canreachFromVertex);
std::set<VertexID>::iterator outgoingIter = outgoing.begin();
while (outgoingIter != outgoing.end()) {
VertexID outgoingVertex = *outgoingIter++;
if ((outgoingVertex == toVertex) && (canreachFromVertex == fromVertex))
continue;
/* if (GetTristateForConnection(canreachFromVertex, outgoingVertex) == notReachableWithoutEdge) {*/
if (toCanreach.find(outgoingVertex) != toCanreach.end()) {
SetTristateForConnection(canreachFromVertex, outgoingVertex, isReachableWithoutEdge);
if (vertexTypeTo == canreachMayHaveFalsePositives)
SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives);
}
/* }*/
}
#endif
std::set<OrientedGraph::VertexID>::iterator iterToCanreach = toCanreach.begin();
while (iterToCanreach != toCanreach.end()) {
VertexID toCanreachVertex = (*iterToCanreach++);
assert(canreachFromVertex != toCanreachVertex);
bool forwardEdge, reverseEdge;
HasLinkage(canreachFromVertex, toCanreachVertex, &forwardEdge, &reverseEdge);
if (forwardEdge) {
// one case is that there's a physical edge and this is just a tristate
// that edge would have to be from canreachFromVertex to toCanreachVertex
// leave it alone
} else {
VertexType vertexTypeToCanreach = m_canreach.GetVertexType(toCanreachVertex);
if (vertexTypeToCanreach == canreachMayHaveFalsePositives) {
// there might be stale false positives which could have been
// left behind, and we need to tolerate those.
ClearReachEdge(toCanreachVertex, canreachFromVertex);
} else {
// if it were actually true that a vertex that toVertex canreach could reach
// a vertex that canreach fromVertex, then a connection from toVertex to fromVertex
// would be a cycle.
assert(!m_canreach.EdgeExists(toCanreachVertex, canreachFromVertex));
}
if (reverseEdge) {
assert(vertexTypeToCanreach == canreachMayHaveFalsePositives);
// we know for a fact here now that canreachFromVertex *can't* reach toCanreachVertex
// so ignore this and don't propagate it...
} else {
VertexType vertexTypeCanreachFrom = m_canreach.GetVertexType(canreachFromVertex);
if ((vertexTypeCanreachFrom == canreachClean) && (vertexTypeTo == canreachClean) && (vertexTypeFrom == canreachClean))
m_canreach.SetVertexType(canreachFromVertex, canreachClean);
else
m_canreach.SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives);
SetReachEdge(canreachFromVertex, toCanreachVertex);
}
}
}
}
#endif
return true;
}
void AddEdge(VertexID fromVertex, VertexID toVertex) {
if (!SetEdge(fromVertex, toVertex))
assert(false);
}
bool ClearEdge(VertexID fromVertex, VertexID toVertex) {
#if DIRECTEDACYCLICGRAPH_CONSISTENCY_CHECK
ConsistencyCheck cc (*this);
#endif
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
if (!EdgeExists(fromVertex, toVertex))
return false;
ExtraTristate extra = static_cast<ExtraTristate>(static_cast<unsigned char>(GetTristateForConnection(fromVertex, toVertex)));
SetTristateForConnection(fromVertex, toVertex, 0); // clear out tristate
OrientedGraph::RemoveEdge(fromVertex, toVertex);
// we have a short cut. if we are removing a edge, and our invalidation data does not have
// "false positives"... then if the vertex tristate said we were connected prior to the edge
// we still will be after remove it. We do not need to mark anything as having false
// positives.
VertexType vertexTypeFrom = m_canreach.GetVertexType(fromVertex);
if ((vertexTypeFrom == canreachClean) && (extra == isReachableWithoutEdge)) {
m_canreach.AddEdge(fromVertex, toVertex);
return true;
}
#else
if (!OrientedGraph::ClearEdge(fromVertex, toVertex))
return false;
#endif
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
// removing a edge calls into question all of our canreach vertices, and all the canreach vertices of vertices that
// canreach us... however, anything downstream of us is guaranteed to not have its reachability affected
// due to the lack of cyclic relationships. e.g. anything that we "canreach" is excluded from concern
// of recalculation of its canreach properties
// because the update can be somewhat costly, we merely dirty ourselves and all the vertices we canreach
// and let a background process take care of the cleaning. this does not affect readers, only writers,
// and insertions on disconnected regions of the graph will not affect each other
// All the vertices that canreach fromVertex...these have their reachability data coming into question
// (Note: we may be dirtying more than we need to due to "false positives" in the reachability)
std::set<OrientedGraph::VertexID> canreachFrom = IncomingReachForVertexIncludingSelf(fromVertex);
std::set<OrientedGraph::VertexID>::iterator canreachFromIter = canreachFrom.begin();
while (canreachFromIter != canreachFrom.end()) {
OrientedGraph::VertexID canreachFromVertex = (*canreachFromIter);
m_canreach.SetVertexType(canreachFromVertex, canreachMayHaveFalsePositives);
canreachFromIter++;
}
// if there was a tristate associated with this edge, we're losing it
// allow for false positives...there may have been a transitive edge!
if (m_canreach.EdgeExists(toVertex, fromVertex))
m_canreach.RemoveEdge(toVertex, fromVertex);
m_canreach.SetEdge(fromVertex, toVertex);
#endif
return true;
}
void RemoveEdge(VertexID fromVertex, VertexID toVertex) {
if (!ClearEdge(fromVertex, toVertex))
assert(false);
}
//
// DEBUGGING ROUTINES
//
#if DIRECTEDACYCLICGRAPH_CACHE_REACHABILITY
public:
std::set<VertexID> OutgoingTransitiveVertices(VertexID vertex, const VertexID* vertexIgnoreEdge, bool includeDirectEdges) {
std::set<VertexID> result;
std::stack<VertexID> vertexStack;
std::set<VertexID> outgoingInit = OutgoingEdgesForVertex(vertex);
std::set<VertexID>::iterator outgoingInitIter = outgoingInit.begin();
while (outgoingInitIter != outgoingInit.end()) {
VertexID outgoingVertex = (*outgoingInitIter++);
if (!vertexIgnoreEdge || (*vertexIgnoreEdge != outgoingVertex)) {
vertexStack.push(outgoingVertex);
if (includeDirectEdges)
result.insert(outgoingVertex);
}
}
while (!vertexStack.empty()) {
VertexID vertexCurrent = vertexStack.top();
vertexStack.pop();
std::set<VertexID> outgoing = OutgoingEdgesForVertex(vertexCurrent);
std::set<VertexID>::iterator outgoingIter = outgoing.begin();
while (outgoingIter != outgoing.end()) {
VertexID outgoingVertex = (*outgoingIter++);
if (result.find(outgoingVertex) == result.end()) {
vertexStack.push(outgoingVertex);
result.insert(outgoingVertex);
}
}
}
return result;
}
std::set<VertexID> OutgoingTransitiveVerticesNotDirectlyEdged(VertexID vertex) {
return OutgoingTransitiveVertices(vertex, NULL, false);
}
bool IsInternallyConsistent() {
for (OrientedGraph::VertexID vertex = 0; vertex < GetFirstInvalidVertexID(); vertex++) {
if (!VertexExists(vertex))
continue;
std::set<VertexID> outgoingReach = OutgoingReachForVertexIncludingSelf(vertex);
std::set<VertexID> outgoingTransitive = OutgoingTransitiveVerticesNotDirectlyEdged(vertex);
std::set<VertexID> outgoingTransitiveClosure = outgoingTransitive;
std::set<VertexID> outgoing = OutgoingEdgesForVertex(vertex);
outgoingTransitiveClosure.insert(outgoing.begin(), outgoing.end());
outgoingTransitiveClosure.insert(vertex);
switch (m_canreach.GetVertexType(vertex)) {
case canreachClean: {
size_t outgoingReachSize = outgoingReach.size();
size_t outgoingTransitiveClosureSize = outgoingTransitiveClosure.size();
std::set<VertexID>::iterator outgoingTransitiveClosureIter = outgoingTransitiveClosure.begin();
while (outgoingTransitiveClosureIter != outgoingTransitiveClosure.end()) {
VertexID outgoingTransitiveClosureVertex = (*outgoingTransitiveClosureIter++);
if (outgoingReach.find(outgoingTransitiveClosureVertex) == outgoingReach.end())
return false;
}
assert(outgoingReach.size() == outgoingTransitiveClosure.size());
#if DIRECTEDACYCLICGRAPH_CACHE_REACH_WITHOUT_LINK
std::set<VertexID>::iterator outgoingIter = outgoing.begin();
while (outgoingIter != outgoing.end()) {
VertexID outgoingVertex = (*outgoingIter++);
std::set<VertexID> outgoingTransitiveWithoutEdge = OutgoingTransitiveVertices(vertex, &outgoingVertex, false);
ExtraTristate extra = static_cast<ExtraTristate>(static_cast<unsigned char>(GetTristateForConnection(vertex, outgoingVertex)));
switch (extra) {
case isReachableWithoutEdge:
if (outgoingTransitiveWithoutEdge.find(outgoingVertex) == outgoingTransitiveWithoutEdge.end())
return false;
break;
case notReachableWithoutEdge:
if (outgoingTransitiveWithoutEdge.find(outgoingVertex) != outgoingTransitiveWithoutEdge.end())
return false;
break;
default:
assert(false);
}
}
#endif
break; }
case canreachMayHaveFalsePositives: {
std::set<VertexID>::iterator outgoingTransitiveClosureIter = outgoingTransitiveClosure.begin();
while (outgoingTransitiveClosureIter != outgoingTransitiveClosure.end()) {
VertexID outgoingTransitiveClosureVertex = (*outgoingTransitiveClosureIter++);
if (outgoingReach.find(outgoingTransitiveClosureVertex) == outgoingReach.end())
return false;
}
assert(outgoingReach.size() >= outgoingTransitiveClosure.size());
break; }
default:
assert(false);
}
}
return true;
}
class ConsistencyCheck {
private:
DirectedAcyclicGraph& m_dag;
public:
ConsistencyCheck(DirectedAcyclicGraph& dag) : m_dag (dag) { };
virtual ~ConsistencyCheck() {
assert(m_dag.IsInternallyConsistent());
}
};
#endif
public:
static bool SelfTest();
};
} // end namespace nocycle