Sequoia
Loading...
Searching...
No Matches
Connectivity.hpp
Go to the documentation of this file.
1
2// Copyright Oliver J. Rosten 2019. //
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
33#include "sequoia/PlatformSpecific/Preprocessor.hpp"
34
35#include <limits>
36#include <stdexcept>
37#include <ranges>
38
39namespace sequoia
40{
41 namespace data_structures
42 {
43 template <class, class, class> class partitioned_sequence;
44 }
45
46 namespace maths
47 {
48 namespace graph_impl
49 {
50 template<class EdgeType, class EdgeStorageType>
52 {
53 using edge_type = EdgeType;
54 using edge_weight_type = typename edge_type::weight_type;
55 using edge_index_type = typename edge_type::index_type;
56 using edge_storage_type = EdgeStorageType;
57 using const_edge_iterator = typename edge_storage_type::const_partition_iterator;
58 using size_type = typename edge_storage_type::size_type;
59
60 struct data
61 {
62 const edge_weight_type* m_WeightPtr{};
63 edge_index_type node_index{}, edge_index{};
64 };
65
66 edge_storage_type& m_Storage;
67 std::vector<data> m_WeightMap;
68 public:
69 explicit edge_maker(edge_storage_type& storage)
70 : m_Storage{storage}
71 {}
72
73 void operator()(const size_type node, const_edge_iterator first, const_edge_iterator i)
74 {
75 auto weightPtr{&i->weight()};
76 auto found{std::ranges::lower_bound(m_WeightMap, weightPtr, [](const edge_weight_type* lhs, const edge_weight_type* rhs) { return lhs < rhs; }, [](const data& d) { return d.m_WeightPtr; })};
77 if((found == m_WeightMap.end()) || (found->m_WeightPtr != weightPtr))
78 {
79 auto appender{[=, this](auto e){
80 m_Storage.push_back_to_partition(node, std::move(e));
81 m_WeightMap.emplace(found, &i->weight(), node, static_cast<edge_index_type>(std::ranges::distance(first, i)));
82 }
83 };
84
85 appender(edge_type{i->target_node(), i->weight()});
86 }
87 else
88 {
89 m_Storage.push_back_to_partition(node, i->target_node(), *(m_Storage.cbegin_partition(found->node_index) + found->edge_index));
90 }
91 }
92 };
93
94 template<std::input_or_output_iterator Iter>
95 class [[nodiscard]] weight_sentinel
96 {
97 public:
98 using edge_weight_type = std::remove_cvref_t<decltype(std::declval<Iter>()->weight())>;
99
100 template<class Fn1, class Fn2, class... Args1>
101 constexpr weight_sentinel(Iter i, Fn1 fn1, Fn2 fn2, Args1&&... args1)
102 : m_Iter{i}
103 , m_OldEdgeWeight{m_Iter->weight()}
104 {
105 auto partnerIter{fn1(m_Iter, std::forward<Args1>(args1)...)};
106 m_PartiallyComplete = true;
107
108 fn2(m_Iter, partnerIter);
109 m_PartiallyComplete = false;
110 }
111
112 constexpr ~weight_sentinel()
113 {
114 if(m_PartiallyComplete)
115 {
116 m_Iter->weight(std::move(m_OldEdgeWeight));
117 }
118 }
119 private:
120 bool m_PartiallyComplete{};
121 Iter m_Iter;
122 edge_weight_type m_OldEdgeWeight;
123 };
124
125 template<class Edges>
126 class [[nodiscard]] join_sentinel
127 {
128 public:
129 using edge_index_type = typename Edges::index_type;
130
131 join_sentinel(Edges& e, const edge_index_type node1, const edge_index_type pos)
132 : m_Edges{e}
133 , m_Node1{node1}
134 , m_Pos{pos}
135 , m_InitialSize{e.size()}
136 {}
137
139 {
140 if(m_Edges.size() == m_InitialSize)
141 {
142 m_Edges.erase_from_partition(m_Node1, m_Pos);
143 }
144 }
145 private:
146 Edges& m_Edges;
147 edge_index_type m_Node1{}, m_Pos{};
148 std::size_t m_InitialSize{};
149 };
150
152 {
153 template<class Edge>
154 [[nodiscard]]
155 constexpr bool operator()(const Edge& e1, const Edge& e2) const noexcept
156 {
157 using edge_weight_type = typename Edge::weight_type;
158 constexpr bool sort_weights{!std::is_empty_v<edge_weight_type> && std::totally_ordered<edge_weight_type>};
159
160 if constexpr(!sort_weights)
161 {
162 return e1.target_node() < e2.target_node();
163 }
164 else
165 {
166 return (e1.target_node() == e2.target_node()) ? e1.weight() < e2.weight() : e1.target_node() < e2.target_node();
167 }
168 }
169 };
170 }
171
173
182 template<graph_flavour Flavour, class EdgeStorage>
184 {
185 friend struct sequoia::assignment_helper;
186 public:
187 using edge_storage_type = EdgeStorage;
188
189 using edge_type = typename edge_storage_type::value_type;
190 using edge_weight_type = typename edge_type::weight_type;
191 using edge_meta_data_type = typename edge_type::meta_data_type;
192 using edge_index_type = typename edge_type::index_type;
193 using edge_init_type = edge_init_type_generator_t<edge_type>;
194 using edges_initializer = std::initializer_list<std::initializer_list<edge_init_type>>;
195
196 using size_type = typename edge_storage_type::size_type;
197 using const_edge_iterator = typename edge_storage_type::const_partition_iterator;
198 using const_reverse_edge_iterator = typename edge_storage_type::const_reverse_partition_iterator;
199 using const_edges_range = std::ranges::subrange<const_edge_iterator>;
200
201 static_assert(std::is_unsigned_v<edge_index_type>);
202
203 constexpr static auto npos{std::numeric_limits<edge_index_type>::max()};
204 constexpr static graph_flavour flavour{Flavour};
205
206 constexpr connectivity_base() = default;
207
208 constexpr connectivity_base(std::initializer_list<std::initializer_list<edge_init_type>> edges)
209 : m_Edges{make_edges(edges)}
210 {}
211
212 constexpr connectivity_base(const connectivity_base& other)
213 : m_Edges{copy_edges(other)}
214 {}
215
216 [[nodiscard]]
217 constexpr size_type order() const noexcept { return m_Edges.num_partitions(); }
218
219 [[nodiscard]]
220 constexpr size_type size() const noexcept
221 {
222 return !is_directed(flavour) ? m_Edges.size() / 2 : m_Edges.size();
223 }
224
225 [[nodiscard]]
226 constexpr const_edge_iterator cbegin_edges(const edge_index_type node) const
227 {
228 graph_errors::check_node_index_range("cbegin_edges", order(), node);
229
230 return m_Edges.cbegin_partition(node);
231 }
232
233 [[nodiscard]]
234 constexpr const_edge_iterator cend_edges(const edge_index_type node) const
235 {
236 graph_errors::check_node_index_range("cend_edges", order(), node);
237
238 return m_Edges.cend_partition(node);
239 }
240
241 [[nodiscard]]
242 constexpr const_reverse_edge_iterator crbegin_edges(const edge_index_type node) const
243 {
244 graph_errors::check_node_index_range("crbegin_edges", order(), node);
245
246 return m_Edges.crbegin_partition(node);
247 }
248
249 [[nodiscard]]
250 constexpr const_reverse_edge_iterator crend_edges(const edge_index_type node) const
251 {
252 graph_errors::check_node_index_range("crend_edges", order(), node);
253
254 return m_Edges.crend_partition(node);
255 }
256
257 [[nodiscard]]
258 constexpr const_edges_range cedges(const edge_index_type node) const
259 {
260 graph_errors::check_node_index_range("cedges", order(), node);
261
262 return {m_Edges.cbegin_partition(node), m_Edges.cend_partition(node)};
263 }
264
265 template<class... Args>
266 requires initializable_from<edge_weight_type, Args...>
267 constexpr void set_edge_weight(const_edge_iterator citer, Args&&... args)
268 {
269 if constexpr(!shared_weight_v && !is_directed(flavour))
270 {
271 auto partnerSetter{
272 [this](const_edge_iterator iter, auto&&... args){
273 return this->set_partner_edge_weight(iter, std::forward<decltype(args)>(args)...);
274 }
275 };
276
277 auto sourceSetter{
278 [this](edge_iterator iter, const_edge_iterator partnerIter){
279 this->set_source_edge_weight(iter, partnerIter->weight());
280 }
281 };
282
283 graph_impl::weight_sentinel sentinel{to_edge_iterator(citer), partnerSetter, sourceSetter, std::forward<Args>(args)...};
284 }
285 else
286 {
287 set_source_edge_weight(to_edge_iterator(citer), std::forward<Args>(args)...);
288 }
289 }
290
291 template<class... Args>
292 requires initializable_from<edge_weight_type, Args...>
293 constexpr void set_edge_weight(const_reverse_edge_iterator criter, Args&&... args)
294 {
295 set_edge_weight(to_const_edge_iterator(criter), std::forward<Args>(args)...);
296 }
297
298 template<std::invocable<edge_weight_type&> Fn>
299 requires (!std::is_empty_v<edge_weight_type>)
300 constexpr std::invoke_result_t<Fn, edge_weight_type&> mutate_edge_weight(const_edge_iterator citer, Fn fn)
301 {
302 if constexpr(!shared_weight_v && !is_directed(flavour))
303 {
304 mutate_partner_edge_weight(citer, fn);
305 }
306
307 return mutate_source_edge_weight(citer, fn);
308 }
309
310 template<std::invocable<edge_weight_type&> Fn>
311 requires (!std::is_empty_v<edge_weight_type>)
312 constexpr std::invoke_result_t<Fn, edge_weight_type&> mutate_edge_weight(const_reverse_edge_iterator criter, Fn fn)
313 {
314 return mutate_edge_weight(to_const_edge_iterator(criter), std::move(fn));
315 }
316
317 template<class... Args>
318 requires initializable_from<edge_meta_data_type, Args...>
319 constexpr void set_edge_meta_data(const_edge_iterator citer, Args&&... args)
320 {
321 to_edge_iterator(citer)->meta_data(std::forward<Args>(args)...);
322 }
323
324 template<class... Args>
325 requires initializable_from<edge_meta_data_type, Args...>
326 constexpr void set_edge_meta_data(const_reverse_edge_iterator criter, Args&&... args)
327 {
328 set_edge_meta_data(to_const_edge_iterator(criter), std::forward<Args>(args)...);
329 }
330
331
332 template<std::invocable<edge_meta_data_type&> Fn>
333 requires (!std::is_empty_v<edge_meta_data_type>)
334 constexpr std::invoke_result_t<Fn, edge_meta_data_type&> mutate_edge_meta_data(const_edge_iterator citer, Fn fn)
335 {
336 return to_edge_iterator(citer)->mutate_meta_data(std::move(fn));
337 }
338
339
340 template<std::invocable<edge_meta_data_type&> Fn>
341 requires (!std::is_empty_v<edge_meta_data_type>)
342 constexpr std::invoke_result_t<Fn, edge_meta_data_type&> mutate_edge_meta_data(const_reverse_edge_iterator criter, Fn fn)
343 {
344 return mutate_edge_meta_data(to_const_edge_iterator(criter), std::move(fn));
345 }
346
347 //===============================equality (not isomorphism) operators================================//
348
349 [[nodiscard]]
350 friend constexpr bool operator==(const connectivity_base& lhs, const connectivity_base& rhs) noexcept
352 {
353 return lhs.m_Edges == rhs.m_Edges;
354 }
355 protected:
356 using edge_iterator = typename edge_storage_type::partition_iterator;
357 using reverse_edge_iterator = typename edge_storage_type::reverse_partition_iterator;
358 using edges_range = std::ranges::subrange<edge_iterator>;
359
360 template<alloc... Allocators>
361 requires (sizeof...(Allocators) > 0)
362 constexpr connectivity_base(const Allocators&... as)
363 : m_Edges(as...)
364 {}
365
366 template<alloc... Allocators>
367 requires (sizeof...(Allocators) > 0)
368 constexpr connectivity_base(edges_initializer edges, const Allocators&... as)
369 : m_Edges{make_edges(edges, as...)}
370 {}
371
372 template<alloc... Allocators>
373 requires (sizeof...(Allocators) > 0)
374 constexpr connectivity_base(const connectivity_base& c, const Allocators&... as)
375 : m_Edges{copy_edges(c, as...)}
376 {}
377
378 constexpr connectivity_base(connectivity_base&&) noexcept = default;
379
380 template<alloc... Allocators>
381 constexpr connectivity_base(connectivity_base&& c, const Allocators&... as)
382 : m_Edges{std::move(c.m_Edges), as...}
383 {}
384
385 ~connectivity_base() = default;
386
387 constexpr connectivity_base& operator=(connectivity_base&&) = default;
388
389 constexpr connectivity_base& operator=(const connectivity_base& in)
390 {
391 if(&in != this)
392 {
393 auto allocGetter{
394 []([[maybe_unused]] const connectivity_base& in){
395 if constexpr(has_allocator_type_v<edge_storage_type>)
396 {
397 return in.m_Edges.get_allocator();
398 }
399 }
400 };
401
402 auto partitionsAllocGetter{
403 []([[maybe_unused]] const connectivity_base& in){
404 if constexpr(has_partitions_allocator<edge_storage_type>)
405 {
406 return in.m_Edges.get_partitions_allocator();
407 }
408 }
409 };
410
411 assignment_helper::assign(*this, in, allocGetter, partitionsAllocGetter);
412 }
413
414 return *this;
415 }
416
417 template<alloc... Allocs>
418 requires ((sizeof...(Allocs) > 0)
419 && (std::allocator_traits<Allocs>::propagate_on_container_copy_assignment::value && ...))
420 void reset(const Allocs&... allocs)
421 {
422 m_Edges.reset(allocs...);
423 }
424
425 void swap(connectivity_base& rhs)
426 noexcept(noexcept(std::ranges::swap(this->m_Edges, rhs.m_Edges)))
427 {
428 std::ranges::swap(m_Edges, rhs.m_Edges);
429 }
430
431 // TO DO: change semantics to non-throwing?
432 constexpr void swap_edges(edge_index_type node, edge_index_type i, edge_index_type j)
433 {
434 graph_errors::check_edge_swap_indices(node, i, j, static_cast<std::size_t>(std::ranges::distance(cedges(node))));
435
436 std::ranges::iter_swap(begin_edges(node) + i, begin_edges(node) + j);
437 }
438
439 // TO DO: change semantics to non-throwing?
440 constexpr void swap_nodes(edge_index_type i, edge_index_type j)
441 {
442 graph_errors::check_node_index_range("swap_nodes", order(), i, j);
443
444 if(i == j) return;
445
446 auto fixUp{
447 [this, i, j](edge_index_type n){
448 for(auto& e : edges(n))
449 {
450 if (e.target_node() == i) e.target_node(j);
451 else if(e.target_node() == j) e.target_node(i);
452 }
453 }
454 };
455
456 if constexpr(!is_directed(flavour))
457 {
458 // TO DO: use concat_view when available
459 auto getTarget{[](const auto& e){ return e.target_node(); }};
460 auto targetEdges{std::views::transform(edges(i), getTarget) | std::ranges::to<std::vector>()};
461 std::ranges::transform(edges(j), std::back_inserter(targetEdges), getTarget);
462 std::ranges::sort(targetEdges);
463 auto duplicates{std::ranges::unique(targetEdges)};
464
465 for(auto iter{targetEdges.begin()}; iter != duplicates.begin(); ++iter)
466 {
467 fixUp(*iter);
468 }
469 }
470 else
471 {
472 for(edge_index_type n{}; n < static_cast<edge_index_type>(order()); ++n)
473 {
474 fixUp(n);
475 }
476 }
477
478 m_Edges.swap_partitions(i, j);
479 }
480
481 [[nodiscard]]
482 auto get_edge_allocator() const
483 {
484 return m_Edges.get_allocator();
485 }
486
487 [[nodiscard]]
488 auto get_edge_allocator(partitions_allocator_tag) const
489 requires has_partitions_allocator<edge_storage_type>
490 {
491 return m_Edges.get_partitions_allocator();
492 }
493
494 void reserve_nodes(const size_type size)
495 {
496 m_Edges.reserve_partitions(size);
497 }
498
499 [[nodiscard]]
500 size_type node_capacity() const noexcept
501 {
502 return m_Edges.num_partitions_capacity();
503 }
504
505 void reserve_edges(const edge_index_type partition, const edge_index_type size)
506 requires graph_impl::has_reservable_partitions<edge_storage_type>
507 {
508 m_Edges.reserve_partition(partition, size);
509 }
510
511 void reserve_edges(const edge_index_type size)
512 requires (!graph_impl::has_reservable_partitions<edge_storage_type>)
513 {
514 m_Edges.reserve(size);
515 }
516
517 [[nodiscard]]
518 size_type edges_capacity(const edge_index_type partition) const
519 requires graph_impl::has_reservable_partitions<edge_storage_type>
520 {
521 return m_Edges.partition_capacity(partition);
522 }
523
524 [[nodiscard]]
525 size_type edges_capacity() const noexcept
526 requires (!graph_impl::has_reservable_partitions<edge_storage_type>)
527 {
528 return m_Edges.capacity();
529 }
530
531 void shrink_to_fit()
532 {
533 m_Edges.shrink_to_fit();
534 }
535
536 // TO DO: Consider interaction of throwing semantics with that of edge storage class
537 [[nodiscard]]
538 constexpr edge_iterator begin_edges(const edge_index_type node)
539 {
540 graph_errors::check_node_index_range("begin_edges", order(), node);
541
542 return m_Edges.begin_partition(node);
543 }
544
545 [[nodiscard]]
546 constexpr edge_iterator end_edges(const edge_index_type node)
547 {
548 graph_errors::check_node_index_range("end_edges", order(), node);
549
550 return m_Edges.end_partition(node);
551 }
552
553 [[nodiscard]]
554 constexpr const_edge_iterator begin_edges(const edge_index_type node) const
555 {
556 graph_errors::check_node_index_range("begin_edges", order(), node);
557
558 return m_Edges.begin_partition(node);
559 }
560
561 [[nodiscard]]
562 constexpr const_edge_iterator end_edges(const edge_index_type node) const
563 {
564 graph_errors::check_node_index_range("end_edges", order(), node);
565
566 return m_Edges.end_partition(node);
567 }
568
569 [[nodiscard]]
570 constexpr reverse_edge_iterator rbegin_edges(const edge_index_type node)
571 {
572 graph_errors::check_node_index_range("rbegin_edges", order(), node);
573
574 return m_Edges.rbegin_partition(node);
575 }
576
577 [[nodiscard]]
578 constexpr reverse_edge_iterator rend_edges(const edge_index_type node)
579 {
580 graph_errors::check_node_index_range("rend_edges", order(), node);
581
582 return m_Edges.rend_partition(node);
583 }
584
585 [[nodiscard]]
586 constexpr const_reverse_edge_iterator rbegin_edges(const edge_index_type node) const
587 {
588 graph_errors::check_node_index_range("rbegin_edges", order(), node);
589
590 return m_Edges.rbegin_partition(node);
591 }
592
593 [[nodiscard]]
594 constexpr const_reverse_edge_iterator rend_edges(const edge_index_type node) const
595 {
596 graph_errors::check_node_index_range("rend_edges", order(), node);
597
598 return m_Edges.rend_partition(node);
599 }
600
601 [[nodiscard]]
602 constexpr edges_range edges(const edge_index_type node) { return {begin_edges(node), end_edges(node)}; }
603
604 void add_node()
605 {
606 m_Edges.add_slot();
607 }
608
609 size_type insert_node(const size_type node)
610 {
611 m_Edges.insert_slot(node);
612 fix_edge_data(node,
613 [](const auto targetNode, const auto node) { return targetNode >= node; },
614 [](const auto index) { return index + 1; });
615
616 return node;
617 }
618
619 void erase_node(const size_type node)
620 {
621 graph_errors::check_node_index_range("erase_node", order(), node);
622
623 if constexpr(!is_directed(flavour))
624 {
625 std::vector<size_type> partitionsToVisit{};
626
627 for(const auto& edge : m_Edges.partition(node))
628 {
629 const auto target{edge.target_node()};
630 if(target != node) partitionsToVisit.push_back(target);
631 }
632
633 std::ranges::sort(partitionsToVisit);
634 auto duplicates{std::ranges::unique(partitionsToVisit)};
635
636 for(const auto partition : std::ranges::subrange{partitionsToVisit.begin(), duplicates.begin()})
637 {
638 auto fn{[node](const edge_type& e) { return e.target_node() == node; }};
639
640 if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
641 {
642 // TO DO: consider reversing iteration
643 for(auto iter{m_Edges.begin_partition(partition)}; iter != m_Edges.end_partition(partition);)
644 {
645 if(fn(*iter))
646 {
647 iter = m_Edges.erase_from_partition(iter);
648 decrement_comp_indices(iter, m_Edges.end_partition(partition), 1);
649 }
650 else
651 {
652 ++iter;
653 }
654 }
655 }
656 else
657 {
658 erase_from_partition_if(partition, fn);
659 }
660 }
661 }
662 else
663 {
664 for(size_type i{}; i < m_Edges.num_partitions(); ++i)
665 {
666 if(i == node) continue;
667 erase_from_partition_if(i, [node](const edge_type& e) {
668 return (e.target_node() == node);
669 });
670 }
671 }
672
673 m_Edges.erase_slot(node);
674 fix_edge_data(node,
675 [](const auto targetNode, const auto node) { return targetNode > node; },
676 [](const auto index) { return index - 1; });
677 }
678
679 template<class... Args>
680 requires (std::is_empty_v<edge_meta_data_type>&& initializable_from<edge_weight_type, Args...> && (is_directed(flavour) || std::is_copy_constructible_v<edge_type>))
681 void join(const edge_index_type node1, const edge_index_type node2, Args&&... args)
682 {
683 graph_errors::check_node_index_range("join", order(), node1, node2);
684
685 add_to_partition(node1, node2, std::forward<Args>(args)...);
686
687 if constexpr(!is_directed(flavour))
688 {
689 reciprocal_join(node1, node2);
690 }
691 }
692
693 template<class... Args>
694 requires (!std::is_empty_v<edge_meta_data_type>&& initializable_from<edge_weight_type, Args...> && (is_directed(flavour) || std::is_copy_constructible_v<edge_type>))
695 void join(const edge_index_type node1, const edge_index_type node2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
696 {
697 graph_errors::check_node_index_range("join", order(), node1, node2);
698
699 add_to_partition(node1, node2, meta1, std::forward<Args>(args)...);
700
701 if constexpr(!is_directed(flavour))
702 {
703 reciprocal_join(node1, node2, std::move(meta2));
704 }
705 }
706
707 template<class... Args>
708 requires (std::is_empty_v<edge_meta_data_type> && initializable_from<edge_weight_type, Args...>&& is_embedded(flavour) && std::is_copy_constructible_v<edge_type>)
709 std::pair<const_edge_iterator, const_edge_iterator>
710 insert_join(const_edge_iterator citer1, const_edge_iterator citer2, Args&&... args)
711 {
712 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
713 const auto dist2{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node2), citer2))};
714 if(node1 == node2) return insert_join(citer1, dist2, std::forward<Args>(args)...);
715
716 citer1 = insert_to_partition(citer1, node2, dist2, std::forward<Args>(args)...);
717 return insert_reciprocal_join(citer1, cbegin_edges(node2) + dist2);
718 }
719
720 template<class... Args>
721 requires (!std::is_empty_v<edge_meta_data_type>, initializable_from<edge_weight_type, Args...>&& is_embedded(flavour) && std::is_copy_constructible_v<edge_type>)
722 std::pair<const_edge_iterator, const_edge_iterator>
723 insert_join(const_edge_iterator citer1, const_edge_iterator citer2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
724 {
725 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
726 const auto dist2{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node2), citer2))};
727 if(node1 == node2) return insert_join(citer1, dist2, meta1, meta2, std::forward<Args>(args)...);
728
729 citer1 = insert_to_partition(citer1, node2, dist2, meta1, std::forward<Args>(args)...);
730 return insert_reciprocal_join(citer1, cbegin_edges(node2) + dist2, meta2);
731 }
732
733 template<class... Args>
734 requires (std::is_empty_v<edge_meta_data_type> && initializable_from<edge_weight_type, Args...>&& is_embedded(flavour) && std::is_copy_constructible_v<edge_type>)
735 std::pair<const_edge_iterator, const_edge_iterator>
736 insert_join(const_edge_iterator citer1, const edge_index_type pos2, Args&&... args)
737 {
738 const auto node{citer1.partition_index()};
739 graph_errors::check_edge_insertion_index("insert_join", node, std::ranges::distance(cedges(node)) + 1, pos2);
740
741 citer1 = insert_to_partition(citer1, node, pos2, std::forward<Args>(args)...);
742
743 return insert_reciprocal_join(citer1, pos2);
744 }
745
746 template<class... Args>
747 requires (!std::is_empty_v<edge_meta_data_type>, initializable_from<edge_weight_type, Args...>&& is_embedded(flavour) && std::is_copy_constructible_v<edge_type>)
748 std::pair<const_edge_iterator, const_edge_iterator>
749 insert_join(const_edge_iterator citer1, const edge_index_type pos2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
750 {
751 const auto node{citer1.partition_index()};
752 graph_errors::check_edge_insertion_index("insert_join", node, std::ranges::distance(cedges(node)) + 1, pos2);
753
754 citer1 = insert_to_partition(citer1, node, pos2, meta1, std::forward<Args>(args)...);
755
756 return insert_reciprocal_join(citer1, pos2, meta2);
757 }
758
759 void erase_edge(const_edge_iterator citer)
760 {
761 // TO DO: ensure strong exception guarantee (maybe just insist on
762 // noexcept move assignment for m_Edges
763
764 if(!order() || (citer == cend_edges(citer.partition_index()))) return;
765
766 if constexpr (!is_directed(flavour))
767 {
768 const auto source{citer.partition_index()};
769 const auto partner{citer->target_node()};
770
771 auto pred{
772 [=](const edge_type& potentialPartner){
773 if(potentialPartner.target_node() == source)
774 {
775 if constexpr(std::is_empty_v<edge_weight_type>)
776 return true;
777 else if constexpr(shared_weight_v)
778 return std::addressof(citer->weight()) == std::addressof(potentialPartner.weight());
779 else if constexpr(std::is_empty_v<edge_meta_data_type>)
780 return citer->weight() == potentialPartner.weight();
781 else
783 }
784
785 return false;
786 }
787 };
788
789 if(source != partner)
790 {
791 if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
792 {
793 const auto partnerLocalIndex{citer->complementary_index()};
794 citer = m_Edges.erase_from_partition(citer);
795 decrement_comp_indices(to_edge_iterator(citer), m_Edges.end_partition(source), 1);
796
797 auto partnerIter{m_Edges.erase_from_partition(partner, partnerLocalIndex)};
798 decrement_comp_indices(partnerIter, m_Edges.end_partition(partner), 1);
799 }
800 else
801 {
802 auto found{std::ranges::find_if(cedges(partner), pred)};
803
804 if(found == cend_edges(partner))
805 throw std::logic_error{graph_errors::erase_edge_error(partner, {source, static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(source), citer))})};
806
807 const auto partnerDist{std::ranges::distance(cbegin_edges(partner), found)};
808
809 m_Edges.erase_from_partition(citer);
810 m_Edges.erase_from_partition(cbegin_edges(partner) + partnerDist);
811 }
812 }
813 else
814 {
815 if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
816 {
817 const auto compIndex{citer->complementary_index()};
818 const auto pos{std::ranges::distance(cbegin_edges(source), citer)};
819 const auto separation{std::ranges::distance(citer, cbegin_edges(source) + compIndex)};
820 if((separation == 1) || (separation == -1))
821 {
822 const auto delta{separation == 1 ? 0 : -1};
823 citer = m_Edges.erase_from_partition(citer+delta, citer+(2+delta));
824
825 decrement_comp_indices(to_edge_iterator(citer), m_Edges.end_partition(source), 2);
826 }
827 else
828 {
829 const bool negativeSeparation{separation < 0};
830
831 auto erase{
832 [this, source](auto index){
833 auto i{m_Edges.erase_from_partition(m_Edges.begin_partition(source) + index)};
834 decrement_comp_indices(i, m_Edges.end_partition(source), 1);
835 }
836 };
837
838 erase(negativeSeparation ? pos : compIndex);
839 erase(negativeSeparation ? compIndex : pos);
840 }
841 }
842 else
843 {
844 auto found{std::ranges::find_if(cbegin_edges(source), citer, pred)};
845 if(found == citer) found = std::ranges::find_if(std::ranges::next(citer), cend_edges(source), pred);
846
847 if(found == cend_edges(source))
848 throw std::logic_error{graph_errors::erase_edge_error(source, {source, static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(source), citer))})};
849
850 auto dist{std::ranges::distance(cbegin_edges(source), citer)};
851 if(std::ranges::distance(cbegin_edges(source), found) < dist) --dist;
852
853 m_Edges.erase_from_partition(found);
854 m_Edges.erase_from_partition(cbegin_edges(source) + dist);
855 }
856 }
857 }
858 else
859 {
860 m_Edges.erase_from_partition(citer);
861 }
862 }
863
864 void clear() noexcept
865 {
866 m_Edges.clear();
867 }
868
869 template<class Comp, class Proj = std::identity>
870 constexpr void sort_edges(const_edge_iterator begin, const_edge_iterator end, Comp comp, Proj proj = {})
871 requires ((edge_type::flavour == edge_flavour::partial) && std::sortable<edge_iterator, Comp, Proj>)
872 {
873 if(begin.partition_index() != end.partition_index()) return;
874
875 std::ranges::sort(to_edge_iterator(begin), to_edge_iterator(end), std::move(comp), std::move(proj));
876 }
877
878 template<class Comp, class Proj = std::identity>
879 constexpr void sort_edges(const_edges_range r, Comp comp, Proj proj = {})
880 requires ((edge_type::flavour == edge_flavour::partial) && std::sortable<edge_iterator, Comp, Proj>)
881 {
882 sort_edges(r.begin(), r.end(), std::move(comp), std::move(proj));
883 }
884
885 template<class Comp, class Proj = std::identity>
886 constexpr void stable_sort_edges(const_edge_iterator begin, const_edge_iterator end, Comp comp, Proj proj = {})
887 requires ((edge_type::flavour == edge_flavour::partial) && merge_sortable<edge_iterator, Comp, Proj>)
888 {
889 if(begin.partition_index() != end.partition_index()) return;
890
891 sequoia::stable_sort(to_edge_iterator(begin), to_edge_iterator(end), std::move(comp), std::move(proj));
892 }
893
894 template<class Comp, class Proj = std::identity>
895 constexpr void stable_sort_edges(const_edges_range r, Comp comp, Proj proj = {})
896 requires ((edge_type::flavour == edge_flavour::partial) && merge_sortable<edge_iterator, Comp, Proj>)
897 {
898 stable_sort_edges(r.begin(), r.end(), std::move(comp), std::move(proj));
899 }
900
901 private:
902 constexpr static bool direct_init_v{std::is_same_v<edge_type, edge_init_type>};
903 constexpr static bool direct_copy_v{direct_init_v};
904 constexpr static bool shared_weight_v{graph_impl::has_shared_weight_v<edge_type>};
905
906 // private data
907 edge_storage_type m_Edges;
908
909 // helper methods
910
911 template<class Pred>
912 void erase_from_partition_if(const edge_index_type partitionIndex, Pred pred)
913 {
914 auto discarded{std::ranges::remove_if(m_Edges.begin_partition(partitionIndex), m_Edges.end_partition(partitionIndex), pred)};
915 m_Edges.erase_from_partition(discarded.begin(), discarded.end());
916 }
917
918 template<alloc... Allocators>
919 [[nodiscard]]
920 constexpr edge_storage_type make_edges(edges_initializer edges, const Allocators&... as)
921 {
922 return process_edges(validate(preprocess(edges)), as...);
923 }
924
925 [[nodiscard]]
926 constexpr static const edges_initializer& preprocess(const edges_initializer& edges)
927 requires (is_embedded(flavour) || is_directed(flavour))
928 {
929 return edges;
930 }
931
932 [[nodiscard]]
933 constexpr static edge_storage_type preprocess(edges_initializer edges)
934 requires (direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
935 {
936 return preprocess(edge_storage_type{edges});
937 }
938
939 [[nodiscard]]
940 constexpr static auto preprocess(edges_initializer edges)
941 requires (!direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
942 {
943 using namespace data_structures;
944 using sequence_t = partitioned_sequence<edge_init_type>;
945
946 return preprocess(sequence_t{edges});
947 }
948
949 template<class IntermediateEdges>
950 [[nodiscard]]
951 constexpr static IntermediateEdges preprocess(IntermediateEdges edges)
952 requires (!is_embedded(flavour) && !is_directed(flavour))
953 {
954 constexpr bool clusterEdges{!std::is_empty_v<edge_weight_type> && !std::totally_ordered<edge_weight_type>};
955
956 for(edge_index_type i{}; i < edges.num_partitions(); ++i)
957 {
958 sequoia::stable_sort(edges.begin_partition(i), edges.end_partition(i), graph_impl::edge_comparer{});
959
960 if constexpr(clusterEdges)
961 {
962 auto lowerIter{edges.begin_partition(i)}, upperIter{edges.begin_partition(i)};
963 while(lowerIter != edges.end_partition(i))
964 {
965 upperIter = std::ranges::find_if_not(upperIter, edges.end_partition(i), [target{lowerIter->target_node()}](const auto& wt) { return target == wt.target_node(); });
966
967 sequoia::cluster(lowerIter, upperIter, [](const auto& e1, const auto& e2) {
968 return e1.weight() == e2.weight();
969 });
970
971 lowerIter = upperIter;
972 }
973 }
974 }
975
976 return edges;
977 }
978
979 [[nodiscard]]
980 constexpr static const edges_initializer& validate(const edges_initializer& edges)
981 requires (is_embedded(flavour))
982 {
983 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
984 {
985 const auto nodeIndex{static_cast<std::size_t>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
986 const auto& nodeEdges{*nodeEdgesIter};
987 for(auto edgeIter{nodeEdges.begin()}; edgeIter != nodeEdges.end(); ++edgeIter)
988 {
989 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(nodeEdges.begin(), edgeIter))};
990 const auto& edge{*edgeIter};
991 const auto target{edge.target_node()};
992
993 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "target", edges.size(), target);
994 const auto compIndex{edge.complementary_index()};
995
996 const bool doValidate{
997 [&]() {
998 if constexpr(!is_directed(flavour)) return true;
999 else return (edge.target_node() != nodeIndex) || (edge.source_node() == edge.target_node());
1000 }()
1001 };
1002
1003 if(doValidate)
1004 {
1005 auto targetEdgesIter{edges.begin() + target};
1006 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "complementary", targetEdgesIter->size(), compIndex);
1007
1008 if((target == nodeIndex) && (compIndex == edgeIndex))
1009 {
1010 throw std::logic_error{graph_errors::self_referential_error({nodeIndex, edgeIndex}, target, compIndex)};
1011 }
1012 else if(const auto& targetEdge{*(targetEdgesIter->begin() + compIndex)}; targetEdge.complementary_index() != edgeIndex)
1013 {
1014 throw std::logic_error{graph_errors::reciprocated_error_message({nodeIndex, edgeIndex}, "complementary", targetEdge.complementary_index(), edgeIndex)};
1015 }
1016 else
1017 {
1018 if constexpr(!is_directed(flavour))
1019 {
1020 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", targetEdge.target_node(), nodeIndex);
1021 }
1022 else
1023 {
1024 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", targetEdge.target_node(), target);
1025 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "source", targetEdge.source_node(), edge.source_node());
1026
1027 if constexpr(is_embedded(flavour))
1028 {
1029 graph_errors::check_inversion_consistency(nodeIndex, {edgeIndex, edge.inverted()}, {compIndex, targetEdge.inverted()});
1030 }
1031 }
1032
1033 if constexpr(!std::is_empty_v<edge_weight_type>)
1034 {
1035 if(edge.weight() != targetEdge.weight())
1036 throw std::logic_error{graph_errors::mismatched_weights_message("process_complementary_edges", {nodeIndex, edgeIndex})};
1037 }
1038 }
1039 }
1040
1041 if constexpr(is_directed(flavour))
1042 {
1043 const auto source{edge.source_node()};
1044 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "source", edges.size(), source);
1045 graph_errors::check_embedded_edge(nodeIndex, source, target);
1046
1047 if((edge.target_node() == nodeIndex) && (edge.source_node() != edge.target_node()))
1048 {
1049 auto sourceEdgesIter{edges.begin() + source};
1050 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "complementary", sourceEdgesIter->size(), compIndex);
1051
1052 const auto& sourceEdge{*(sourceEdgesIter->begin() + compIndex)};
1053 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", sourceEdge.target_node(), nodeIndex);
1054 }
1055 }
1056 }
1057 }
1058
1059 return edges;
1060 }
1061
1062 [[nodiscard]]
1063 constexpr static const edges_initializer& validate(const edges_initializer& edges)
1064 requires (!is_embedded(flavour) && is_directed(flavour))
1065 {
1066 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1067 {
1068 const auto nodeIndex{static_cast<std::size_t>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1069 const auto& nodeEdges{*nodeEdgesIter};
1070 for(auto edgeIter{nodeEdges.begin()}; edgeIter != nodeEdges.end(); ++edgeIter)
1071 {
1072 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(nodeEdges.begin(), edgeIter))};
1073 const auto& edge{*edgeIter};
1074
1075 const auto target{edge.target_node()};
1076 graph_errors::check_edge_index_range("process_edges", {nodeIndex, edgeIndex}, "target", edges.size(), target);
1077 }
1078 }
1079
1080 return edges;
1081 }
1082
1083
1084 template<class Edges>
1085 using partition_iterator_range = std::ranges::subrange<typename Edges::const_partition_iterator>;
1086
1087 template<class IntermediateEdges>
1088 [[nodiscard]]
1089 constexpr static decltype(auto) validate(IntermediateEdges&& edges)
1090 requires (!is_embedded(flavour) && !is_directed(flavour))
1091 {
1092 using range_t = partition_iterator_range<IntermediateEdges>;
1093 using namespace graph_errors;
1094
1095 visit_edges(
1096 edges,
1097 []() {},
1098 [](edge_index_type i, range_t hostRange) {
1099 if(std::ranges::distance(hostRange) % 2) throw std::logic_error{odd_num_loops_error("connectivity_base", i)};
1100 },
1101 [&](edge_index_type i, edge_index_type target, range_t hostRange, range_t targetRange) {
1102 const auto brokenEdge{
1103 [&, i, target, hostRange, targetRange]() -> std::optional<edge_indices> {
1104 if(auto reciprocalCount{std::ranges::distance(targetRange)}; !reciprocalCount)
1105 {
1106 return edge_indices{i, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(i), hostRange.begin()))};
1107 }
1108 else if(auto count{std::ranges::distance(hostRange)}; count > reciprocalCount)
1109 {
1110 return edge_indices{i, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(i), hostRange.begin()) + reciprocalCount)};
1111 }
1112 else if(count < reciprocalCount)
1113 {
1114 return edge_indices{target, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(target), targetRange.begin()) + count)};
1115 }
1116
1117 return {};
1118 }()
1119 };
1120
1121 if(brokenEdge)
1122 throw std::logic_error{absent_reciprocated_partial_edge_message("connectivity_base", brokenEdge.value())};
1123 }
1124 );
1125
1126 return std::forward<IntermediateEdges>(edges);
1127 }
1128
1129 template<std::ranges::view View>
1130 constexpr static auto find_cluster_end(View v) -> decltype(v.begin())
1131 {
1132 return !v.empty() ? std::ranges::find_if_not(v, [&front{v.front()}](const auto& e){ return e == front; }) : v.end();
1133 }
1134
1135 template<class Edges, alloc... Allocators>
1136 [[nodiscard]]
1137 constexpr edge_storage_type process_edges(Edges&& orderedEdges, const Allocators&... as)
1138 requires direct_init_v
1139 {
1140 return {std::forward<Edges>(orderedEdges), as...};
1141 }
1142
1143 template<alloc... Allocators>
1144 [[nodiscard]]
1145 constexpr edge_storage_type process_edges(edges_initializer edges, const Allocators&... as)
1146 requires (!direct_init_v && !is_embedded(flavour))
1147 {
1148 edge_storage_type storage(as...);
1149 storage.reserve_partitions(edges.size());
1150
1151 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1152 {
1153 storage.add_slot();
1154
1155 const auto nodeIndex{static_cast<edge_index_type>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1156 const auto& nodeEdges{*nodeEdgesIter};
1157 for(const auto& edge : nodeEdges)
1158 {
1159 storage.push_back_to_partition(nodeIndex, edge.target_node(), edge.weight());
1160 }
1161 }
1162
1163 return storage;
1164 }
1165
1166 template<alloc... Allocators>
1167 [[nodiscard]]
1168 constexpr edge_storage_type process_edges(edges_initializer edges, const Allocators&... as)
1169 requires (!direct_init_v && is_embedded(flavour))
1170 {
1171 edge_storage_type storage(as...);
1172 storage.reserve_partitions(edges.size());
1173
1174 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1175 {
1176 storage.add_slot();
1177
1178 const auto nodeIndex{static_cast<edge_index_type>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1179 const auto& nodeEdges{*nodeEdgesIter};
1180 for(const edge_init_type& edge : nodeEdges)
1181 {
1182 storage.push_back_to_partition(nodeIndex, edge_type{edge});
1183 }
1184 }
1185
1186 return storage;
1187 }
1188
1189 template<class Edges, alloc... Allocators>
1190 [[nodiscard]]
1191 constexpr edge_storage_type process_edges(const Edges& orderedEdges, const Allocators&... as)
1192 requires (!direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
1193 {
1194 using range_t = partition_iterator_range<Edges>;
1195
1196 edge_storage_type storage(as...);
1197 storage.reserve_partitions(orderedEdges.num_partitions());
1198
1199 auto addToStorage{
1200 [&storage,this](edge_index_type host, edge_index_type target, edge_index_type compIndex, range_t hostRange){
1201 storage.push_back_to_partition(host, (compIndex == npos) ? edge_type{hostRange.front()}
1202 : edge_type{target, *(cbegin_edges(target) + compIndex)});
1203 }
1204 };
1205
1206 visit_edges(
1207 orderedEdges,
1208 [&storage]() { storage.add_slot(); },
1209 [addToStorage,&orderedEdges](edge_index_type host, range_t hostRange) {
1210 for(; hostRange.begin() != hostRange.end(); hostRange.advance(1))
1211 {
1212 const bool hasCompIndex{(std::ranges::distance(hostRange) % 2) && shared_weight_v};
1213 const auto compIndex{hasCompIndex ? static_cast<edge_index_type>(std::ranges::distance(orderedEdges.cbegin_partition(host), hostRange.begin()) - 1) : npos};
1214
1215 addToStorage(host, host, compIndex, hostRange);
1216 }
1217 },
1218 [addToStorage,&orderedEdges](edge_index_type host, edge_index_type target, range_t hostRange, range_t targetRange) {
1219 for(; hostRange.begin() != hostRange.end(); hostRange.advance(1))
1220 {
1221 const bool hasCompIndex{(host > target) && shared_weight_v};
1222 const auto compIndex{hasCompIndex ? static_cast<edge_index_type>(std::ranges::distance(orderedEdges.cbegin_partition(target), targetRange.end()) - std::ranges::distance(hostRange)) : npos};
1223
1224 addToStorage(host, target, compIndex, hostRange);
1225 }
1226 }
1227 );
1228
1229 return storage;
1230 }
1231
1232 template<class Edges,
1233 std::invocable PerNodeFn,
1234 std::invocable<edge_index_type, partition_iterator_range<Edges>> PerLoopFn,
1235 std::invocable<edge_index_type, edge_index_type, partition_iterator_range<Edges>, partition_iterator_range<Edges>> PerLinkFn
1236 >
1237 constexpr static void visit_edges(const Edges& orderedEdges, PerNodeFn perNode, PerLoopFn perLoop, PerLinkFn perLink)
1238 requires (!is_embedded(flavour) && !is_directed(flavour))
1239 {
1240 constexpr bool clusterEdges{!std::is_empty_v<edge_weight_type> && !std::totally_ordered<edge_weight_type>};
1241
1242 for(edge_index_type i{}; i < orderedEdges.num_partitions(); ++i)
1243 {
1244 perNode();
1245
1246 auto lowerIter{orderedEdges.cbegin_partition(i)}, upperIter{orderedEdges.cbegin_partition(i)};
1247 while(lowerIter != orderedEdges.cend_partition(i))
1248 {
1249 upperIter = std::ranges::upper_bound(lowerIter, orderedEdges.cend_partition(i), *lowerIter, graph_impl::edge_comparer{});
1250 if constexpr(clusterEdges) upperIter = find_cluster_end(std::ranges::subrange{lowerIter, upperIter});
1251
1252 if(const auto target{lowerIter->target_node()}; target == i)
1253 {
1254 perLoop(i, {lowerIter, upperIter});
1255 }
1256 else
1257 {
1258 graph_errors::check_edge_index_range("", {i, static_cast<std::size_t>(std::ranges::distance(orderedEdges.cbegin_partition(i), lowerIter))}, "target", orderedEdges.num_partitions(), lowerIter->target_node());
1259
1260 const auto comparisonEdge{
1261 [lowerIter, i]() {
1262 auto compEdge{*lowerIter};
1263 compEdge.target_node(i);
1264 return compEdge;
1265 }()
1266 };
1267
1268 auto eqrange{std::ranges::equal_range(orderedEdges.partition(target), comparisonEdge, graph_impl::edge_comparer{})};
1269
1270 if constexpr(clusterEdges)
1271 {
1272 auto clusters{std::views::drop_while(eqrange, [&wt{lowerIter->weight()}](const auto& e) { return e.weight() != wt; })};
1273 eqrange = {clusters.begin(), find_cluster_end(clusters)};
1274 }
1275
1276 perLink(i, target, {lowerIter, upperIter}, eqrange);
1277 }
1278
1279 lowerIter = upperIter;
1280 }
1281 }
1282 }
1283
1284 template<alloc... Allocators>
1285 [[nodiscard]]
1286 edge_storage_type copy_edges(const connectivity_base& in, const Allocators&... as)
1287 requires direct_copy_v
1288 {
1289 return edge_storage_type{in.m_Edges, as...};
1290 }
1291
1292 [[nodiscard]]
1293 edge_storage_type copy_edges(const connectivity_base& in)
1294 requires (!direct_copy_v)
1295 {
1296 if constexpr(has_partitions_allocator<edge_storage_type>)
1297 {
1298 return copy_edges(in, in.m_Edges.get_allocator(), in.m_Edges.get_partitions_allocator());
1299 }
1300 else
1301 {
1302 return copy_edges(in, in.m_Edges.get_allocator());
1303 }
1304 }
1305
1306 template<alloc... Allocators>
1307 [[nodiscard]]
1308 edge_storage_type copy_edges(const connectivity_base& in, const Allocators&... as)
1309 requires (!direct_copy_v && (sizeof...(Allocators) > 0))
1310 {
1311 edge_storage_type storage({std::allocator_traits<Allocators>::select_on_container_copy_construction(as)}...);
1312
1313 if constexpr(edge_type::flavour == edge_flavour::partial)
1314 {
1315 copy_edges(in, storage, graph_impl::edge_maker<edge_type, edge_storage_type>{storage});
1316 }
1317 else if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1318 {
1319 auto processor{
1320 [this, &storage](const size_type node, const_edge_iterator first, const_edge_iterator i) {
1321 const auto dist{static_cast<edge_index_type>(std::ranges::distance(first, i))};
1322 const bool encountered{(i->target_node() < node)
1323 || ((i->target_node() == node) && (i->complementary_index() < dist))};
1324
1325 if(!encountered)
1326 {
1327 storage.push_back_to_partition(node, i->target_node(), i->complementary_index(), i->weight());
1328 }
1329 else
1330 {
1331 const auto compI{i->complementary_index()};
1332 storage.push_back_to_partition(node, i->target_node(), compI, *(cbegin_edges(i->target_node()) + compI));
1333 }
1334 }
1335 };
1336
1337 copy_edges(in, storage, processor);
1338 }
1339
1340 return storage;
1341 }
1342
1343 template<std::invocable<size_type, const_edge_iterator, const_edge_iterator> Processor>
1344 void copy_edges(const connectivity_base& in, edge_storage_type& storage, Processor processor)
1345 {
1346 reserve_nodes(in.m_Edges.num_partitions());
1347 if constexpr(!graph_impl::has_reservable_partitions<edge_storage_type>)
1348 {
1349 storage.reserve(in.m_Edges.size());
1350 }
1351 for(size_type i{}; i<in.order(); ++i)
1352 {
1353 storage.add_slot();
1354 if constexpr(graph_impl::has_reservable_partitions<edge_storage_type>)
1355 {
1356 storage.reserve_partition(i, std::ranges::distance(in.cbegin_edges(i), in.cend_edges(i)));
1357 }
1358 for(auto inIter{in.cbegin_edges(i)}; inIter != in.cend_edges(i); ++inIter)
1359 {
1360 processor(i, in.cbegin_edges(i), inIter);
1361 }
1362 }
1363 }
1364
1365 [[nodiscard]]
1366 constexpr const_edge_iterator to_const_edge_iterator(const_reverse_edge_iterator criter) const
1367 {
1368 const auto source{criter.partition_index()};
1369 return m_Edges.cbegin_partition(source) + std::ranges::distance(criter, m_Edges.crend_partition(source)) - 1;
1370 }
1371
1372 [[nodiscard]]
1373 constexpr edge_iterator to_edge_iterator(const_edge_iterator citer)
1374 {
1375 const auto source{citer.partition_index()};
1376 const auto dist{std::ranges::distance(cbegin_edges(source), citer)};
1377 return m_Edges.begin_partition(source) + dist;
1378 }
1379
1380 template<std::invocable<edge_weight_type&> Fn>
1381 constexpr std::invoke_result_t<Fn, edge_weight_type&> mutate_source_edge_weight(const_edge_iterator citer, Fn fn)
1382 {
1383 return fn(to_edge_iterator(citer)->weight());
1384 }
1385
1386 template<class... Args>
1387 constexpr void set_source_edge_weight(edge_iterator iter, Args&&... args)
1388 {
1389 iter->weight(std::forward<Args>(args)...);
1390 }
1391
1392 template<class Setter>
1393 requires std::is_invocable_r_v<edge_iterator, Setter, edge_iterator>
1394 constexpr const_edge_iterator manipulate_partner_edge_weight(const_edge_iterator citer, Setter setter)
1395 {
1396 const auto partner{citer->target_node()};
1397
1398 if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1399 {
1400 const auto comp{citer->complementary_index()};
1401 return setter(m_Edges.begin_partition(partner) + comp);
1402 }
1403 else
1404 {
1405
1406 auto found{end_edges(partner)};
1407 if(const auto source{citer.partition_index()}; source == partner)
1408 {
1409 for(auto iter{m_Edges.begin_partition(partner)}; iter != m_Edges.end_partition(partner); ++iter)
1410 {
1411 if(std::ranges::distance(m_Edges.begin_partition(partner), iter) == std::ranges::distance(cbegin_edges(partner), citer))
1412 continue;
1413
1414 if(*iter == *citer)
1415 {
1416 found = setter(iter);
1417 break;
1418 }
1419 }
1420 }
1421 else
1422 {
1423 for(auto iter{m_Edges.begin_partition(partner)}; iter != m_Edges.end_partition(partner); ++iter)
1424 {
1425 if((iter->target_node() == source) && (iter->weight() == citer->weight()))
1426 {
1427 found = setter(iter);
1428 break;
1429 }
1430 }
1431 }
1432
1433 if(found != end_edges(partner))
1434 {
1435 return found;
1436 }
1437 else
1438 {
1439 const auto node{citer.partition_index()};
1440 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(cbegin_edges(node), citer))};
1441 throw std::logic_error{ graph_errors::absent_partner_weight_message("manipulate_partner_edge_weight", {node, edgeIndex}) };
1442 }
1443 }
1444 }
1445
1446 template<std::invocable<edge_weight_type&> Fn>
1447 constexpr void mutate_partner_edge_weight(const_edge_iterator citer, Fn fn)
1448 {
1449 manipulate_partner_edge_weight(citer, [fn](edge_iterator iter) -> edge_iterator { fn(iter->weight()); return iter; });
1450 }
1451
1452 template<class... Args>
1453 requires initializable_from<edge_weight_type, Args...>
1454 constexpr const_edge_iterator set_partner_edge_weight(const_edge_iterator citer, Args&&... args)
1455 {
1456 return manipulate_partner_edge_weight(citer, [&args...](edge_iterator iter) -> edge_iterator { iter->weight(std::forward<Args>(args)...); return iter; });
1457 }
1458
1459 template<class... MetaData>
1460 requires std::is_copy_constructible_v<edge_type> && (std::is_same_v<MetaData, edge_meta_data_type> && ...)
1461 void reciprocal_join(const edge_index_type node1, const edge_index_type node2, MetaData... md)
1462 {
1463 graph_impl::join_sentinel sentinel{m_Edges, node1, m_Edges.size_of_partition(node1) - 1};
1464 if constexpr(edge_type::flavour == edge_flavour::partial)
1465 {
1466 m_Edges.push_back_to_partition(node2, node1, std::move(md)..., *crbegin_edges(node1));
1467 }
1468 else if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1469 {
1470 const edge_index_type compIndex(std::ranges::distance(cbegin_edges(node1), cend_edges(node1)) - 1);
1471 m_Edges.push_back_to_partition(node2, node1, compIndex, std::move(md)..., *crbegin_edges(node1));
1472 }
1473 }
1474
1475
1476 template<class... MetaData>
1477 requires (is_embedded(flavour) && std::is_copy_constructible_v<edge_type> && (std::is_same_v<edge_meta_data_type, MetaData> && ...))
1478 std::pair<const_edge_iterator, const_edge_iterator>
1479 insert_reciprocal_join(const_edge_iterator citer1, const edge_index_type pos2, MetaData... args)
1480 {
1481 const auto node{citer1.partition_index()};
1482
1483 auto pos1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node), citer1))};
1484 if(pos2 <= pos1) ++pos1;
1485
1486 const auto dist1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node), citer1))};
1487 graph_impl::join_sentinel sentinel{m_Edges, node, dist1};
1488
1489 auto citer2{m_Edges.insert_to_partition(cbegin_edges(node) + pos2, node, pos1, std::move(args)..., *citer1)};
1490 if(pos2 > pos1)
1491 {
1492 increment_comp_indices(++to_edge_iterator(citer2), end_edges(node), 1);
1493 }
1494 else
1495 {
1496 auto iter1{begin_edges(node) + pos1};
1497 increment_comp_indices(++to_edge_iterator(citer2), iter1, 1);
1498 increment_comp_indices(iter1 + 1, end_edges(node), 1);
1499 }
1500
1501 citer1 = cbegin_edges(node) + pos1;
1502 return {citer1, citer2};
1503 }
1504
1505 template<class... MetaData>
1506 requires (is_embedded(flavour) && std::is_copy_constructible_v<edge_type> && (std::is_same_v<edge_meta_data_type, MetaData> && ...))
1507 std::pair<const_edge_iterator, const_edge_iterator>
1508 insert_reciprocal_join(const_edge_iterator citer1, const_edge_iterator citer2, MetaData... md)
1509 {
1510 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
1511 const auto dist1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node1), citer1))};
1512
1513 graph_impl::join_sentinel sentinel{m_Edges, node1, dist1};
1514 citer2 = m_Edges.insert_to_partition(citer2, node1, dist1, md..., *citer1);
1515 increment_comp_indices(++to_edge_iterator(citer2), end_edges(node2), 1);
1516
1517 citer1 = cbegin_edges(node1) + dist1;
1518 return {citer1, citer2};
1519 }
1520
1521 template<class... Args>
1522 void add_to_partition(const edge_index_type node1, const edge_index_type node2, Args&&... args)
1523 {
1524 if constexpr (edge_type::flavour == edge_flavour::partial)
1525 {
1526 m_Edges.push_back_to_partition(node1, node2, std::forward<Args>(args)...);
1527 }
1528 else if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
1529 {
1530 const edge_index_type dist(std::ranges::distance(cbegin_edges(node2), cend_edges(node2)));
1531 const edge_index_type i{ node1 == node2 ? dist + 1 : dist };
1532 m_Edges.push_back_to_partition(node1, node2, i, std::forward<Args>(args)...);
1533 }
1534 }
1535
1536 template<class... Args>
1537 const_edge_iterator insert_to_partition(const_edge_iterator citer1, const edge_index_type node2, const edge_index_type pos2, Args&&... args)
1538 {
1539 const auto node1{ citer1.partition_index() };
1540 citer1 = m_Edges.insert_to_partition(citer1, node2, pos2, std::forward<Args>(args)...);
1541 increment_comp_indices(++to_edge_iterator(citer1), end_edges(node1), 1);
1542
1543 return citer1;
1544 }
1545
1546 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel, class Fn>
1547 requires std::is_invocable_r_v<edge_index_type, Fn, edge_index_type>
1548 constexpr void modify_comp_indices(Iter first, Sentinel last, Fn fn) noexcept
1549 {
1550 while(first != last)
1551 {
1552 const auto source{first.partition_index()};
1553 const auto other{first->target_node()};
1554
1555 auto setPartnerCompIndex{
1556 [this, other, fn](edge_index_type cmpIndex){
1557 const auto edgeIter{m_Edges.begin_partition(other) + cmpIndex};
1558 edgeIter->complementary_index(fn(edgeIter->complementary_index()));
1559 }
1560 };
1561
1562 const auto compIndex{first->complementary_index()};
1563 if(source != other)
1564 {
1565 setPartnerCompIndex(compIndex);
1566 }
1567 else
1568 {
1569 const auto dist{static_cast<edge_index_type>(std::ranges::distance(begin_edges(source), first))};
1570 if(const auto shiftedCompIndex{fn(compIndex)}; shiftedCompIndex <= dist)
1571 {
1572 setPartnerCompIndex(compIndex);
1573 }
1574 else
1575 {
1576 setPartnerCompIndex(shiftedCompIndex);
1577 }
1578 }
1579
1580 ++first;
1581 }
1582 }
1583
1584 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel>
1585 constexpr void decrement_comp_indices(Iter first, Sentinel last, const edge_index_type num) noexcept
1586 {
1587 modify_comp_indices(first, last, [num](const auto compIndex) { return compIndex >= num ? compIndex - num : edge_index_type{}; });
1588 }
1589
1590 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel>
1591 constexpr void increment_comp_indices(Iter first, Sentinel last, const edge_index_type num) noexcept
1592 {
1593 modify_comp_indices(first, last, [num](const auto compIndex) { return compIndex + num; });
1594 }
1595
1596 template<class Pred, class Modifier>
1597 constexpr void fix_edge_data(const edge_index_type node, Pred pred, Modifier modifier) noexcept
1598 {
1599 for(size_type i{}; i < m_Edges.num_partitions(); ++i)
1600 {
1601 for(auto& edge : m_Edges.partition(i))
1602 {
1603 const auto target{edge.target_node()};
1604 if(pred(target, node)) edge.target_node(modifier(target));
1605 }
1606 }
1607 }
1608 };
1609
1610 template<std::input_or_output_iterator Iterator>
1612 {
1613 public:
1614 constexpr static bool is_const{is_const_reference_v<typename std::iterator_traits<Iterator>::reference>};
1615
1616 using value_type = typename std::iterator_traits<Iterator>::value_type::weight_type;
1617 using reference = std::conditional_t<is_const, const value_type&, value_type&>;
1618
1619 constexpr edge_weight_dereference_policy() = default;
1621
1622 [[nodiscard]]
1623 friend constexpr bool operator==(const edge_weight_dereference_policy&, const edge_weight_dereference_policy&) noexcept = default;
1624
1625 [[nodiscard]]
1626 static reference get(Iterator i)
1627 {
1628 return i->weight();
1629 }
1630 protected:
1631 constexpr edge_weight_dereference_policy(edge_weight_dereference_policy&&) noexcept = default;
1632
1634
1635 constexpr edge_weight_dereference_policy& operator=(const edge_weight_dereference_policy&) = default;
1636 constexpr edge_weight_dereference_policy& operator=(edge_weight_dereference_policy&&) noexcept = default;
1637 };
1638
1639 template<graph_flavour Flavour, class EdgeStorage>
1640 class connectivity : public connectivity_base<Flavour, EdgeStorage>
1641 {
1642 protected:
1643 using connectivity_base<Flavour, EdgeStorage>::connectivity_base;
1644 };
1645
1646 template<class EdgeStorage>
1647 requires (!std::is_empty_v<typename EdgeStorage::value_type::weight_type>)
1649 {
1650 public:
1652
1657 using edge_weights_range = std::ranges::subrange<edge_weight_iterator>;
1658 using const_edge_weights_range = std::ranges::subrange<const_edge_weight_iterator>;
1659
1660 using edge_index_type = typename base_type::edge_index_type;
1661
1662 [[nodiscard]]
1663 constexpr edge_weight_iterator begin_edge_weights(const edge_index_type node)
1664 {
1665 return edge_weight_iterator{this->begin_edges(node)};
1666 }
1667
1668 [[nodiscard]]
1669 constexpr edge_weight_iterator end_edge_weights(const edge_index_type node)
1670 {
1671 return edge_weight_iterator{this->end_edges(node)};
1672 }
1673
1674 [[nodiscard]]
1675 constexpr const_edge_weight_iterator begin_edge_weights(const edge_index_type node) const
1676 {
1677 return const_edge_weight_iterator{this->begin_edges(node)};
1678 }
1679
1680 [[nodiscard]]
1681 constexpr const_edge_weight_iterator end_edge_weights(const edge_index_type node) const
1682 {
1683 return const_edge_weight_iterator{this->end_edges(node)};
1684 }
1685
1686 [[nodiscard]]
1687 constexpr const_edge_weight_iterator cbegin_edge_weights(const edge_index_type node) const
1688 {
1689 return const_edge_weight_iterator{this->begin_edges(node)};
1690 }
1691
1692 [[nodiscard]]
1693 constexpr const_edge_weight_iterator cend_edge_weights(const edge_index_type node) const
1694 {
1695 return const_edge_weight_iterator{this->end_edges(node)};
1696 }
1697
1698 [[nodiscard]]
1699 constexpr reverse_edge_weight_iterator rbegin_edge_weights(const edge_index_type node)
1700 {
1701 return reverse_edge_weight_iterator{this->rbegin_edges(node)};
1702 }
1703
1704 [[nodiscard]]
1705 constexpr reverse_edge_weight_iterator rend_edge_weights(const edge_index_type node)
1706 {
1707 return reverse_edge_weight_iterator{this->rend_edges(node)};
1708 }
1709
1710 [[nodiscard]]
1711 constexpr const_reverse_edge_weight_iterator rbegin_edge_weights(const edge_index_type node) const
1712 {
1713 return const_reverse_edge_weight_iterator{this->rbegin_edges(node)};
1714 }
1715
1716 [[nodiscard]]
1717 constexpr const_reverse_edge_weight_iterator rend_edge_weights(const edge_index_type node) const
1718 {
1719 return const_reverse_edge_weight_iterator{this->rend_edges(node)};
1720 }
1721
1722 [[nodiscard]]
1723 constexpr const_reverse_edge_weight_iterator crbegin_edge_weights(const edge_index_type node) const
1724 {
1725 return const_reverse_edge_weight_iterator{this->rbegin_edges(node)};
1726 }
1727
1728 [[nodiscard]]
1729 constexpr const_reverse_edge_weight_iterator crend_edge_weights(const edge_index_type node) const
1730 {
1731 return const_reverse_edge_weight_iterator{this->rend_edges(node)};
1732 }
1733
1734 [[nodiscard]]
1735 constexpr edge_weights_range edge_weights(const edge_index_type node) { return {begin_edge_weights(node), end_edge_weights(node)}; }
1736
1737 [[nodiscard]]
1738 constexpr const_edge_weights_range edge_weights(const edge_index_type node) const { return {begin_edge_weights(node), end_edge_weights(node)}; }
1739
1740 [[nodiscard]]
1741 constexpr const_edge_weights_range cedge_weights(const edge_index_type node) const { return {begin_edge_weights(node), end_edge_weights(node)}; }
1742 protected:
1743 using connectivity_base<graph_flavour::directed, EdgeStorage>::connectivity_base;
1744 };
1745 }
1746}
A collection of constexpr algorithms.
constexpr void cluster(Iter begin, Iter end, Comp comp={}, Proj proj={})
An algorithm which clusters together elements which compare equal.
Definition: Algorithms.hpp:143
Helper for dealing with allocator propagation during copy assignment.
Traits for data structures.
Meta-programming elements for graph implementation.
Error messages for graphs.
Traits and Concepts for graphs.
Traits and Concepts for Sharing Policies.
Traits which are sufficiently general to appear in the sequoia namespace.
Graph connectivity_base, used as a building block for concrete graphs.
Definition: Connectivity.hpp:184
Definition: Connectivity.hpp:1641
Definition: Connectivity.hpp:1612
Definition: Connectivity.hpp:52
Definition: Connectivity.hpp:127
Definition: Connectivity.hpp:96
An iterator with policies controlling dereferencing and auxiliary data.
Definition: Iterator.hpp:234
A concept for allocators.
Definition: Concepts.hpp:48
Concept to work around the fact that currently the stl typically underconstrains operator==.
Definition: Concepts.hpp:105
A concept similar to std::constructible_from, but which considers braced-init.
Definition: Concepts.hpp:75
Helper class to assist with copy assignment for allocator-aware types.
Definition: AssignmentUtilities.hpp:67
static constexpr void assign(T &to, const T &from, AllocGetters... allocGetters)
Definition: AssignmentUtilities.hpp:70
Standard meta-programming utility.
Definition: TypeTraits.hpp:27
Definition: Connectivity.hpp:152
Definition: Connectivity.hpp:172