1/***************************************************************************** 2 * This file is part of the Prolog Development Tool (PDT) 3 * 4 * Author: G�nter Kniesel (among others) 5 * WWW: http://sewiki.iai.uni-bonn.de/research/pdt/start 6 * Mail: pdt@lists.iai.uni-bonn.de 7 * Copyright (C): 2004-2012, CS Dept. III, University of Bonn 8 * 9 * All rights reserved. This program is made available under the terms 10 * of the Eclipse Public License v1.0 which accompanies this distribution, 11 * and is available at http://www.eclipse.org/legal/epl-v10.html 12 * 13 ****************************************************************************/ 14 15/* 16 * This file implements generic graph algorithms in Prolog. 17 * - Tarjan's O(M+N) computation of strongly connected components (SCCs) 18 * extended to deal also with nested SCCs. Nested SCCs are collapsed 19 * into a single one. 20 * - Depth first path search (also O(M+N)) 21 * 22 * To use the algorithms in the context of module M do the following: 23 * - import this module into M via ":-use_module(THISMODULENAME)." 24 * - define the graph on which the algorithms should operate by 25 * providing in M clauses for the predicates 26 * - graph_node(Node) 27 * - graph_edge(FromNode,ToNode) 28 * By adhering to this convention, different modules can work on different 29 * locally defined graphs without interfering. 30 * DO not even think of patching 31 * Autor: G�nter Kniesel 32 * Date: September 26, 2005 33 * Date: October 2, 2009: Made "public" predicates module transparent 34 */ 35:- module('condor.graph.cycle.scc', 36 [ strongly_connected/1 % Arg1 = List of node lists representing SCCs 37 , dfs/3 % Called locally in metapredicates 38 , node/1 % Called locally in metapredicates 39 , edge/2 % Called locally in metapredicates 40 ] ). 41 42 43 44 45 46/* 47 * Global data structures for depth first traversal and SCC. 48 */ 49:- dynamic discovery_time/2. % args = node, time 50:- dynamic finishing_time/2. % args = node, time 51:- dynamic global_time/1. 52:- dynamic scc_counter/1. 53:- dynamic current_scc_nr/1. 54:- dynamic scc_nr_for/2. 55:- dynamic graph_orientation/1. 56 57 58 59/* 60 * Define graph structure based on predicates graph_edge/2 61 * and graph_node/1 to be defined in the calling module. 62 */ 63:- module_transparent node/1. 64node(N) :- 65 context_module(M), 66 M:graph_node(N). 67 68:- module_transparent edge/2. 69edge(N1,N2) :- 70 context_module(M), 71 ( graph_orientation(forward) 72 -> M:graph_edge(N1,N2) 73 ; M:graph_edge(N2,N1) 74 ). 75 76 77/* 78 * For the graph defined by node/1 and edge/2 calculate the 79 * strongly connected components (SCCs) using Tarjan's algorithm: 80 * 1. Perform a depth first toplogical sort that 81 * assigns start and finishing times to the nodes. 82 * 2. Order nodes by reverse finishing time. 83 * 3. Inverse the direction of graph edges. 84 * 4. Perform a depth first topological sort of the reversed 85 * graph processing the nodes in the order of reverse 86 * finishing time determined in step 2. 87 * 5. Each path determined in this second toposort is a SCC. 88 */ 89:- module_transparent( strongly_connected/1 ). 90 91strongly_connected(SortedUniqueSCCs) :- 92 clean, % Initialize local data for first DF traversal 93 visit_all_nodes_randomly(_NumberedPaths), 94 % show_internals, 95 sort_by_reverse_finishing_times(Nodes), 96 clean, % Reinitialize local data for second DF traversal 97 % Must come AFTER sort_by_reverse_finishing_times! 98 set_graph_orientation(reverse), % invert graph edges 99 visit_all_nodes_ordered(Nodes,NumberedSccCandidatess), 100 % show_internals, 101 combine_nested_cycles(NumberedSccCandidatess,NumberedSCCs), 102 findall(Unique, 103 ( member((_,Path),NumberedSCCs), 104 sort(Path,Sorted), 105 removeDuplicates(Sorted,Unique) 106 ), 107 SortedUniqueSCCs), 108 true. 109 110 111 112combine_nested_cycles(SccCandidates,SCCs):- 113 length(SccCandidates,L), 114 scc_counter(L), 115 !, 116 SccCandidates = SCCs. 117combine_nested_cycles(SccCandidates,SCCs):- 118 % findall( (Cnt,Path1), scc_candidate(Cnt,Path1), All), 119 patch(SccCandidates,SCCs).
125patch( [],[] ). 126patch( [(Nr,List1)|Tail],Patch) :- 127 member_remove_first((Nr,List2),Tail,Rest), 128 !, 129 append(List2,List1,L21), 130 patch( [(Nr,L21)|Rest],Patch). 131patch( [Elem|Tail],[Elem|Patch]) :- 132 patch( Tail,Patch).
138member_remove_first(Elem,[Elem|Rest],Rest) :- !. 139member_remove_first(Elem,[H|Tail],[H|Rest]):- 140 member_remove_first(Elem,Tail,Rest). 141 142/* 143 * Topological sorting based on depth first search returns a list 144 * of depths first paths through the graph, starting randomly. 145 * This variant is used in the first phase of Tarjan's algorithm 146 * for computing strongly connected components. 147 */ 148:- module_transparent( visit_all_nodes_randomly/1 ). 149 150visit_all_nodes_randomly(Paths) :- 151 findall((Cnt,Path), ( % dfs_for_node(Cnt,Path) 152 node(N), dfs(N,Cnt,Path) 153 ), Paths). 154 155 156 157/* 158 * Topological sorting based on depth first search returns a list 159 * of depths first paths through the graph, proceessing the nodes 160 * of the graph in the order indicated by NodeList. 161 * This variant is used in the first phase of Tarjan's algorithm 162 * for computing strongly connected components. When used that 163 * way the computed list of paths corresponds to the set of 164 * strongly connected components of the graph. 165 */ 166:- module_transparent( visit_all_nodes_ordered/2 ). 167visit_all_nodes_ordered(NodeList,Paths) :- 168 findall((Cnt,Path), ( % dfs_for_node_from_list(NodeList,Cnt,Path) 169 member(N,NodeList),dfs(N,Cnt,Path) 170 ), Paths). 171 172 173:- module_transparent( dfs_for_node/2 ). 174:- module_transparent( dfs_for_node_from_list/3 ). 175dfs_for_node( Cnt,Path) :- node(N), dfs(N,Cnt,Path). 176dfs_for_node_from_list(NodeList,Cnt,Path) :- member(N,NodeList),dfs(N,Cnt,Path). 177 178 179:- module_transparent( dfs/3 ). 180dfs(N,Cnt,Path) :- 181 visit_first_node(N,Path), % Increments scc_counter only once 182 clause('condor.graph.cycle.scc':scc_counter(Cnt),_). % New value for every FIRST node! 183 184:- module_transparent( visit_first_node/2 ). 185visit_first_node(N,Path) :- 186 not('condor.graph.cycle.scc':visited(N)), 187 set_time(N, discovery_time), % assert discovery_time(N,...) 188 increment_scc_counter, 189 visit_neighbours(N,Path). 190 191/* 192 * Depth first traversal starting at node N returns path Path. 193 */ 194:- module_transparent( visit_node/2 ). 195visit_node(N,Path) :- 196 not('condor.graph.cycle.scc':visited(N)), 197 set_time(N, discovery_time), % assert discovery_time(N,...) 198 visit_neighbours(N,Path). 199 200 201/* 202 * A node has already been visited if its discovery time is set. 203 * The node might still be visited ('grey' node) or its visit might 204 * already be finished ('black' node). Use finished/1 to find out 205 * the difference. 206 */ 207visited(N) :- 208 discovery_time(N,_), 209 !. 210 211 212/* 213 * Visit one neighbour at one time. Visit them all upon backtracking. 214 * Uses the graph_edge/2 definition from the calling context module. 215 */ 216:- module_transparent( visit_neighbours/2 ). 217 218visit_neighbours(N,Path) :- % no unvisited neighbours 219 dead_end(N), 220 !, 221 Path = [N], 222 set_time(N, finishing_time). % assert finishing_time(N,...) 223visit_neighbours(N,Path) :- % visit all unvisited neighbours 224 context_module(M), 225 M:edge(N,Other), % ... by backtracking over M:edge/2 226 not( 'condor.graph.cycle.scc':visited(Other) ), 227 Path = [N|Rest], 228 visit_node(Other,Rest). 229visit_neighbours(N,_) :- % all neighbours visited 230 set_time(N, finishing_time), % assert finishing_time(N,...) 231 fail. 232 233 234/* 235 * The node N either has no neighbours at all or it has only 236 * visited ones. 237 */ 238:- module_transparent dead_end/1. 239 240dead_end(N) :- 241 not( edge(N,_) ), 242 !. 243dead_end(N) :- 244 forall( edge(N,N2), 'condor.graph.cycle.scc':visited(N2) ). 245 246 247 248/* 249 * Sort nodes in reverse finishing time assigned by first 250 * toposort pass through the graph. 251 */ 252sort_by_reverse_finishing_times(Nodes) :- 253 findall( (Time,N), finishing_time(N,Time), All), 254 sort(All,Sorted), 255 reverse(Sorted,Rev), 256 findall( Node, member((_,Node), Rev), Nodes). 257 258/* 259test(sort_by_reverse_finishing_times(Nodes),Expected) :- 260 sort_by_reverse_finishing_times(Nodes), 261 Expected = [a,b,e,d,c,f]. 262 263finishing_time(a, 18). 264finishing_time(b, 17). 265finishing_time(c, 6). 266finishing_time(d, 7). 267finishing_time(e, 8). 268finishing_time(f, 3). 269*/ 270 271 272/* --------- Helper Predicates -------------------------- */ 273 274 275/* 276 * Show snapshot of helper data structures. 277 */ 278show_internals :- 279 listing_if_defined(discovery_time), 280 listing_if_defined(finishing_time), 281 listing_if_defined(global_time), 282 listing_if_defined(scc_counter), 283 listing_if_defined(graph_orientation). 284 285 286/* 287 * (Re)Initialize helper data structures. 288 */ 289clean :- 290 retractall(discovery_time(_,_)), 291 retractall(finishing_time(_,_)), 292 reset_time, 293 reset_scc_counter, 294 set_graph_orientation(forward). 295 296 297/* 298 * Assert discovery_time(N,currentTime) or finishing_time(N,currentTime) 299 * and increment currentTime. 300 */ 301set_time(N, Which) :- 302 retract(global_time(T)), 303 T1 is T+1, 304 assert(global_time(T1)), 305 Fact =.. [Which, N, T], % discovery_time(N,T) or finishing_time(N,T) 306 assert(Fact).
312reset_time :-
313 retractall(global_time(_)),
314 assert(global_time(1)).
321reset_scc_counter :- 322 set_scc_counter(0). 323 324set_scc_counter(New) :- 325 retractall(scc_counter(_)), 326 assert(scc_counter(New)). 327 % format('asserted scc_counter(~a).~n',[New]). 328 329increment_scc_counter :- 330 retract(scc_counter(Old)), 331 New is Old+1, 332 assert(scc_counter(New)). 333 % format('asserted scc_counter(~a).~n',[New]). 334 335 336 337/* 338 * Set graph orientation. Legal values are 'forward' and 339 * 'reverse'. (Actually, anything different from 'forward' 340 * is treated as 'reverse'. This flag controls the direction 341 * of arcs in the graph. In the second phase it allows to 342 * virtually invert the arcs without physically copying the 343 * graph. 344 */ 345set_graph_orientation(X) :- 346 retractall(graph_orientation(_)), 347 assert(graph_orientation(X))