Chi-Tech
chi_directed_graph.cc
Go to the documentation of this file.
2
3#include "chi_runtime.h"
4#include "chi_log.h"
5#include "chi_mpi.h"
6
7#include <sstream>
8#include <algorithm>
9
10//###################################################################
11/** Adds a vertex to the graph with a supplied id.*/
13 VertexAccessor::AddVertex(size_t id, void* context)
14{
15 vertices_.emplace_back(id, context);
16 vertex_valid_flags_.push_back(true);
17}
18
19/** Adds a vertex to the graph where the ID is assigned to
20 * the number of vertices already loaded on the graph.
21 * For example, if there are 3 vertices on the graph (with
22 * IDs 0 through 2) then the next vertex (this one) will
23 * be assigned and ID of 3.*/
25VertexAccessor::AddVertex(void* context)
26{
27 vertices_.emplace_back(vertices_.size(), context);
28 vertex_valid_flags_.push_back(true);
29}
30
31
32//###################################################################
33/** Removes a vertex from the graph.*/
36{
37 ChiLogicalErrorIf(v >= vertices_.size(), "Error removing vertex.");
38
39 auto& vertex = vertices_[v];
40
41 //=================================== Get adjacent vertices
42 auto num_us = vertex.us_edge.size();
43 auto num_ds = vertex.ds_edge.size();
44 std::vector<size_t> adj_verts;
45 adj_verts.reserve(num_us+num_ds);
46
47 for (size_t u : vertex.us_edge)
48 adj_verts.push_back(u);
49
50 for (size_t u : vertex.ds_edge)
51 adj_verts.push_back(u);
52
53 //=================================== Remove v from all u
54 for (size_t u : adj_verts)
55 {
56 vertices_[u].us_edge.erase(v);
57 vertices_[u].ds_edge.erase(v);
58 }
59
60 vertex_valid_flags_[v] = false;
61}
62
63//###################################################################
64/** Accesses a vertex from the graph.*/
67{
68 if (not vertex_valid_flags_[v])
70 << "chi_graph::DirectedGraph::VertexAccessor: "
71 "Invalid vertex accessed. Vertex may have been removed.";
72 return vertices_[v];
73}
74
75//###################################################################
76/** Adds a vertex to the graph. By default <I>context</I> is
77 * assumed to be nullptr.*/
78void chi::DirectedGraph::AddVertex(size_t id,void* context/*=nullptr*/)
79{
80 vertices.AddVertex(id, context);
81}
82
83//###################################################################
84/** Adds a vertex to the graph. By default <I>context</I> is
85 * assumed to be nullptr and <I>id</I> is assumed to be assigned
86 * automatically. In
87 * the latter case the vertex id will be the same as the order
88 * in which it was added (0,1,2,3,etc ... will have id's 0,1,2,3,etc)*/
89void chi::DirectedGraph::AddVertex(void* context/*=nullptr*/)
90{
91 vertices.AddVertex(context);
92}
93
94//###################################################################
95/** Removes a vertex from the graph. This method does not
96 * free any context related data.*/
98 RemoveVertex(size_t v)
99{
101}
102
103//###################################################################
104/** Adds an edge to the graph. Range checks are supplied by the
105 * vertex accessor.*/
107 AddEdge(size_t from, size_t to, double weight)
108{
109 vertices[from].ds_edge.insert(to);
110 vertices[to].us_edge.insert(from);
111
112 vertices[from].ds_weights[to] = weight;
113 vertices[to].us_weights[from] = weight;
114
115 return true;
116}
117
118//###################################################################
119/**Remove an edge from the graph. Range checks are supplied by the
120 * vertex accessor.*/
121void chi::DirectedGraph::RemoveEdge(size_t from, size_t to)
122{
123 vertices[from].ds_edge.erase(to);
124 vertices[to].us_edge.erase(from);
125}
126
127//###################################################################
128/** Depth-First-Search main recursive algorithm. This is the recursive
129 * portion of the method below this one
130 * (chi_graph::DirectedGraph::DepthFirstSearch).*/
132 DFSAlgorithm(std::vector<size_t> &traversal,
133 std::vector<bool> &visited,
134 size_t cur_vid)
135{
136 traversal.push_back(cur_vid);
137 visited[cur_vid] = true;
138
139 for (auto v : vertices[cur_vid].ds_edge)
140 if (not visited[v])
141 DFSAlgorithm(traversal,visited,v);
142
143}
144
145//###################################################################
146/**SCC main recursive algorithm. This is the recursive call for the
147 * method defined below this one
148 * (chi_graph::DirectedGraph::FindStronglyConnectedConnections).*/
150 size_t u, int& time,
151 std::vector<int>& disc,
152 std::vector<int>& low,
153 std::vector<bool>& on_stack,
154 std::stack<size_t>& stack,
155 std::vector<std::vector<size_t>>& SCCs)
156{
157 // Init discovery time and low value
158 disc[u] = low[u] = ++time;
159 stack.push(u);
160 on_stack[u] = true;
161
162 for (auto v : vertices[u].ds_edge)
163 {
164 if (disc[v] == -1)
165 {
166 SCCAlgorithm(v,time,disc,low,on_stack,stack,SCCs);
167 low[u] = std::min(low[u],low[v]);
168 }
169 else if (on_stack[v])
170 low[u] = std::min(low[u],disc[v]);
171 }
172
173 size_t w=0;
174 if (low[u] == disc[u])
175 {
176 std::vector<size_t> sub_SCC;
177 while (stack.top() != u)
178 {
179 w = stack.top();
180 sub_SCC.push_back(w);
181 on_stack[w] = false;
182 stack.pop();
183 }
184 w = stack.top();
185 sub_SCC.push_back(w);
186 if (sub_SCC.size() > 1) SCCs.push_back(sub_SCC);
187 on_stack[w] = false;
188 stack.pop();
189 }
190
191}
192
193//###################################################################
194/**Find strongly connected components. This method is the implementation
195 * of Tarjan's algorithm [1].
196 *
197 * [1] Tarjan R.E. "Depth-first search and linear graph algorithms",
198 * SIAM Journal on Computing, 1972.
199 *
200 * It returns collections of vertices that form strongly connected
201 * components excluding singletons.*/
202std::vector<std::vector<size_t>>
205{
206 size_t V = vertices.size();
207
208 std::vector<int> disc(V,-1); // Discovery times
209 std::vector<int> low(V,-1); // Earliest visited vertex
210 std::vector<bool> on_stack(V,false); // On stack flags
211 std::stack<size_t> stack; // Stack
212
213 std::vector<std::vector<size_t>> SCCs; // Collection of SCCs
214
215 int time = 0;
216
217 for (size_t v=0; v<V; ++v)
218 if (disc[v]==-1)
219 SCCAlgorithm(v,time,disc,low,on_stack,stack,SCCs);
220
221
222 return SCCs;
223}
224
225
226//###################################################################
227/** Generates a topological sort. This method is the implementation
228 * of Kahn's algorithm [1].
229 *
230 * [1] Kahn, Arthur B. (1962), "Topological sorting of large networks",
231 * Communications of the ACM, 5 (11): 558–562
232 *
233 * \return Returns the vertex ids sorted topologically. If this
234 * vector is empty the algorithm failed because it detected
235 * cyclic dependencies.*/
237{
238 bool has_cycles=false;
239 std::vector<size_t> L;
240 std::vector<GraphVertex*> S;
241
242 L.reserve(vertices.size());
243 S.reserve(vertices.size());
244
245 //======================================== Make a copy of the graph
246 auto cur_vertices = vertices;
247
248 //======================================== Identify vertices that
249 // have no incoming edge
250 for (auto& vertex : cur_vertices)
251 if (vertex.us_edge.empty())
252 S.push_back(&vertex);
253
254 if (S.empty())
255 {
256 has_cycles = true;
257 goto endofalgo;
258 }
259
260 //======================================== Repeatedly remove
261 // vertices
262 while (not S.empty())
263 {
264 GraphVertex* node_n = S.back(); size_t n=node_n->id;
265 S.erase(S.end()-1);
266
267 L.push_back(n);
268 auto nodes_m = node_n->ds_edge;
269 for (size_t m : nodes_m)
270 {
271 GraphVertex* node_m = &cur_vertices[m];
272
273 //remove edge from graph
274 node_n->ds_edge.erase(m);
275 node_m->us_edge.erase(n);
276
277 if (node_m->us_edge.empty())
278 S.push_back(node_m);
279 }
280 }
281
282 endofalgo:
283 {
284 if (has_cycles)
285 return {};
286 if (L.size() != vertices.size())
287 return {};
288 }
289
290 return L;
291}
292
293//###################################################################
294/**Finds a sequence that minimizes the Feedback Arc Set (FAS). This
295 * algorithm implements the algorithm depicted in [1].
296 *
297 * [1] Eades P., Lin X., Smyth W.F., "Fast & Effective heuristic for
298 * the feedback arc set problem", Information Processing Letters,
299 * Volume 47. 1993.*/
300std::vector<size_t> chi::DirectedGraph::
302{
303 auto GetVertexDelta = [](chi::GraphVertex& vertex)
304 {
305 double delta = 0.0;
306 for (size_t ds : vertex.ds_edge)
307 delta += 1.0*vertex.ds_weights[ds];
308
309 for (size_t us : vertex.us_edge)
310 delta -= 1.0*vertex.us_weights[us];
311
312 return delta;
313 };
314
315 auto& TG = *this;
316
317 //==================================== Execute GR-algorithm
318 std::vector<size_t> s1,s2,s;
319 while (TG.vertices.GetNumValid()>0)
320 {
321 //======================== Remove sinks
322 while (TG.GetNumSinks()>0)
323 {
324 for (auto& u : TG.vertices)
325 if (u.ds_edge.empty())
326 {
327 TG.RemoveVertex(u.id);
328 s2.push_back(u.id);
329 break;
330 }
331 }//G contains sinks
332
333 //======================== Remove sources
334 while (TG.GetNumSources()>0)
335 {
336 for (auto& u : TG.vertices)
337 if (u.us_edge.empty())
338 {
339 TG.RemoveVertex(u.id);
340 s1.push_back(u.id);
341 break;
342 }
343 }//G contains sinks
344
345 //======================== Get max delta
346 std::pair<int,double> max_delta(-1,-100.0);
347 for (auto& u : TG.vertices)
348 {
349 double delta = GetVertexDelta(u);
350 if (delta > max_delta.second)
351 max_delta = std::make_pair(u.id,delta);
352 }
353
354 //======================== Remove max delta
355 TG.RemoveVertex(max_delta.first);
356 s1.push_back(max_delta.first);
357 }
358
359
360 //========================== Make appr. minimum FAS sequence
361 s.reserve(s1.size() + s2.size());
362 for (size_t u : s1) s.push_back(u);
363 for (size_t u : s2) s.push_back(u);
364
365 return s;
366}
367
368
369//###################################################################
370/**Prints the graph in Graphviz format.*/
372{
373 if (Chi::mpi.location_id != location_mask) return;
374
375 std::stringstream o;
376 std::string offset(" ");
377 o << "Printing directed graph:\n";
378 o << "digraph DG {\n";
379
380 o << offset << "splines=\"FALSE\";\n";
381 o << offset << "rankdir=\"LR\";\n\n";
382
383
384 o << offset << "/* Vertices */\n";
385 for (auto& v : vertices)
386 o << offset << v.id << " [shape=\"circle\"]\n";
387
388 o << "\n" << offset << "/* Edges */\n";
389 for (auto& v : vertices)
390 for (int w : v.ds_edge)
391 o << offset << v.id << " -> " << w << "\n";
392
393 o << "}\n";
394
395 std::cout << o.str();
396}
397
398//###################################################################
399/**Prints a sub-graph in Graphviz format.*/
401 PrintSubGraphviz(const std::vector<int>& verts_to_print,
402 int location_mask)
403{
404 if (Chi::mpi.location_id != location_mask) return;
405
406 std::stringstream o;
407 std::string offset(" ");
408 o << "Printing directed graph:\n";
409 o << "digraph DG {\n";
410
411 o << offset << "splines=\"FALSE\";\n";
412 o << offset << "rankdir=\"LR\";\n\n";
413
414
415 o << offset << "/* Vertices */\n";
416 for (int v : verts_to_print)
417 o << offset << v << " [shape=\"circle\"]\n";
418
419 o << "\n" << offset << "/* Edges */\n";
420 for (int v : verts_to_print)
421 for (int w : vertices[v].ds_edge)
422 {
423 if (std::find(verts_to_print.begin(),
424 verts_to_print.end(),
425 w) != verts_to_print.end())
426 o << offset << v << " -> " << w << "\n";
427 }
428
429 o << "}\n";
430
431 std::cout << o.str();
432}
433
434//###################################################################
435std::vector<std::pair<size_t,size_t>>
437{
438 std::vector<std::pair<size_t,size_t>> edges_to_remove;
439
440 //============================================= Utility lambdas
441 auto IsInList = [](std::vector<size_t>& list, size_t val)
442 {
443 return std::find(list.begin(),list.end(),val) != list.end();
444 };
445
446
447 //============================================= Find initial SCCs
449
450 int iter=0;
451 while (not SCCs.empty())
452 {
453 if (Chi::log.GetVerbosity() >= chi::ChiLog::LOG_LVL::LOG_0VERBOSE_2)
455 << "Inter cell cyclic dependency removal. Iteration " << ++iter;
456
457 //============================================= Remove bi-connected then
458 // tri-connected SCCs then
459 // n-connected
460 for (auto& subDG : SCCs)
461 {
462 //====================================== If bi-connected
463 if (subDG.size()==2)
464 {
465 RemoveEdge(subDG.front(), subDG.back());
466 edges_to_remove.emplace_back(subDG.front(), subDG.back());
467 }//bi-connected
468 //====================================== If tri-connected
469 else if (subDG.size()==3)
470 {
471 bool found=false;
472 for (size_t u : subDG)
473 {
474 for (size_t v : vertices[u].ds_edge)
475 if (IsInList(subDG,v))
476 {
477 found=true;
478 RemoveEdge(u, v);
479 edges_to_remove.emplace_back(u, v);
480 break;
481 }
482 if (found) break;
483 }//for u
484 }//tri-connected
485 //====================================== If n-connected
486 else
487 {
488 //=============================== Add vertices to temporary graph
489 chi::DirectedGraph TG; //Temp Graph
490 for (size_t k=0; k<subDG.size(); ++k)
491 TG.AddVertex();
492
493 //=============================== Add local connectivity
494 int mapping_u = 0;
495 for (auto u : subDG)
496 {
497 for (auto v : vertices[u].ds_edge)
498 {
499 auto mapv = std::find(subDG.begin(),subDG.end(),v);
500 if (mapv != subDG.end())
501 {
502 size_t mapping_v = mapv - subDG.begin();
503 TG.AddEdge(mapping_u,mapping_v,vertices[u].ds_weights[v]);
504 }
505 }//for v
506
507 ++mapping_u;
508 }//for u
509
510 //=============================== Make a copy of the graph verts
511 std::vector<chi::GraphVertex> verts_copy;
512 verts_copy.reserve(TG.vertices.size());
513 for (auto& v : TG.vertices)
514 verts_copy.push_back(v);
515
516 //=============================== Solve the minimum Feedback
517 // Arc Set (FAS) problem
518 auto s = TG.FindApproxMinimumFAS();
519
520 //========================== Build a sequence map
521 // This maps original sequence
522 // to minFAS sequence. i.e. originally
523 // we had v=0,1,2,3... and afterwards we
524 // something like s=7,3,1,5,0,....
525 // smap[v] then gives the position of v in s
526 std::vector<int> smap(s.size(),-1);
527 int count=0;
528 for (size_t u: s)
529 smap[u] = count++;
530
531 //========================== Build edges to remove
532 std::vector<std::pair<int,int>> edges_to_rem;
533 for (auto& u : verts_copy)
534 {
535 int cur_map = smap[u.id];
536 for (size_t v : u.ds_edge)
537 {
538 int adj_map = smap[v];
539 if (adj_map < cur_map)
540 edges_to_rem.emplace_back(u.id,v);
541 }
542 }
543
544 for (auto& edge : edges_to_rem)
545 {
546 size_t u = subDG[edge.first];
547 size_t v = subDG[edge.second];
548 RemoveEdge(u, v);
549 edges_to_remove.emplace_back(u, v);
550 }
551
552 }//n-connected
553 }//for sub-DG
554
555
556 //============================================= Find SSCs again
557 // This step is like an insurance policy for if
558 // something came through. There should be no SSCs
559 // after the minFAS process, however, we just look
560 // again in-case.
562 }
563
564 return edges_to_remove;
565}
566
567//###################################################################
568/**Clears all the data structures associated with the graph.*/
570{
571 vertices.clear();
572}
573
574
575//###################################################################
576/**Destructor.*/
578{
579 Clear();
580}
#define ChiLogicalErrorIf(condition, message)
static chi::ChiLog & log
Definition: chi_runtime.h:81
static chi::MPI_Info & mpi
Definition: chi_runtime.h:78
LogStream LogAllError()
Definition: chi_log.h:239
LogStream LogAll()
Definition: chi_log.h:237
void AddVertex(size_t id, void *context)
std::vector< GraphVertex > vertices_
std::vector< size_t > GenerateTopologicalSort()
void SCCAlgorithm(size_t u, int &time, std::vector< int > &disc, std::vector< int > &low, std::vector< bool > &on_stack, std::stack< size_t > &stack, std::vector< std::vector< size_t > > &SCCs)
std::vector< std::pair< size_t, size_t > > RemoveCyclicDependencies()
void AddVertex(size_t id, void *context=nullptr)
std::vector< size_t > FindApproxMinimumFAS()
VertexAccessor vertices
void PrintSubGraphviz(const std::vector< int > &verts_to_print, int location_mask=0)
std::vector< std::vector< size_t > > FindStronglyConnectedComponents()
void RemoveEdge(size_t from, size_t to)
void DFSAlgorithm(std::vector< size_t > &traversal, std::vector< bool > &visited, size_t cur_vid)
void RemoveVertex(size_t v)
bool AddEdge(size_t from, size_t to, double weight=1.0)
void PrintGraphviz(int location_mask=0)
std::set< size_t > ds_edge
std::set< size_t > us_edge