OILS / vendor / souffle / datastructure / Graph.h View on Github | oilshell.org

595 lines, 354 significant
1/*
2 * Souffle - A Datalog Compiler
3 * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved
4 * Licensed under the Universal Permissive License v 1.0 as shown at:
5 * - https://opensource.org/licenses/UPL
6 * - <souffle root>/licenses/SOUFFLE-UPL.txt
7 */
8
9/************************************************************************
10 *
11 * @file Graph.h
12 *
13 * A basic digraph with edge labels.
14 *
15 ***********************************************************************/
16
17#pragma once
18
19#include "souffle/utility/ContainerUtil.h"
20#include "souffle/utility/Types.h"
21#include <deque>
22#include <functional>
23#include <map>
24#include <ostream>
25#include <set>
26#include <vector>
27#ifndef NDEBUG
28#include <algorithm>
29#endif
30
31namespace souffle {
32
33namespace detail {
34
35struct GraphUnitEdgeSet {};
36
37} // namespace detail
38
39/**
40 * A simple graph structure for graph-based operations.
41 */
42template <typename Vertex, typename EdgeLabel = Unit, typename CompareVertex = std::less<Vertex>,
43 typename CompareLabel = std::less<EdgeLabel>, bool UnitEdgeLabels = std::is_same_v<Unit, EdgeLabel>>
44class GraphLabeled {
45 // HACK: helper to avoid pulling in `Util.h` for `souffle::contains`
46 template <typename C, typename E = typename C::element_type>
47 static bool containsValue(const C& xs, const E& x) {
48 return xs.find(x) != xs.end();
49 }
50
51public:
52 using self_t = GraphLabeled<Vertex, EdgeLabel, CompareVertex, CompareLabel>;
53 using VertexSet = std::set<Vertex, CompareVertex>;
54 using EdgeLabelSet = std::set<EdgeLabel, CompareLabel>;
55 enum class Visit { pre, post };
56
57 /**
58 * Adds a new edge from the given vertex to the target vertex.
59 */
60 template <typename = std::enable_if_t<UnitEdgeLabels>>
61 bool insert(const Vertex& from, const Vertex& to) {
62 return insert(from, to, EdgeLabelSet{});
63 }
64
65 bool insert(const Vertex& from, const Vertex& to, EdgeLabel label) {
66 return insert(from, to, EdgeLabelSet{std::move(label)});
67 }
68
69 bool insert(const Vertex& from, const Vertex& to, EdgeLabelSet labels) {
70 insert(from);
71 insert(to);
72 bool inserted = _successors[from].insert(to).second;
73 _predecessors[to].insert(from);
74 if constexpr (!UnitEdgeLabels) {
75 auto& labels_store = _labels[{from, to}]; // force creation of entry
76 if (labels_store.empty() && !labels.empty()) {
77 labels_store = std::move(labels);
78 inserted = true;
79 } else {
80 for (auto&& l : labels)
81 inserted |= labels_store.insert(l).second;
82 }
83 }
84 return inserted;
85 }
86
87 /**
88 * Adds a vertex.
89 */
90 bool insert(const Vertex& vertex) {
91 return _vertices.insert(vertex).second;
92 }
93
94 bool insert(const self_t& g) {
95 bool changed = false;
96 auto combineSet = [&](auto& dst, auto&& src) {
97 for (auto&& x : src)
98 changed |= dst.insert(x).second;
99 };
100 auto combineMap = [&](auto& dst, auto&& src) {
101 for (auto&& [v, xs] : src) {
102 auto [it, added] = dst.insert({v, {}});
103 changed |= added;
104 combineSet(it->second, xs);
105 }
106 };
107
108 combineSet(_vertices, g._vertices);
109 combineMap(_successors, g._successors);
110 combineMap(_predecessors, g._predecessors);
111 if constexpr (!UnitEdgeLabels) {
112 combineMap(_labels, g._labels);
113 }
114
115 return changed;
116 }
117
118 bool remove(const Vertex& vertex) {
119 if (_vertices.erase(vertex) == 0) return false;
120
121 if constexpr (!UnitEdgeLabels) {
122 for (auto&& m : successors(vertex))
123 _labels.erase({vertex, m});
124 }
125
126 _successors.erase(vertex);
127 _predecessors.erase(vertex);
128 return true;
129 }
130
131 size_t remove(const Vertex& src, const Vertex& dst) {
132 auto it = _successors.find(src);
133 if (it == _successors.end()) return 0;
134 if (it->second.erase(dst) == 0) return 0;
135
136 if (it->second.empty()) _successors.erase(it);
137 removeExistingAdjEdge(_predecessors, dst, src);
138
139 if constexpr (UnitEdgeLabels) {
140 return 1;
141 } else {
142 auto it = _labels.find({src, dst});
143 assert(it != _labels.end());
144 assert(!it->second.empty());
145 auto n = it->second.size();
146 _labels.erase(it);
147 return n;
148 }
149 }
150
151 bool remove(const Vertex& src, const Vertex& dst, const EdgeLabel& label) {
152 if constexpr (UnitEdgeLabels) {
153 return 0 < remove(src, dst);
154 } else {
155 auto it = _labels.find({src, dst});
156 if (it == _labels.end()) return false;
157 if (it->second.erase(label) == 0) return false;
158
159 // all edges erased -> remove from succ/pred
160 if (it->second.empty()) {
161 _labels.erase(it);
162 removeExistingAdjEdge(_predecessors, dst, src);
163 removeExistingAdjEdge(_successors, src, dst);
164 }
165
166 return true;
167 }
168 }
169
170 size_t removeEdgesPred(const Vertex& node) {
171 return removeEdgesIncidentTo(_predecessors, _successors, node,
172 [](auto node, auto adj) { return std::make_pair(adj, node); });
173 }
174
175 size_t removeEdgesSucc(const Vertex& node) {
176 return removeEdgesIncidentTo(_successors, _predecessors, node,
177 [](auto node, auto adj) { return std::make_pair(node, adj); });
178 }
179
180 /** Obtains a reference to the set of all vertices */
181 const VertexSet& vertices() const {
182 return _vertices;
183 }
184
185 // set of vertices which appear as the source of an edge
186 // i.e. { a | (a, b) \in E }
187 VertexSet edgeSources() const {
188 return keysOfAdjacency(_successors);
189 }
190
191 // set of vertices which appear as the target of an edge
192 // i.e. { b | (a, b) \in E }
193 VertexSet edgeTargets() const {
194 return keysOfAdjacency(_predecessors);
195 }
196
197 /** Returns the set of vertices the given vertex has edges to */
198 const VertexSet& successors(const Vertex& from) const {
199 auto it = _successors.find(from);
200 return it == _successors.end() ? null : it->second;
201 }
202
203 /** Returns the set of vertices the given vertex has edges from */
204 const VertexSet& predecessors(const Vertex& to) const {
205 auto it = _predecessors.find(to);
206 return it == _predecessors.end() ? null : it->second;
207 }
208
209 /** Obtains the union of sources and nodes transitively reachable via the sources' successors */
210 template <typename C>
211 VertexSet reachableFromSucc(C&& sources) const {
212 return reachableFrom(std::forward<C>(sources), std::mem_fn(&GraphLabeled::successors));
213 }
214
215 /** Obtains the union of sources and nodes transitively reachable via the sources' predecessors */
216 template <typename C>
217 VertexSet reachableFromPred(C&& sources) const {
218 return reachableFrom(std::forward<C>(sources), std::mem_fn(&GraphLabeled::predecessors));
219 }
220
221 VertexSet reachableFromSucc(Vertex src) const {
222 return reachableFromSucc(VertexSet{std::move(src)});
223 }
224
225 VertexSet reachableFromPred(Vertex src) const {
226 return reachableFromPred(VertexSet{std::move(src)});
227 }
228
229 template <typename Edges>
230 VertexSet reachableFrom(Vertex src, Edges&& edges) const {
231 return reachableFrom(VertexSet{std::move(src)}, std::forward<Edges>(edges));
232 }
233
234 template <typename Edges>
235 VertexSet reachableFrom(VertexSet&& sources, Edges&& edges) const {
236 std::vector<Vertex> pending{sources.begin(), sources.end()};
237 return reachableFrom(std::move(sources), std::move(pending), std::forward<Edges>(edges));
238 }
239
240 template <typename C, typename Edges>
241 VertexSet reachableFrom(C&& srcs, Edges&& edges) const {
242 // nitpick: build `pending` from `srcs` to preserve whatever iteration order is imposed by `srcs`
243 std::vector<Vertex> pending{srcs.begin(), srcs.end()};
244 return reachableFrom({srcs.begin(), srcs.end()}, std::move(pending), std::forward<Edges>(edges));
245 }
246
247 const EdgeLabelSet& labels(const Vertex& from, const Vertex& to) const {
248 if constexpr (UnitEdgeLabels) {
249 if (contains(from, to)) return _labels.unit;
250 } else {
251 auto it = _labels.find({from, to});
252 if (it != _labels.end()) return it->second;
253 }
254
255 return nullLabel;
256 }
257
258 /** Determines whether the given vertex is present */
259 bool contains(const Vertex& vertex) const {
260 return _vertices.find(vertex) != _vertices.end();
261 }
262
263 /** Determines whether the given edge is present */
264 bool contains(const Vertex& from, const Vertex& to) const {
265 auto pos = _successors.find(from);
266 if (pos == _successors.end()) {
267 return false;
268 }
269 auto p2 = pos->second.find(to);
270 return p2 != pos->second.end();
271 }
272
273 bool contains(const Vertex& from, const Vertex& to, const EdgeLabel& label) const {
274 if constexpr (UnitEdgeLabels) {
275 return contains(from, to);
276 } else {
277 auto it = _labels.find({from, to});
278 return it != _labels.end() && containsValue(it->second, label);
279 }
280 }
281
282 /** Determines whether there is a directed path between the two vertices */
283 bool reaches(const Vertex& from, const Vertex& to) const {
284 // quick check
285 if (!contains(from) || !contains(to)) {
286 return false;
287 }
288
289 // conduct a depth-first search starting at from
290 bool found = false;
291 bool first = true;
292 visit(from, [&](const Vertex& cur) {
293 found = !first && (found || cur == to);
294 first = false;
295 });
296 return found;
297 }
298
299 /** Obtains the set of all vertices in the same clique than the given vertex */
300 VertexSet clique(const Vertex& vertex) const {
301 VertexSet res;
302 res.insert(vertex);
303 for (const auto& cur : vertices()) {
304 if (reaches(vertex, cur) && reaches(cur, vertex)) {
305 res.insert(cur);
306 }
307 }
308 return res;
309 }
310
311 template <typename C>
312 self_t induced(const C& nodes) const {
313 VertexSet xs;
314 for (auto&& n : nodes)
315 if (containsValue(_vertices, n)) xs.insert(n);
316
317 return inducedImpl(std::move(xs));
318 }
319
320 self_t inducedReachableDijkstra(const Vertex& source, const std::set<Vertex>& sinks) const {
321 // `sink_depths` seperate from `depths` to handle case where `source \in sinks`,
322 // otherwise we'd see a sink as having depth 0 on the backwards pass.
323 std::map<Vertex, size_t, CompareVertex> sink_depths;
324 std::map<Vertex, size_t, CompareVertex> depths;
325 std::deque<Vertex> pending;
326
327 auto update = [&](const Vertex& x, size_t depth) {
328 auto it = depths.find(x);
329 if (it != depths.end() && it->second <= depth) return false;
330
331 depths[x] = depth;
332 pending.push_back(x);
333 return true;
334 };
335
336 update(source, 0);
337
338 while (!pending.empty() && sink_depths.size() < sinks.size()) {
339 auto x = pending.front();
340 pending.pop_front();
341
342 auto depth = depths.at(x);
343 for (auto&& succ : successors(x)) {
344 if (souffle::contains(sinks, succ)) {
345 sink_depths.insert({succ, depth + 1});
346 // Keep walking.
347 // 1) The shortest path 'tween a src-sink pair could pass through a different sink.
348 // 2) We want all shortest paths, not just one shortest path.
349 // e.g. ```
350 // a --> b --> d -> e
351 // \-> c -/
352 // ```
353 // Source : `a`
354 // Sinks : `{d, e}`
355 }
356
357 update(succ, depth + 1);
358 }
359 }
360
361 self_t induced;
362 std::vector<std::pair<Vertex, size_t>> rev_pending(sink_depths.begin(), sink_depths.end());
363 while (!rev_pending.empty()) {
364 auto [x, depth] = rev_pending.back();
365 rev_pending.pop_back();
366 assert(1 <= depth && "rev-walk should terminate at source");
367
368 for (auto&& pred : predecessors(x)) {
369 auto it = depths.find(pred);
370 if (it == depths.end() || depth <= it->second) continue; // edge not in shortest-paths
371 assert(it->second == depth - 1 && "shorter path found on reverse walk?");
372 assert(((1 == depth) == (pred == source)) && "depth-1 vertices should only link to `source`");
373
374 // 1) not already in `rev_pending` (implied by `x \in induced`)
375 // 2) not the source
376 if (!induced.contains(pred) && 1 < depth) {
377 rev_pending.push_back({pred, depth - 1});
378 }
379
380 induced.insert(pred, x, labels(pred, x));
381 }
382 }
383
384 return induced;
385 }
386
387 /** A generic utility for depth-first pre-order visits */
388 template <typename Lambda>
389 void visit(const Vertex& vertex, const Lambda& lambda) const {
390 visitDepthFirst(vertex, [&](Visit e, auto&& v) {
391 if (e == Visit::pre) lambda(v);
392 });
393 }
394
395 /** A generic utility for depth-first visits */
396 template <typename Lambda>
397 void visitDepthFirst(const Vertex& vertex, const Lambda& lambda) const {
398 VertexSet visited;
399 visitDepthFirst(vertex, lambda, std::mem_fn(&GraphLabeled::successors), visited);
400 }
401
402 // if there are multiple sources then pretend there is a pseudo-source node with edge to each
403 template <typename Lambda>
404 void visitDepthFirstSources(const Lambda& lambda) const {
405 // pseudo node can be impl by iterating over each source w/ a shared `visited`
406 VertexSet visited;
407 for (auto&& v : vertices())
408 if (predecessors(v).empty()) {
409 visitDepthFirst(v, lambda, std::mem_fn(&GraphLabeled::successors), visited);
410 }
411 }
412
413 /** Enables graphs to be printed (e.g. for debugging) */
414 void print(std::ostream& os) const {
415 bool first = true;
416 os << "{";
417 for (auto&& curr : vertices()) {
418 auto&& succs = successors(curr);
419 for (auto&& succ : succs) {
420 if (!first) os << "\n,";
421 first = false;
422
423 os << curr << "->" << succ;
424 if constexpr (!UnitEdgeLabels) {
425 os << " [" << join(labels(curr, succ)) << "]";
426 }
427 }
428
429 // handle case where node is disconnected
430 if (succs.empty() && predecessors(curr).empty()) {
431 if (!first) os << "\n,";
432 first = false;
433 os << curr;
434 }
435 }
436 os << "}";
437 }
438
439 friend std::ostream& operator<<(std::ostream& out, const self_t& g) {
440 g.print(out);
441 return out;
442 }
443
444 void DBG_sanCheck() const {
445#ifndef NDEBUG
446 auto checkPeer = [&](auto&& src, auto&& peer) {
447 for (auto&& [n, ms] : src) {
448 assert(!ms.empty() && "shouldn't store empty sets");
449 assert(containsValue(_vertices, n));
450 for (auto&& m : ms) {
451 assert(containsValue(_vertices, m));
452 assert(containsValue(peer, m));
453 assert(containsValue(peer.at(m), n));
454 }
455 }
456 };
457
458 checkPeer(_successors, _predecessors);
459 checkPeer(_predecessors, _successors);
460
461 if constexpr (!UnitEdgeLabels) {
462 for (auto&& [kv, labels] : _labels) {
463 auto&& [n, m] = kv;
464 assert(!labels.empty() && "shouldn't store an empty label set");
465 assert(containsValue(_successors.at(n), m));
466 }
467 }
468#endif
469 }
470
471private:
472 using Vertex2Peer = std::map<Vertex, VertexSet, CompareVertex>;
473 using EdgeVerts = std::pair<Vertex, Vertex>;
474 using Edge2Labels =
475 std::conditional_t<UnitEdgeLabels, detail::GraphUnitEdgeSet, std::map<EdgeVerts, EdgeLabelSet>>;
476
477 // not a very efficient but simple graph representation
478 VertexSet null; // the empty set
479 EdgeLabelSet nullLabel; // the empty set
480 VertexSet _vertices; // all the vertices in the graph
481 Vertex2Peer _successors; // all edges forward directed
482 Vertex2Peer _predecessors; // all edges backward
483 Edge2Labels _labels;
484
485 /** The internal implementation of depth-first visits */
486 template <typename Fn, typename Edges>
487 void visitDepthFirst(const Vertex& v, const Fn& lambda, const Edges& edges, VertexSet& visited) const {
488 lambda(Visit::pre, v);
489
490 for (auto&& next : edges(*this, v))
491 if (visited.insert(next).second) {
492 visitDepthFirst(next, lambda, edges, visited);
493 }
494
495 lambda(Visit::post, v);
496 }
497
498 template <typename Edges>
499 VertexSet reachableFrom(VertexSet visited, std::vector<Vertex> pending, Edges&& edges) const {
500 assert(visited.size() == pending.size());
501 assert(all_of(pending, [&](auto&& x) { return souffle::contains(visited, x); }));
502
503 while (!pending.empty()) {
504 auto curr = pending.back();
505 pending.pop_back();
506 for (auto&& next : edges(*this, curr))
507 if (visited.insert(next).second) {
508 pending.push_back(next);
509 }
510 }
511
512 return visited;
513 }
514
515 template <typename EdgePair>
516 size_t removeEdgesIncidentTo(Vertex2Peer& fwd, Vertex2Peer& rev, const Vertex& node, EdgePair&& edge) {
517 auto it = fwd.find(node);
518 if (it == fwd.end()) return 0;
519
520 size_t n_edges = 0;
521 if constexpr (UnitEdgeLabels) {
522 n_edges = it->second.size();
523 }
524
525 for (auto&& adj : it->second) {
526 removeExistingAdjEdge(rev, adj, node);
527
528 if constexpr (!UnitEdgeLabels) {
529 auto it_label = _labels.find(edge(node, adj));
530 n_edges += it_label->second.size();
531 _labels.erase(it_label);
532 }
533 }
534
535 fwd.erase(it);
536 return n_edges;
537 }
538
539 self_t inducedImpl(VertexSet nodes) const {
540 DBG_sanCheck();
541 assert(std::includes(_vertices.begin(), _vertices.end(), nodes.begin(), nodes.end()) &&
542 "`nodes` must be a subset of `_vertices`");
543
544 self_t induced;
545 induced._vertices = std::move(nodes);
546
547 for (auto&& n : induced._vertices) {
548 auto addEdges = [&](auto&& dst_map, auto&& src) {
549 if (src.empty()) return;
550
551 auto&& [it, _] = dst_map.insert({n, {}});
552 auto& dst = it->second;
553 bool added_anything = false;
554 for (auto&& m : src)
555 if (containsValue(induced._vertices, m)) {
556 added_anything = true;
557 dst.insert(m);
558 }
559
560 if (!added_anything) dst_map.erase(it);
561 };
562
563 addEdges(induced._successors, successors(n));
564 addEdges(induced._predecessors, predecessors(n));
565
566 if constexpr (!UnitEdgeLabels) {
567 for (auto&& m : induced.successors(n))
568 induced._labels[{n, m}] = labels(n, m);
569 }
570 }
571
572 induced.DBG_sanCheck();
573 return induced;
574 }
575
576 static void removeExistingAdjEdge(Vertex2Peer& adj, const Vertex& a, const Vertex& b) {
577 auto it = adj.find(a);
578 assert(it != adj.end());
579 [[maybe_unused]] auto n = it->second.erase(b);
580 assert(n == 1);
581 if (it->second.empty()) adj.erase(it);
582 }
583
584 static VertexSet keysOfAdjacency(const Vertex2Peer& adj) {
585 VertexSet xs;
586 for (auto&& [k, _] : adj)
587 xs.insert(k);
588 return xs;
589 }
590};
591
592template <typename Vertex, typename Compare = std::less<Vertex>>
593using Graph = GraphLabeled<Vertex, Unit, Compare>;
594
595} // end of namespace souffle