1*9e3b08aeSAndroid Build Coastguard Worker# SCC finder core algorithm 2*9e3b08aeSAndroid Build Coastguard Worker 3*9e3b08aeSAndroid Build Coastguard WorkerWhen comparing types recursively, we explore a graph whose nodes are pairs of 4*9e3b08aeSAndroid Build Coastguard Workertypes. Nodes can be visited more than once, and there can even be cycles. 5*9e3b08aeSAndroid Build Coastguard Worker 6*9e3b08aeSAndroid Build Coastguard WorkerThe standard tool for exploring directed graphs is Depth First Search. Dealing 7*9e3b08aeSAndroid Build Coastguard Workerwith cycles in such a graph requires the determination of its Strongly-Connected 8*9e3b08aeSAndroid Build Coastguard WorkerComponents. 9*9e3b08aeSAndroid Build Coastguard Worker 10*9e3b08aeSAndroid Build Coastguard WorkerDFS code for graph traversal is trivial. SCC determination is not and we would 11*9e3b08aeSAndroid Build Coastguard Workerlike it to be provided by a reliable and efficient library component. 12*9e3b08aeSAndroid Build Coastguard Worker 13*9e3b08aeSAndroid Build Coastguard Worker## Core algorithm 14*9e3b08aeSAndroid Build Coastguard Worker 15*9e3b08aeSAndroid Build Coastguard WorkerThere are three commonly-studied asymptotically-optimal approaches to 16*9e3b08aeSAndroid Build Coastguard Workerdetermining the Strongly-Connected Components of a directed graph. Each of these 17*9e3b08aeSAndroid Build Coastguard Workeradmits various optimisations and specialisations for different purposes. 18*9e3b08aeSAndroid Build Coastguard Worker 19*9e3b08aeSAndroid Build Coastguard Worker* [Kosaraju's algorithm](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm) 20*9e3b08aeSAndroid Build Coastguard Worker* [Tarjan's algorithm](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) 21*9e3b08aeSAndroid Build Coastguard Worker* [The path-based algorithm](https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm) 22*9e3b08aeSAndroid Build Coastguard Worker 23*9e3b08aeSAndroid Build Coastguard WorkerKosaraju's algorithm is unsuited to DFS-generated graphs (such as type 24*9e3b08aeSAndroid Build Coastguard Workercomparison graphs) as it requires both forwards and reverse edges to be known 25*9e3b08aeSAndroid Build Coastguard Workerahead of time. 26*9e3b08aeSAndroid Build Coastguard Worker 27*9e3b08aeSAndroid Build Coastguard WorkerTarjan's algorithm can be massaged into the form where it can be separated into 28*9e3b08aeSAndroid Build Coastguard Workera plain DFS traversal and SCC-specific pieces but the resulting code is a bit 29*9e3b08aeSAndroid Build Coastguard Workermessy and responsibility for SCC state management is rather scattered. 30*9e3b08aeSAndroid Build Coastguard Worker 31*9e3b08aeSAndroid Build Coastguard WorkerThe path-based algorithm is the best fit and can be put in a form where the DFS 32*9e3b08aeSAndroid Build Coastguard Workertraversal and SCC state management are cleanly separated. The concept of "open" 33*9e3b08aeSAndroid Build Coastguard Workernodes carries directly over to the implementation used here and SCC state 34*9e3b08aeSAndroid Build Coastguard Workermanagement occurs in two well-defined places. 35*9e3b08aeSAndroid Build Coastguard Worker 36*9e3b08aeSAndroid Build Coastguard Worker* node visit starts; repeat visits to open nodes are detected 37*9e3b08aeSAndroid Build Coastguard Worker* node visit end; completed SCCs are detected 38*9e3b08aeSAndroid Build Coastguard Worker 39*9e3b08aeSAndroid Build Coastguard WorkerThe remainder of this document discusses the finer details of what the finder 40*9e3b08aeSAndroid Build Coastguard WorkerAPI should look like. 41*9e3b08aeSAndroid Build Coastguard Worker 42*9e3b08aeSAndroid Build Coastguard Worker## Choice of primitives 43*9e3b08aeSAndroid Build Coastguard Worker 44*9e3b08aeSAndroid Build Coastguard WorkerThe choice of primitives should be determined by the interface priorities: 45*9e3b08aeSAndroid Build Coastguard Worker 46*9e3b08aeSAndroid Build Coastguard Worker* simple - hard to misuse 47*9e3b08aeSAndroid Build Coastguard Worker* efficient - does not preclude optimal implementation 48*9e3b08aeSAndroid Build Coastguard Worker* powerful - no need to code around deficiencies 49*9e3b08aeSAndroid Build Coastguard Worker 50*9e3b08aeSAndroid Build Coastguard WorkerRoughly speaking, the SCC finder "opens" and "closes" nodes and the user code 51*9e3b08aeSAndroid Build Coastguard Workerwill try to open and close nodes at the beginning and end of each node visit. 52*9e3b08aeSAndroid Build Coastguard Worker 53*9e3b08aeSAndroid Build Coastguard Worker### Close 54*9e3b08aeSAndroid Build Coastguard Worker 55*9e3b08aeSAndroid Build Coastguard WorkerThe logic to follow when the SCC finder reports a complete SCC (nodes closed) is 56*9e3b08aeSAndroid Build Coastguard Workersimple: the user code must ensure it considers each node as definitively visited 57*9e3b08aeSAndroid Build Coastguard Workerfrom this point and avoid asking the finder to consider it again. This condition 58*9e3b08aeSAndroid Build Coastguard Workermeans the SCC does not have to independently track "ever visited" status that 59*9e3b08aeSAndroid Build Coastguard Workermay be duplicated elsewhere. 60*9e3b08aeSAndroid Build Coastguard Worker 61*9e3b08aeSAndroid Build Coastguard Worker### Open 62*9e3b08aeSAndroid Build Coastguard Worker 63*9e3b08aeSAndroid Build Coastguard WorkerWhen reaching a node via DFS, the user of the SCC finder has to perform a 3-way 64*9e3b08aeSAndroid Build Coastguard Workerclassification: 65*9e3b08aeSAndroid Build Coastguard Worker 66*9e3b08aeSAndroid Build Coastguard Worker1. never visited before - the node should immediately transition to open and 67*9e3b08aeSAndroid Build Coastguard Worker known to the SCC finder 68*9e3b08aeSAndroid Build Coastguard Worker2. open - the link just followed would create a cycle and the SCC finder 69*9e3b08aeSAndroid Build Coastguard Worker algorithm needs to do some state maintenance; the user code must not 70*9e3b08aeSAndroid Build Coastguard Worker recursively process the node 71*9e3b08aeSAndroid Build Coastguard Worker3. closed - the link just followed reaches a node already fully processed and 72*9e3b08aeSAndroid Build Coastguard Worker assigned to a SCC; the user code must not recursively process the node 73*9e3b08aeSAndroid Build Coastguard Worker 74*9e3b08aeSAndroid Build Coastguard WorkerThere are at least 3 different ways of structuring program logic to distinguish 75*9e3b08aeSAndroid Build Coastguard Workerthese paths. 76*9e3b08aeSAndroid Build Coastguard Worker 77*9e3b08aeSAndroid Build Coastguard Worker#### Populate user visited state on open 78*9e3b08aeSAndroid Build Coastguard Worker 79*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle: 80*9e3b08aeSAndroid Build Coastguard Worker 81*9e3b08aeSAndroid Build Coastguard Worker1. unvisited + not open 82*9e3b08aeSAndroid Build Coastguard Worker2. visited + open 83*9e3b08aeSAndroid Build Coastguard Worker3. visited + not open 84*9e3b08aeSAndroid Build Coastguard Worker 85*9e3b08aeSAndroid Build Coastguard WorkerIf a node has never been visited, it can be unconditionally opened. If it has 86*9e3b08aeSAndroid Build Coastguard Workerbeen visited, we must still check if it's open. This is a bit odd in the context 87*9e3b08aeSAndroid Build Coastguard Workerof the SCC algorithm as `really_open` and `is_open` become separate operations 88*9e3b08aeSAndroid Build Coastguard Worker(-simplicity). It's possible that a user could know, for other reasons, whether 89*9e3b08aeSAndroid Build Coastguard Workera visited node is open or not and then omitting to call `is_open` could upset 90*9e3b08aeSAndroid Build Coastguard Workerthe SCC finder (which needs to update its internal state in the already-open 91*9e3b08aeSAndroid Build Coastguard Workercase). This risk could be eliminated by duplicating the state update logic in 92*9e3b08aeSAndroid Build Coastguard Workerboth methods (-efficiency). 93*9e3b08aeSAndroid Build Coastguard Worker 94*9e3b08aeSAndroid Build Coastguard Worker```c++ 95*9e3b08aeSAndroid Build Coastguard Workerif (visited) { 96*9e3b08aeSAndroid Build Coastguard Worker if (is_open(node)) { 97*9e3b08aeSAndroid Build Coastguard Worker // cycle-breaking back link 98*9e3b08aeSAndroid Build Coastguard Worker return; 99*9e3b08aeSAndroid Build Coastguard Worker } else { 100*9e3b08aeSAndroid Build Coastguard Worker // work-saving link to shared node 101*9e3b08aeSAndroid Build Coastguard Worker return; 102*9e3b08aeSAndroid Build Coastguard Worker } 103*9e3b08aeSAndroid Build Coastguard Worker} 104*9e3b08aeSAndroid Build Coastguard Workermark_visited(); 105*9e3b08aeSAndroid Build Coastguard Workertoken = really_open(node); 106*9e3b08aeSAndroid Build Coastguard Worker... 107*9e3b08aeSAndroid Build Coastguard Worker// do work 108*9e3b08aeSAndroid Build Coastguard Worker... 109*9e3b08aeSAndroid Build Coastguard Workernodes = close(token); 110*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) { 111*9e3b08aeSAndroid Build Coastguard Worker ... 112*9e3b08aeSAndroid Build Coastguard Worker} 113*9e3b08aeSAndroid Build Coastguard Worker``` 114*9e3b08aeSAndroid Build Coastguard Worker 115*9e3b08aeSAndroid Build Coastguard Worker#### Check SCC open / closed first 116*9e3b08aeSAndroid Build Coastguard Worker 117*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle: 118*9e3b08aeSAndroid Build Coastguard Worker 119*9e3b08aeSAndroid Build Coastguard Worker1. not open + unvisited (never visited) 120*9e3b08aeSAndroid Build Coastguard Worker2. open (being visited) 121*9e3b08aeSAndroid Build Coastguard Worker3. not open + visited (closed) 122*9e3b08aeSAndroid Build Coastguard Worker 123*9e3b08aeSAndroid Build Coastguard WorkerThis scheme also requires separate `is_open` and `really_open` operations as 124*9e3b08aeSAndroid Build Coastguard Workernodes musn't be reopened (-simplicity, -efficiency). It does allow the user to 125*9e3b08aeSAndroid Build Coastguard Workermark nodes as visited any time between open and close (-simplicity, +power). 126*9e3b08aeSAndroid Build Coastguard Worker 127*9e3b08aeSAndroid Build Coastguard Worker```c++ 128*9e3b08aeSAndroid Build Coastguard Workerif (is_open(node)) { 129*9e3b08aeSAndroid Build Coastguard Worker // cycle-breaking back link 130*9e3b08aeSAndroid Build Coastguard Worker return; 131*9e3b08aeSAndroid Build Coastguard Worker} 132*9e3b08aeSAndroid Build Coastguard Workerif (visited) { 133*9e3b08aeSAndroid Build Coastguard Worker // work-saving link to shared node 134*9e3b08aeSAndroid Build Coastguard Worker return; 135*9e3b08aeSAndroid Build Coastguard Worker} 136*9e3b08aeSAndroid Build Coastguard Workertoken = really_open(node); 137*9e3b08aeSAndroid Build Coastguard Worker... 138*9e3b08aeSAndroid Build Coastguard Worker// do work 139*9e3b08aeSAndroid Build Coastguard Worker... 140*9e3b08aeSAndroid Build Coastguard Workernodes = close(token); 141*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) { 142*9e3b08aeSAndroid Build Coastguard Worker // last chance to call mark_visited 143*9e3b08aeSAndroid Build Coastguard Worker} 144*9e3b08aeSAndroid Build Coastguard Worker``` 145*9e3b08aeSAndroid Build Coastguard Worker 146*9e3b08aeSAndroid Build Coastguard Worker#### Test user visited state before open, populate on close 147*9e3b08aeSAndroid Build Coastguard Worker 148*9e3b08aeSAndroid Build Coastguard WorkerNOTE: This is the currently implemented approach. 149*9e3b08aeSAndroid Build Coastguard Worker 150*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle: 151*9e3b08aeSAndroid Build Coastguard Worker 152*9e3b08aeSAndroid Build Coastguard Worker1. unvisited + not open 153*9e3b08aeSAndroid Build Coastguard Worker2. unvisited + open 154*9e3b08aeSAndroid Build Coastguard Worker3. visited + not open 155*9e3b08aeSAndroid Build Coastguard Worker 156*9e3b08aeSAndroid Build Coastguard WorkerThis is the purest form of the algorithm with the `open` and `close` operations 157*9e3b08aeSAndroid Build Coastguard Workerclearly bracketing "real" work. `really_open` and `is_open` operations are 158*9e3b08aeSAndroid Build Coastguard Workerfolded together into `open` which will fail to open an already open node 159*9e3b08aeSAndroid Build Coastguard Worker(+simplicity, +efficiency). 160*9e3b08aeSAndroid Build Coastguard Worker 161*9e3b08aeSAndroid Build Coastguard WorkerThe user value store cannot be something that gets initialised and updated 162*9e3b08aeSAndroid Build Coastguard Workerbetween open and close or it will also need to duplicate the open/closed state 163*9e3b08aeSAndroid Build Coastguard Workerand this correspondence will need to be maintained as an invariant (-power). 164*9e3b08aeSAndroid Build Coastguard Worker 165*9e3b08aeSAndroid Build Coastguard Worker```c++ 166*9e3b08aeSAndroid Build Coastguard Workerif (visited) { 167*9e3b08aeSAndroid Build Coastguard Worker // work-saving link to shared node 168*9e3b08aeSAndroid Build Coastguard Worker return; 169*9e3b08aeSAndroid Build Coastguard Worker} 170*9e3b08aeSAndroid Build Coastguard Workertoken = open(node); 171*9e3b08aeSAndroid Build Coastguard Workerif (!token) { 172*9e3b08aeSAndroid Build Coastguard Worker // cycle-breaking back link 173*9e3b08aeSAndroid Build Coastguard Worker return; 174*9e3b08aeSAndroid Build Coastguard Worker} 175*9e3b08aeSAndroid Build Coastguard Worker... 176*9e3b08aeSAndroid Build Coastguard Worker// do work 177*9e3b08aeSAndroid Build Coastguard Worker... 178*9e3b08aeSAndroid Build Coastguard Workernodes = close(token.value()); 179*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) { 180*9e3b08aeSAndroid Build Coastguard Worker ... 181*9e3b08aeSAndroid Build Coastguard Worker mark_visited(); 182*9e3b08aeSAndroid Build Coastguard Worker} 183*9e3b08aeSAndroid Build Coastguard Worker``` 184*9e3b08aeSAndroid Build Coastguard Worker 185*9e3b08aeSAndroid Build Coastguard WorkerEvaluating equality can be made to fit nicely into this category, as can using 186*9e3b08aeSAndroid Build Coastguard Workerequality along with the return stack to build a diff tree with stubs for 187*9e3b08aeSAndroid Build Coastguard Workersharing- and cycle-breaking links. 188*9e3b08aeSAndroid Build Coastguard Worker 189*9e3b08aeSAndroid Build Coastguard WorkerHowever, building a graph (say a copy of the traversal, or a diff graph) 190*9e3b08aeSAndroid Build Coastguard Workerrequires open node state to be squirrelled away somewhere. 191*9e3b08aeSAndroid Build Coastguard Worker 192*9e3b08aeSAndroid Build Coastguard Worker##### Enhancement 193*9e3b08aeSAndroid Build Coastguard Worker 194*9e3b08aeSAndroid Build Coastguard WorkerThe SCC finder data structure can be made to carry values associated with open 195*9e3b08aeSAndroid Build Coastguard Workernodes and hand them to the user on failure-to-open and closure. This allows us 196*9e3b08aeSAndroid Build Coastguard Workerto retain purity and regain the ability to maintain simple state for open nodes 197*9e3b08aeSAndroid Build Coastguard Workerseparately from that for closed nodes, at the expense of a slightly 198*9e3b08aeSAndroid Build Coastguard Workerheavier-touch interface (+power). 199*9e3b08aeSAndroid Build Coastguard Worker 200*9e3b08aeSAndroid Build Coastguard WorkerIn the simplest case, we'd want nothing stored at all (beyond the node identity) 201*9e3b08aeSAndroid Build Coastguard Workerand actually supplying a second empty type would be an annoyance and an 202*9e3b08aeSAndroid Build Coastguard Workerinefficiency (-simplicity, -power, -efficiency)). So the best thing to supply is 203*9e3b08aeSAndroid Build Coastguard Workerthe user's container's `value_type` and associated `value_compare` comparator. 204*9e3b08aeSAndroid Build Coastguard Worker 205*9e3b08aeSAndroid Build Coastguard WorkerHowever, in this variation, it's painful to set up the SCC structures for 206*9e3b08aeSAndroid Build Coastguard Workerefficient `open` as nodes need to exist in a map or set, independently of any 207*9e3b08aeSAndroid Build Coastguard Workerpayload. The approach could be revisited if there's a solution to this. 208*9e3b08aeSAndroid Build Coastguard Worker 209*9e3b08aeSAndroid Build Coastguard Worker```c++ 210*9e3b08aeSAndroid Build Coastguard Workerif (visited) { 211*9e3b08aeSAndroid Build Coastguard Worker // work-saving link to shared node 212*9e3b08aeSAndroid Build Coastguard Worker return; 213*9e3b08aeSAndroid Build Coastguard Worker} 214*9e3b08aeSAndroid Build Coastguard Worker[&node_state, token] = open(node_state); 215*9e3b08aeSAndroid Build Coastguard Workerif (!token) { 216*9e3b08aeSAndroid Build Coastguard Worker // cycle-breaking back link 217*9e3b08aeSAndroid Build Coastguard Worker return; 218*9e3b08aeSAndroid Build Coastguard Worker} 219*9e3b08aeSAndroid Build Coastguard Worker... 220*9e3b08aeSAndroid Build Coastguard Worker// do work, update node_state if you like 221*9e3b08aeSAndroid Build Coastguard Worker... 222*9e3b08aeSAndroid Build Coastguard Workernode_states = close(token.value()) 223*9e3b08aeSAndroid Build Coastguard Workerif (!node_states.empty()) { 224*9e3b08aeSAndroid Build Coastguard Worker ... 225*9e3b08aeSAndroid Build Coastguard Worker mark_visited(); 226*9e3b08aeSAndroid Build Coastguard Worker} 227*9e3b08aeSAndroid Build Coastguard Worker``` 228