Sequoia
Loading...
Searching...
No Matches
GraphTraversalDetails.hpp
Go to the documentation of this file.
1
2// Copyright Oliver J. Rosten 2018. //
3// Distributed under the GNU GENERAL PUBLIC LICENSE, Version 3.0. //
4// (See accompanying file LICENSE.md or copy at //
5// https://www.gnu.org/licenses/gpl-3.0.en.html) //
7
8#pragma once
9
18
19#include <vector>
20
21namespace sequoia::maths
22{
24 {
25 template<class T>
26 void operator()(T&&);
27 };
28
29 enum class disconnected_discovery_mode { on, off };
30
31 enum class traversal_flavour { breadth_first, depth_first, pseudo_depth_first, priority };
32
33 template<traversal_flavour F>
34 struct traversal_constant : std::integral_constant<traversal_flavour, F> {};
35
40
41 constexpr breadth_first_search_type breadth_first{};
42 constexpr depth_first_search_type depth_first{};
43 constexpr pseudo_depth_first_search_type pseudo_depth_first{};
44 constexpr priority_first_search_type priority_first{};
45
47 {
48 public:
49 constexpr traversal_conditions_base() = default;
50
51 constexpr explicit traversal_conditions_base(std::size_t startingIndex)
52 : m_Index{startingIndex}
53 {}
54
55 [[nodiscard]]
56 constexpr std::size_t starting_index() const noexcept
57 {
58 return m_Index;
59 }
60 protected:
61 constexpr traversal_conditions_base(const traversal_conditions_base&) = default;
62
63 constexpr traversal_conditions_base& operator=(const traversal_conditions_base&) = default;
64
66 private:
67 std::size_t m_Index{};
68 };
69
70 template<disconnected_discovery_mode FindDisconnected>
72 {
73 public:
74 constexpr static disconnected_discovery_mode mode{FindDisconnected};
75
76 using traversal_conditions_base::traversal_conditions_base;
77
78 constexpr traversal_conditions(const traversal_conditions&) = default;
79 constexpr traversal_conditions& operator=(const traversal_conditions&) = default;
80
81 template<class Bitset>
82 [[nodiscard]]
83 constexpr std::size_t compute_restart_index(const Bitset& b)
84 {
85 if(m_NumDiscovered)
86 {
87 while(b.size() && b[m_Restart]) ++m_Restart;
88
89 return m_Restart;
90 }
91
92 return starting_index();
93 }
94
95 template<class Bitset>
96 constexpr void register_discovered(Bitset& b, const std::size_t index)
97 {
98 b[index] = true;
99 ++m_NumDiscovered;
100 }
101
102 [[nodiscard]]
103 constexpr bool terminate(const std::size_t order) const noexcept
104 {
105 return m_NumDiscovered == order;
106 }
107 private:
108 std::size_t m_NumDiscovered{}, m_Restart{};
109 };
110
111 template<>
112 class traversal_conditions<disconnected_discovery_mode::off> : public traversal_conditions_base
113 {
114 public:
115 constexpr static disconnected_discovery_mode mode{disconnected_discovery_mode::off};
116
117 using traversal_conditions_base::traversal_conditions_base;
118
119 constexpr traversal_conditions(const traversal_conditions&) = default;
120 constexpr traversal_conditions& operator=(const traversal_conditions&) = default;
121
122 template<class Bitset>
123 constexpr std::size_t compute_restart_index(const Bitset&) const noexcept
124 {
125 return starting_index();
126 }
127
128 [[nodiscard]]
129 constexpr bool terminate(std::size_t) const noexcept
130 {
131 return true;
132 }
133
134 template<class Bitset>
135 constexpr void register_discovered(Bitset& b, const std::size_t index)
136 {
137 b[index] = true;
138 }
139 };
140
142
144
145 template<class ConcurrencyModel>
147 {
148 public:
149 using model_type = ConcurrencyModel;
150 using return_type = typename model_type::return_type;
151
152 explicit results_accumulator(ConcurrencyModel& model)
153 : m_Model{&model}
154 {}
155
156 template<class Fn, class... Args>
157 requires std::invocable<Fn, Args...>&& std::is_convertible_v<std::invoke_result_t<Fn, Args...>, return_type>
158 void push(Fn fn, Args&&... args)
159 {
160 m_Futures.emplace_back(m_Model->push(std::move(fn), std::forward<Args>(args)...));
161 }
162
163 [[nodiscard]]
164 std::vector<std::future<return_type>> extract_results() noexcept(noexcept(std::is_nothrow_move_constructible_v<return_type>)) { return std::move(m_Futures); }
165 private:
166 std::vector<std::future<return_type>> m_Futures;
167 ConcurrencyModel* m_Model;
168 };
169
170 template<>
171 class results_accumulator<concurrency::serial<void>>
172 {
173 public:
174 using return_type = void;
175
176 constexpr explicit results_accumulator(const concurrency::serial<void>&) {}
177
178 template<class Fn, class... Args>
179 requires std::invocable<Fn, Args...>
180 constexpr void push(Fn&& fn, Args&&... args)
181 {
182 concurrency::serial<void>{}.push(std::forward<Fn>(fn), std::forward<Args>(args)...);
183 }
184
185 constexpr void extract_results() const noexcept {}
186 };
187
188 template<class R>
189 class results_accumulator<concurrency::serial<R>>
190 {
191 public:
192 using return_type = R;
193
195
196 template<class Fn, class... Args>
197 requires std::invocable<Fn, Args...>&& std::is_convertible_v<std::invoke_result_t<Fn, Args...>, R>
198 void push(Fn fn, Args&&... args)
199 {
200 m_Results.emplace_back(concurrency::serial<R>{}.push(std::forward<Fn>(fn), std::forward<Args>(args)...));
201 }
202
203 [[nodiscard]]
204 std::vector<R> extract_results() noexcept(noexcept(std::is_nothrow_move_constructible_v<R>)) { return std::move(m_Results); }
205 private:
206 std::vector<R> m_Results;
207 };
208}
209
210namespace sequoia::maths::graph_impl
211{
212 template<traversal_flavour F> struct iterator_getter
213 {
214 template<network G>
215 [[nodiscard]]
216 constexpr static auto begin(const G& graph, const typename G::edge_index_type nodeIndex) { return graph.cbegin_edges(nodeIndex); }
217
218 template<network G>
219 [[nodiscard]]
220 constexpr static auto end(const G& graph, const typename G::edge_index_type nodeIndex) { return graph.cend_edges(nodeIndex); }
221 };
222
223 template<> struct iterator_getter<traversal_flavour::pseudo_depth_first>
224 {
225 template<network G>
226 [[nodiscard]]
227 constexpr static auto begin(const G& graph, const typename G::edge_index_type nodeIndex) { return graph.crbegin_edges(nodeIndex); }
228
229 template<network G>
230 [[nodiscard]]
231 constexpr static auto end(const G& graph, const typename G::edge_index_type nodeIndex) { return graph.crend_edges(nodeIndex); }
232 };
233
234
235 template<network G, traversal_flavour F, class... QArgs>
237
238 template<network G, traversal_flavour F, class... QArgs>
240 {
241 using queue_type = typename traversal_traits_base<G, F, QArgs...>::queue_type;
242 using edge_index_type = typename G::edge_index_type;
243
244 [[nodiscard]]
245 constexpr static queue_type make(QArgs... args)
246 {
247 return queue_type{std::forward<QArgs>(args)...};
248 }
249
250 [[nodiscard]]
251 constexpr static auto begin(const G& graph, const edge_index_type nodeIndex)
252 {
253 return iterator_getter<F>::begin(graph, nodeIndex);
254 }
255
256 [[nodiscard]]
257 constexpr static auto end(const G& graph, const edge_index_type nodeIndex)
258 {
259 return iterator_getter<F>::end(graph, nodeIndex);
260 }
261 };
262
263 template<network G> struct traversal_tracking_traits;
264
265 template<network G, class Compare>
267 {
268 public:
269 using compare_type = Compare;
270
271 constexpr node_comparer(const G& g) : m_Graph(g) {}
272 constexpr node_comparer(const node_comparer&) = default;
273
274 [[nodiscard]]
275 constexpr bool operator()(const std::size_t index1, const std::size_t index2)
276 {
277 return Compare{}(*(m_Graph.cbegin_node_weights() + index1), *(m_Graph.cbegin_node_weights() + index2));
278 }
279 private:
280 const G& m_Graph;
281 };
282
283 template<network G, graph_flavour GraphFlavour=G::flavour>
285 {
286 public:
287 template<std::forward_iterator Iter>
288 [[nodiscard]]
289 constexpr static bool loop_matched(Iter begin, Iter current)
290 {
291 for(auto i{begin}; i != current; ++i)
292 {
293 if(&(*i) == &(*current)) return true;
294 }
295
296 return false;
297 }
298 };
299
300 template<network G, graph_flavour GraphFlavour>
301 requires requires() {
302 std::declval<typename G::edge_type>().complementary_index();
303 }
304 class loop_processor<G, GraphFlavour>
305 {
306 public:
307 template<std::forward_iterator Iter>
308 [[nodiscard]]
309 constexpr static bool loop_matched(Iter begin, Iter current)
310 {
311 using index_type = typename G::edge_index_type;
312 const auto dist{static_cast<index_type>(std::ranges::distance(begin, current))};
313 return (current->complementary_index() < dist);
314 }
315 };
316
317 template<network G>
318 class loop_processor<G, graph_flavour::undirected>
319 {
320 public:
321 template<class Iter>
322 [[nodiscard]]
323 constexpr bool loop_matched(Iter, Iter) noexcept
324 {
325 m_Matched = !m_Matched;
326 return m_Matched;
327 }
328 private:
329 bool m_Matched{true};
330 };
331
332 template<network G>
334 {
335 public:
336 using edge_index_type = typename G::edge_index_type;
337 using const_edge_iterator = typename G::const_edge_iterator;
338
339 template
340 <
341 traversal_flavour F,
342 disconnected_discovery_mode FindDisconnected,
343 class NBEF,
344 class NAEF,
345 class EFTF,
346 class ESTF,
347 class TaskProcessingModel,
348 class... QArgs
349 >
350 requires (std::invocable<NBEF, edge_index_type> )
351 && (std::invocable<NAEF, edge_index_type> )
352 && (std::invocable<EFTF, const_edge_iterator>)
353 && (std::invocable<ESTF, const_edge_iterator>)
354 constexpr auto traverse(traversal_constant<F>,
355 const G& graph,
357 NBEF&& nodeBeforeEdgesFn,
358 NAEF&& nodeAfterEdgesFn,
359 EFTF&& edgeFirstTraversalFn,
360 ESTF&& edgeSecondTraversalFn,
361 TaskProcessingModel&& taskProcessingModel,
362 QArgs&&... qargs)
363 {
364 // Note: do not forward any of the Fns as they could in principle end up repeatedly moved from.
365 // However, the Fns should not be captured by value as they may have mutable state with
366 // external visibility.
367
368 constexpr bool hasEdgeSecondFn{!std::same_as<std::remove_cvref_t<ESTF>, null_func_obj>};
369 static_assert(!is_directed(G::flavour) || !hasEdgeSecondFn,
370 "For a directed graph, edges are traversed only once: the edgeSecondTraversalFn is ignored and so should be the null_func_obj");
371
372 results_accumulator resultsAccumulator{taskProcessingModel};
373 if(conditions.starting_index() < graph.order())
374 {
375 auto discovered{traversal_tracking_traits<G>::make_bitset(graph)};
376 auto processed{traversal_tracking_traits<G>::make_bitset(graph)};
377
378 auto nodeIndexQueue{traversal_traits<G, F, QArgs...>::make(std::forward<QArgs>(qargs)...)};
379
380 do
381 {
382 const auto restartNode{static_cast<edge_index_type>(conditions.compute_restart_index(discovered))};
383
384 nodeIndexQueue.push(restartNode);
385 conditions.register_discovered(discovered, restartNode);
386
387 while(!nodeIndexQueue.empty())
388 {
389 const auto nodeIndex{traversal_traits<G, F, QArgs...>::get_container_element(nodeIndexQueue)};
390 nodeIndexQueue.pop();
391
392 auto onDiscovery{[&nodeIndexQueue](const edge_index_type nextNode) { nodeIndexQueue.push(nextNode); }};
393
394 inner_loop(graph,
395 nodeIndex,
396 conditions,
399 discovered,
400 processed,
401 onDiscovery,
402 nodeBeforeEdgesFn,
403 nodeAfterEdgesFn,
404 edgeFirstTraversalFn,
405 edgeSecondTraversalFn,
406 resultsAccumulator);
407 }
408 } while(!conditions.terminate(graph.order()));
409 }
410
411 return resultsAccumulator.extract_results();
412 }
413
414 template
415 <
416 disconnected_discovery_mode FindDisconnected,
417 class NBEF,
418 class NAEF,
419 class ETUN,
420 class TaskProcessingModel
421 >
422 requires (std::invocable<NBEF, edge_index_type>)
423 && (std::invocable<NAEF, edge_index_type>)
424 && (std::invocable<ETUN, typename G::const_edge_iterator>)
425 constexpr auto traverse(depth_first_search_type,
426 const G& graph,
428 NBEF&& nodeBeforeEdgesFn,
429 NAEF&& nodeAfterEdgesFn,
430 ETUN&& edgeToUndiscoveredNodeFn,
431 TaskProcessingModel&& taskProcessingModel)
432 {
433 // Note: do not forward any of the Fns as they could in principle end up repeatedly moved from.
434 // However, the Fns should not be captured by value as they may have mutable state with
435 // external visibility.
436
437 results_accumulator resultsAccumulator{taskProcessingModel};
438 if(conditions.starting_index() < graph.order())
439 {
440 auto discovered{traversal_tracking_traits<G>::make_bitset(graph)};
441 auto processed{traversal_tracking_traits<G>::make_bitset(graph)};
442
443 do
444 {
445 const auto restartNode{static_cast<edge_index_type>(conditions.compute_restart_index(discovered))};
446 conditions.register_discovered(discovered, restartNode);
447
448 inner_loop(graph,
449 restartNode,
450 conditions,
451 graph.cbegin_edges(restartNode),
452 graph.cend_edges(restartNode),
453 discovered,
454 processed,
455 recurse{},
456 nodeBeforeEdgesFn,
457 nodeAfterEdgesFn,
458 edgeToUndiscoveredNodeFn,
460 resultsAccumulator);
461
462 } while(!conditions.terminate(graph.order()));
463 }
464
465 return resultsAccumulator.extract_results();
466 }
467 private:
468 struct recurse {};
469
470 template<std::input_or_output_iterator Iter>
471 [[nodiscard]]
472 constexpr static bool is_loop(Iter iter, [[maybe_unused]] const edge_index_type currentNodeIndex)
473 {
474 return iter->target_node() == currentNodeIndex;
475 }
476
477 template
478 <
479 disconnected_discovery_mode FindDisconnected,
480 class Iter,
481 class Bitset,
482 class OnDiscovery,
483 class NBEF,
484 class NAEF,
485 class EFTF,
486 class ESTF,
487 class TaskProcessingModel
488 >
489 requires (std::invocable<NBEF, edge_index_type>)
490 && (std::invocable<NAEF, edge_index_type>)
491 && (std::invocable<EFTF, const_edge_iterator>)
492 && (std::invocable<ESTF, const_edge_iterator>)
493 && (std::invocable<OnDiscovery, edge_index_type> || std::same_as<OnDiscovery, recurse>)
494 constexpr void inner_loop([[maybe_unused]] const G& graph,
495 const edge_index_type nodeIndex,
497 Iter begin,
498 Iter end,
499 Bitset& discovered,
500 Bitset& processed,
501 OnDiscovery onDiscovery,
502 NBEF&& nodeBeforeEdgesFn,
503 NAEF&& nodeAfterEdgesFn,
504 EFTF&& edgeFirstTraversalFn,
505 ESTF&& edgeSecondTraversalFn,
507 {
508 constexpr bool hasNodeBeforeFn{!std::same_as<std::remove_cvref_t<NBEF>, null_func_obj>};
509 if constexpr(hasNodeBeforeFn)
510 {
511 resultsAccumulator.push(nodeBeforeEdgesFn, nodeIndex);
512 }
513
514 [[maybe_unused]] loop_processor<G> loops{};
515 for(auto iter{begin}; iter != end; ++iter)
516 {
517 const auto nextNode{iter->target_node()};
518 constexpr bool hasEdgeFirstFn{!std::same_as<std::remove_cvref_t<EFTF>, null_func_obj>};
519 constexpr bool isDFS{std::same_as<OnDiscovery, recurse>};
520
521 if constexpr(isDFS)
522 {
523 if constexpr(hasEdgeFirstFn)
524 {
525 if(!discovered[nextNode]) resultsAccumulator.push(edgeFirstTraversalFn, iter);
526 }
527 }
528 else if constexpr(G::flavour != graph_flavour::directed)
529 {
530 constexpr bool hasEdgeSecondFn{!std::same_as<std::remove_cvref_t<ESTF>, null_func_obj>};
531 if constexpr(hasEdgeFirstFn || hasEdgeSecondFn)
532 {
533 const bool loopMatched{is_loop(iter, nodeIndex) && loops.loop_matched(begin, iter)};
534 const bool secondTraversal{processed[nextNode] || loopMatched};
535 if(secondTraversal)
536 {
537 if constexpr(hasEdgeSecondFn)
538 {
539 resultsAccumulator.push(edgeSecondTraversalFn, iter);
540 }
541 }
542 else
543 {
544 if constexpr(hasEdgeFirstFn)
545 {
546 resultsAccumulator.push(edgeFirstTraversalFn, iter);
547 }
548 }
549 }
550 }
551 else if constexpr(hasEdgeFirstFn)
552 {
553 resultsAccumulator.push(edgeFirstTraversalFn, iter);
554 }
555
556 if(!discovered[nextNode])
557 {
558 conditions.register_discovered(discovered, nextNode);
559 if constexpr(std::same_as<OnDiscovery, recurse>)
560 {
561 inner_loop(graph,
562 nextNode,
563 conditions,
564 graph.cbegin_edges(nextNode),
565 graph.cend_edges(nextNode),
566 discovered,
567 processed,
568 recurse{},
569 nodeBeforeEdgesFn,
570 nodeAfterEdgesFn,
571 edgeFirstTraversalFn,
572 edgeSecondTraversalFn,
573 resultsAccumulator);
574 }
575 else
576 {
577 onDiscovery(nextNode);
578 }
579 }
580 }
581
582 if constexpr(!std::same_as<std::remove_cvref_t<NAEF>, null_func_obj>)
583 {
584 resultsAccumulator.push(nodeAfterEdgesFn, nodeIndex);
585 }
586
587 processed[nodeIndex] = true;
588 }
589 };
590}
Classes with a queue-like behaviour to which tasks can be pushed and results recovered,...
Meta-programming elements for graph implementation.
Traits and Concepts for graphs.
Tasks may be pushed, upon which they are immediately invoked.
Definition: ConcurrencyModels.hpp:150
Definition: GraphTraversalDetails.hpp:285
Definition: GraphTraversalDetails.hpp:267
Definition: GraphTraversalDetails.hpp:334
Definition: GraphTraversalDetails.hpp:147
Definition: GraphTraversalDetails.hpp:47
Definition: GraphTraversalDetails.hpp:72
Definition: GraphTraits.hpp:18
Definition: GraphTraversalDetails.hpp:213
Definition: GraphTraversalDetails.hpp:263
Definition: GraphTraversalDetails.hpp:236
Definition: GraphTraversalDetails.hpp:240
Definition: GraphTraversalDetails.hpp:24
Definition: GraphTraversalDetails.hpp:34