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] <class... PartnerArgs> (const_edge_iterator iter, PartnerArgs&&... partnerArgs){
273 return this->set_partner_edge_weight(iter, std::forward<PartnerArgs>(partnerArgs)...);
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& other)
390 {
391 if(&other != 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, other, 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(
613 [node](const auto targetNode) { return targetNode >= node; },
614 [](const auto index) { return index + 1; }
615 );
616
617 return node;
618 }
619
620 void erase_node(const size_type node)
621 {
622 graph_errors::check_node_index_range("erase_node", order(), node);
623
624 if constexpr(!is_directed(flavour))
625 {
626 std::vector<size_type> partitionsToVisit{};
627
628 for(const auto& edge : m_Edges.partition(node))
629 {
630 const auto target{edge.target_node()};
631 if(target != node) partitionsToVisit.push_back(target);
632 }
633
634 std::ranges::sort(partitionsToVisit);
635 auto duplicates{std::ranges::unique(partitionsToVisit)};
636
637 for(const auto partition : std::ranges::subrange{partitionsToVisit.begin(), duplicates.begin()})
638 {
639 auto fn{[node](const edge_type& e) { return e.target_node() == node; }};
640
641 if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
642 {
643 // TO DO: consider reversing iteration
644 for(auto iter{m_Edges.begin_partition(partition)}; iter != m_Edges.end_partition(partition);)
645 {
646 if(fn(*iter))
647 {
648 iter = m_Edges.erase_from_partition(iter);
649 decrement_comp_indices(iter, m_Edges.end_partition(partition), 1);
650 }
651 else
652 {
653 ++iter;
654 }
655 }
656 }
657 else
658 {
659 erase_from_partition_if(partition, fn);
660 }
661 }
662 }
663 else
664 {
665 for(size_type i{}; i < m_Edges.num_partitions(); ++i)
666 {
667 if(i == node) continue;
668 erase_from_partition_if(i, [node](const edge_type& e) {
669 return (e.target_node() == node);
670 });
671 }
672 }
673
674 m_Edges.erase_slot(node);
675 fix_edge_data(
676 [node](const auto targetNode) { return targetNode > node; },
677 [](const auto index) { return index - 1; }
678 );
679 }
680
681 template<class... Args>
682 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>))
683 void join(const edge_index_type node1, const edge_index_type node2, Args&&... args)
684 {
685 graph_errors::check_node_index_range("join", order(), node1, node2);
686
687 add_to_partition(node1, node2, std::forward<Args>(args)...);
688
689 if constexpr(!is_directed(flavour))
690 {
691 reciprocal_join(node1, node2);
692 }
693 }
694
695 template<class... Args>
696 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>))
697 void join(const edge_index_type node1, const edge_index_type node2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
698 {
699 graph_errors::check_node_index_range("join", order(), node1, node2);
700
701 add_to_partition(node1, node2, meta1, std::forward<Args>(args)...);
702
703 if constexpr(!is_directed(flavour))
704 {
705 reciprocal_join(node1, node2, std::move(meta2));
706 }
707 }
708
709 template<class... Args>
710 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>)
711 std::pair<const_edge_iterator, const_edge_iterator>
712 insert_join(const_edge_iterator citer1, const_edge_iterator citer2, Args&&... args)
713 {
714 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
715 const auto dist2{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node2), citer2))};
716 if(node1 == node2) return insert_join(citer1, dist2, std::forward<Args>(args)...);
717
718 citer1 = insert_to_partition(citer1, node2, dist2, std::forward<Args>(args)...);
719 return insert_reciprocal_join(citer1, cbegin_edges(node2) + dist2);
720 }
721
722 template<class... Args>
723 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>)
724 std::pair<const_edge_iterator, const_edge_iterator>
725 insert_join(const_edge_iterator citer1, const_edge_iterator citer2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
726 {
727 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
728 const auto dist2{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node2), citer2))};
729 if(node1 == node2) return insert_join(citer1, dist2, meta1, meta2, std::forward<Args>(args)...);
730
731 citer1 = insert_to_partition(citer1, node2, dist2, meta1, std::forward<Args>(args)...);
732 return insert_reciprocal_join(citer1, cbegin_edges(node2) + dist2, meta2);
733 }
734
735 template<class... Args>
736 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>)
737 std::pair<const_edge_iterator, const_edge_iterator>
738 insert_join(const_edge_iterator citer1, const edge_index_type pos2, Args&&... args)
739 {
740 const auto node{citer1.partition_index()};
741 graph_errors::check_edge_insertion_index("insert_join", node, std::ranges::distance(cedges(node)) + 1, pos2);
742
743 citer1 = insert_to_partition(citer1, node, pos2, std::forward<Args>(args)...);
744
745 return insert_reciprocal_join(citer1, pos2);
746 }
747
748 template<class... Args>
749 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>)
750 std::pair<const_edge_iterator, const_edge_iterator>
751 insert_join(const_edge_iterator citer1, const edge_index_type pos2, edge_meta_data_type meta1, edge_meta_data_type meta2, Args&&... args)
752 {
753 const auto node{citer1.partition_index()};
754 graph_errors::check_edge_insertion_index("insert_join", node, std::ranges::distance(cedges(node)) + 1, pos2);
755
756 citer1 = insert_to_partition(citer1, node, pos2, meta1, std::forward<Args>(args)...);
757
758 return insert_reciprocal_join(citer1, pos2, meta2);
759 }
760
761 void erase_edge(const_edge_iterator citer)
762 {
763 // TO DO: ensure strong exception guarantee (maybe just insist on
764 // noexcept move assignment for m_Edges
765
766 if(!order() || (citer == cend_edges(citer.partition_index()))) return;
767
768 if constexpr (!is_directed(flavour))
769 {
770 const auto source{citer.partition_index()};
771 const auto partner{citer->target_node()};
772
773 auto pred{
774 [=](const edge_type& potentialPartner){
775 if(potentialPartner.target_node() == source)
776 {
777 if constexpr(std::is_empty_v<edge_weight_type>)
778 return true;
779 else if constexpr(shared_weight_v)
780 return std::addressof(citer->weight()) == std::addressof(potentialPartner.weight());
781 else if constexpr(std::is_empty_v<edge_meta_data_type>)
782 return citer->weight() == potentialPartner.weight();
783 else
785 }
786
787 return false;
788 }
789 };
790
791 if(source != partner)
792 {
793 if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
794 {
795 const auto partnerLocalIndex{citer->complementary_index()};
796 citer = m_Edges.erase_from_partition(citer);
797 decrement_comp_indices(to_edge_iterator(citer), m_Edges.end_partition(source), 1);
798
799 auto partnerIter{m_Edges.erase_from_partition(partner, partnerLocalIndex)};
800 decrement_comp_indices(partnerIter, m_Edges.end_partition(partner), 1);
801 }
802 else
803 {
804 auto found{std::ranges::find_if(cedges(partner), pred)};
805
806 if(found == cend_edges(partner))
807 throw std::logic_error{graph_errors::erase_edge_error(partner, {source, static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(source), citer))})};
808
809 const auto partnerDist{std::ranges::distance(cbegin_edges(partner), found)};
810
811 m_Edges.erase_from_partition(citer);
812 m_Edges.erase_from_partition(cbegin_edges(partner) + partnerDist);
813 }
814 }
815 else
816 {
817 if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
818 {
819 const auto compIndex{citer->complementary_index()};
820 const auto pos{std::ranges::distance(cbegin_edges(source), citer)};
821 const auto separation{std::ranges::distance(citer, cbegin_edges(source) + compIndex)};
822 if((separation == 1) || (separation == -1))
823 {
824 const auto delta{separation == 1 ? 0 : -1};
825 citer = m_Edges.erase_from_partition(citer+delta, citer+(2+delta));
826
827 decrement_comp_indices(to_edge_iterator(citer), m_Edges.end_partition(source), 2);
828 }
829 else
830 {
831 const bool negativeSeparation{separation < 0};
832
833 auto erase{
834 [this, source](auto index){
835 auto i{m_Edges.erase_from_partition(m_Edges.begin_partition(source) + index)};
836 decrement_comp_indices(i, m_Edges.end_partition(source), 1);
837 }
838 };
839
840 erase(negativeSeparation ? pos : compIndex);
841 erase(negativeSeparation ? compIndex : pos);
842 }
843 }
844 else
845 {
846 auto found{std::ranges::find_if(cbegin_edges(source), citer, pred)};
847 if(found == citer) found = std::ranges::find_if(std::ranges::next(citer), cend_edges(source), pred);
848
849 if(found == cend_edges(source))
850 throw std::logic_error{graph_errors::erase_edge_error(source, {source, static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(source), citer))})};
851
852 auto dist{std::ranges::distance(cbegin_edges(source), citer)};
853 if(std::ranges::distance(cbegin_edges(source), found) < dist) --dist;
854
855 m_Edges.erase_from_partition(found);
856 m_Edges.erase_from_partition(cbegin_edges(source) + dist);
857 }
858 }
859 }
860 else
861 {
862 m_Edges.erase_from_partition(citer);
863 }
864 }
865
866 void clear() noexcept
867 {
868 m_Edges.clear();
869 }
870
871 template<class Comp, class Proj = std::identity>
872 constexpr void sort_edges(const_edge_iterator begin, const_edge_iterator end, Comp comp, Proj proj = {})
873 requires ((edge_type::flavour == edge_flavour::partial) && std::sortable<edge_iterator, Comp, Proj>)
874 {
875 if(begin.partition_index() != end.partition_index()) return;
876
877 std::ranges::sort(to_edge_iterator(begin), to_edge_iterator(end), std::move(comp), std::move(proj));
878 }
879
880 template<class Comp, class Proj = std::identity>
881 constexpr void sort_edges(const_edges_range r, Comp comp, Proj proj = {})
882 requires ((edge_type::flavour == edge_flavour::partial) && std::sortable<edge_iterator, Comp, Proj>)
883 {
884 sort_edges(r.begin(), r.end(), std::move(comp), std::move(proj));
885 }
886
887 template<class Comp, class Proj = std::identity>
888 constexpr void stable_sort_edges(const_edge_iterator begin, const_edge_iterator end, Comp comp, Proj proj = {})
889 requires ((edge_type::flavour == edge_flavour::partial) && merge_sortable<edge_iterator, Comp, Proj>)
890 {
891 if(begin.partition_index() != end.partition_index()) return;
892
893 sequoia::stable_sort(to_edge_iterator(begin), to_edge_iterator(end), std::move(comp), std::move(proj));
894 }
895
896 template<class Comp, class Proj = std::identity>
897 constexpr void stable_sort_edges(const_edges_range r, Comp comp, Proj proj = {})
898 requires ((edge_type::flavour == edge_flavour::partial) && merge_sortable<edge_iterator, Comp, Proj>)
899 {
900 stable_sort_edges(r.begin(), r.end(), std::move(comp), std::move(proj));
901 }
902
903 private:
904 constexpr static bool direct_init_v{std::is_same_v<edge_type, edge_init_type>};
905 constexpr static bool direct_copy_v{direct_init_v};
906 constexpr static bool shared_weight_v{graph_impl::has_shared_weight_v<edge_type>};
907
908 // private data
909 edge_storage_type m_Edges;
910
911 // helper methods
912
913 template<class Pred>
914 void erase_from_partition_if(const edge_index_type partitionIndex, Pred pred)
915 {
916 auto discarded{std::ranges::remove_if(m_Edges.begin_partition(partitionIndex), m_Edges.end_partition(partitionIndex), pred)};
917 m_Edges.erase_from_partition(discarded.begin(), discarded.end());
918 }
919
920 template<alloc... Allocators>
921 [[nodiscard]]
922 constexpr edge_storage_type make_edges(edges_initializer edges, const Allocators&... as)
923 {
924 return process_edges(validate(preprocess(edges)), as...);
925 }
926
927 [[nodiscard]]
928 constexpr static const edges_initializer& preprocess(const edges_initializer& edges)
929 requires (is_embedded(flavour) || is_directed(flavour))
930 {
931 return edges;
932 }
933
934 [[nodiscard]]
935 constexpr static edge_storage_type preprocess(edges_initializer edges)
936 requires (direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
937 {
938 return preprocess(edge_storage_type{edges});
939 }
940
941 [[nodiscard]]
942 constexpr static auto preprocess(edges_initializer edges)
943 requires (!direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
944 {
945 using namespace data_structures;
946 using sequence_t = partitioned_sequence<edge_init_type>;
947
948 return preprocess(sequence_t{edges});
949 }
950
951 template<class IntermediateEdges>
952 [[nodiscard]]
953 constexpr static IntermediateEdges preprocess(IntermediateEdges edges)
954 requires (!is_embedded(flavour) && !is_directed(flavour))
955 {
956 constexpr bool clusterEdges{!std::is_empty_v<edge_weight_type> && !std::totally_ordered<edge_weight_type>};
957
958 for(edge_index_type i{}; i < edges.num_partitions(); ++i)
959 {
960 sequoia::stable_sort(edges.begin_partition(i), edges.end_partition(i), graph_impl::edge_comparer{});
961
962 if constexpr(clusterEdges)
963 {
964 auto lowerIter{edges.begin_partition(i)}, upperIter{edges.begin_partition(i)};
965 while(lowerIter != edges.end_partition(i))
966 {
967 upperIter = std::ranges::find_if_not(upperIter, edges.end_partition(i), [target{lowerIter->target_node()}](const auto& wt) { return target == wt.target_node(); });
968
969 sequoia::cluster(lowerIter, upperIter, [](const auto& e1, const auto& e2) {
970 return e1.weight() == e2.weight();
971 });
972
973 lowerIter = upperIter;
974 }
975 }
976 }
977
978 return edges;
979 }
980
981 [[nodiscard]]
982 constexpr static const edges_initializer& validate(const edges_initializer& edges)
983 requires (is_embedded(flavour))
984 {
985 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
986 {
987 const auto nodeIndex{static_cast<std::size_t>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
988 const auto& nodeEdges{*nodeEdgesIter};
989 for(auto edgeIter{nodeEdges.begin()}; edgeIter != nodeEdges.end(); ++edgeIter)
990 {
991 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(nodeEdges.begin(), edgeIter))};
992 const auto& edge{*edgeIter};
993 const auto target{edge.target_node()};
994
995 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "target", edges.size(), target);
996 const auto compIndex{edge.complementary_index()};
997
998 const bool doValidate{
999 [&]() {
1000 if constexpr(!is_directed(flavour)) return true;
1001 else return (edge.target_node() != nodeIndex) || (edge.source_node() == edge.target_node());
1002 }()
1003 };
1004
1005 if(doValidate)
1006 {
1007 auto targetEdgesIter{edges.begin() + target};
1008 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "complementary", targetEdgesIter->size(), compIndex);
1009
1010 if((target == nodeIndex) && (compIndex == edgeIndex))
1011 {
1012 throw std::logic_error{graph_errors::self_referential_error({nodeIndex, edgeIndex}, target, compIndex)};
1013 }
1014 else if(const auto& targetEdge{*(targetEdgesIter->begin() + compIndex)}; targetEdge.complementary_index() != edgeIndex)
1015 {
1016 throw std::logic_error{graph_errors::reciprocated_error_message({nodeIndex, edgeIndex}, "complementary", targetEdge.complementary_index(), edgeIndex)};
1017 }
1018 else
1019 {
1020 if constexpr(!is_directed(flavour))
1021 {
1022 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", targetEdge.target_node(), nodeIndex);
1023 }
1024 else
1025 {
1026 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", targetEdge.target_node(), target);
1027 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "source", targetEdge.source_node(), edge.source_node());
1028
1029 if constexpr(is_embedded(flavour))
1030 {
1031 graph_errors::check_inversion_consistency(nodeIndex, {edgeIndex, edge.inverted()}, {compIndex, targetEdge.inverted()});
1032 }
1033 }
1034
1035 if constexpr(!std::is_empty_v<edge_weight_type>)
1036 {
1037 if(edge.weight() != targetEdge.weight())
1038 throw std::logic_error{graph_errors::mismatched_weights_message("process_complementary_edges", {nodeIndex, edgeIndex})};
1039 }
1040 }
1041 }
1042
1043 if constexpr(is_directed(flavour))
1044 {
1045 const auto source{edge.source_node()};
1046 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "source", edges.size(), source);
1047 graph_errors::check_embedded_edge(nodeIndex, source, target);
1048
1049 if((edge.target_node() == nodeIndex) && (edge.source_node() != edge.target_node()))
1050 {
1051 auto sourceEdgesIter{edges.begin() + source};
1052 graph_errors::check_edge_index_range("process_complementary_edges", {nodeIndex, edgeIndex}, "complementary", sourceEdgesIter->size(), compIndex);
1053
1054 const auto& sourceEdge{*(sourceEdgesIter->begin() + compIndex)};
1055 graph_errors::check_reciprocated_index({nodeIndex, edgeIndex}, "target", sourceEdge.target_node(), nodeIndex);
1056 }
1057 }
1058 }
1059 }
1060
1061 return edges;
1062 }
1063
1064 [[nodiscard]]
1065 constexpr static const edges_initializer& validate(const edges_initializer& edges)
1066 requires (!is_embedded(flavour) && is_directed(flavour))
1067 {
1068 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1069 {
1070 const auto nodeIndex{static_cast<std::size_t>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1071 const auto& nodeEdges{*nodeEdgesIter};
1072 for(auto edgeIter{nodeEdges.begin()}; edgeIter != nodeEdges.end(); ++edgeIter)
1073 {
1074 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(nodeEdges.begin(), edgeIter))};
1075 const auto& edge{*edgeIter};
1076
1077 const auto target{edge.target_node()};
1078 graph_errors::check_edge_index_range("process_edges", {nodeIndex, edgeIndex}, "target", edges.size(), target);
1079 }
1080 }
1081
1082 return edges;
1083 }
1084
1085
1086 template<class Edges>
1087 using partition_iterator_range = std::ranges::subrange<typename Edges::const_partition_iterator>;
1088
1089 template<class IntermediateEdges>
1090 [[nodiscard]]
1091 constexpr static decltype(auto) validate(IntermediateEdges&& edges)
1092 requires (!is_embedded(flavour) && !is_directed(flavour))
1093 {
1094 using range_t = partition_iterator_range<IntermediateEdges>;
1095 using namespace graph_errors;
1096
1097 visit_edges(
1098 edges,
1099 []() {},
1100 [](edge_index_type i, range_t hostRange) {
1101 if(std::ranges::distance(hostRange) % 2) throw std::logic_error{odd_num_loops_error("connectivity_base", i)};
1102 },
1103 [&](edge_index_type i, edge_index_type target, range_t hostRange, range_t targetRange) {
1104 const auto brokenEdge{
1105 [&, i, target, hostRange, targetRange]() -> std::optional<edge_indices> {
1106 if(auto reciprocalCount{std::ranges::distance(targetRange)}; !reciprocalCount)
1107 {
1108 return edge_indices{i, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(i), hostRange.begin()))};
1109 }
1110 else if(auto count{std::ranges::distance(hostRange)}; count > reciprocalCount)
1111 {
1112 return edge_indices{i, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(i), hostRange.begin()) + reciprocalCount)};
1113 }
1114 else if(count < reciprocalCount)
1115 {
1116 return edge_indices{target, static_cast<std::size_t>(std::ranges::distance(edges.cbegin_partition(target), targetRange.begin()) + count)};
1117 }
1118
1119 return {};
1120 }()
1121 };
1122
1123 if(brokenEdge)
1124 throw std::logic_error{absent_reciprocated_partial_edge_message("connectivity_base", brokenEdge.value())};
1125 }
1126 );
1127
1128 return std::forward<IntermediateEdges>(edges);
1129 }
1130
1131 template<std::ranges::view View>
1132 constexpr static auto find_cluster_end(View v) -> decltype(v.begin())
1133 {
1134 return !v.empty() ? std::ranges::find_if_not(v, [&front{v.front()}](const auto& e){ return e == front; }) : v.end();
1135 }
1136
1137 template<class Edges, alloc... Allocators>
1138 [[nodiscard]]
1139 constexpr edge_storage_type process_edges(Edges&& orderedEdges, const Allocators&... as)
1140 requires direct_init_v
1141 {
1142 return {std::forward<Edges>(orderedEdges), as...};
1143 }
1144
1145 template<alloc... Allocators>
1146 [[nodiscard]]
1147 constexpr edge_storage_type process_edges(edges_initializer edges, const Allocators&... as)
1148 requires (!direct_init_v && !is_embedded(flavour))
1149 {
1150 edge_storage_type storage(as...);
1151 storage.reserve_partitions(edges.size());
1152
1153 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1154 {
1155 storage.add_slot();
1156
1157 const auto nodeIndex{static_cast<edge_index_type>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1158 const auto& nodeEdges{*nodeEdgesIter};
1159 for(const auto& edge : nodeEdges)
1160 {
1161 storage.push_back_to_partition(nodeIndex, edge.target_node(), edge.weight());
1162 }
1163 }
1164
1165 return storage;
1166 }
1167
1168 template<alloc... Allocators>
1169 [[nodiscard]]
1170 constexpr edge_storage_type process_edges(edges_initializer edges, const Allocators&... as)
1171 requires (!direct_init_v && is_embedded(flavour))
1172 {
1173 edge_storage_type storage(as...);
1174 storage.reserve_partitions(edges.size());
1175
1176 for(auto nodeEdgesIter{edges.begin()}; nodeEdgesIter != edges.end(); ++nodeEdgesIter)
1177 {
1178 storage.add_slot();
1179
1180 const auto nodeIndex{static_cast<edge_index_type>(std::ranges::distance(edges.begin(), nodeEdgesIter))};
1181 const auto& nodeEdges{*nodeEdgesIter};
1182 for(const edge_init_type& edge : nodeEdges)
1183 {
1184 storage.push_back_to_partition(nodeIndex, edge_type{edge});
1185 }
1186 }
1187
1188 return storage;
1189 }
1190
1191 template<class Edges, alloc... Allocators>
1192 [[nodiscard]]
1193 constexpr edge_storage_type process_edges(const Edges& orderedEdges, const Allocators&... as)
1194 requires (!direct_init_v && !is_embedded(flavour) && !is_directed(flavour))
1195 {
1196 using range_t = partition_iterator_range<Edges>;
1197
1198 edge_storage_type storage(as...);
1199 storage.reserve_partitions(orderedEdges.num_partitions());
1200
1201 auto addToStorage{
1202 [&storage,this](edge_index_type host, edge_index_type target, edge_index_type compIndex, range_t hostRange){
1203 storage.push_back_to_partition(host, (compIndex == npos) ? edge_type{hostRange.front()}
1204 : edge_type{target, *(cbegin_edges(target) + compIndex)});
1205 }
1206 };
1207
1208 visit_edges(
1209 orderedEdges,
1210 [&storage]() { storage.add_slot(); },
1211 [addToStorage,&orderedEdges](edge_index_type host, range_t hostRange) {
1212 for(; hostRange.begin() != hostRange.end(); hostRange.advance(1))
1213 {
1214 const bool hasCompIndex{(std::ranges::distance(hostRange) % 2) && shared_weight_v};
1215 const auto compIndex{hasCompIndex ? static_cast<edge_index_type>(std::ranges::distance(orderedEdges.cbegin_partition(host), hostRange.begin()) - 1) : npos};
1216
1217 addToStorage(host, host, compIndex, hostRange);
1218 }
1219 },
1220 [addToStorage,&orderedEdges](edge_index_type host, edge_index_type target, range_t hostRange, range_t targetRange) {
1221 for(; hostRange.begin() != hostRange.end(); hostRange.advance(1))
1222 {
1223 const bool hasCompIndex{(host > target) && shared_weight_v};
1224 const auto compIndex{hasCompIndex ? static_cast<edge_index_type>(std::ranges::distance(orderedEdges.cbegin_partition(target), targetRange.end()) - std::ranges::distance(hostRange)) : npos};
1225
1226 addToStorage(host, target, compIndex, hostRange);
1227 }
1228 }
1229 );
1230
1231 return storage;
1232 }
1233
1234 template<class Edges,
1235 std::invocable PerNodeFn,
1236 std::invocable<edge_index_type, partition_iterator_range<Edges>> PerLoopFn,
1237 std::invocable<edge_index_type, edge_index_type, partition_iterator_range<Edges>, partition_iterator_range<Edges>> PerLinkFn
1238 >
1239 constexpr static void visit_edges(const Edges& orderedEdges, PerNodeFn perNode, PerLoopFn perLoop, PerLinkFn perLink)
1240 requires (!is_embedded(flavour) && !is_directed(flavour))
1241 {
1242 constexpr bool clusterEdges{!std::is_empty_v<edge_weight_type> && !std::totally_ordered<edge_weight_type>};
1243
1244 for(edge_index_type i{}; i < orderedEdges.num_partitions(); ++i)
1245 {
1246 perNode();
1247
1248 auto lowerIter{orderedEdges.cbegin_partition(i)}, upperIter{orderedEdges.cbegin_partition(i)};
1249 while(lowerIter != orderedEdges.cend_partition(i))
1250 {
1251 upperIter = std::ranges::upper_bound(lowerIter, orderedEdges.cend_partition(i), *lowerIter, graph_impl::edge_comparer{});
1252 if constexpr(clusterEdges) upperIter = find_cluster_end(std::ranges::subrange{lowerIter, upperIter});
1253
1254 if(const auto target{lowerIter->target_node()}; target == i)
1255 {
1256 perLoop(i, {lowerIter, upperIter});
1257 }
1258 else
1259 {
1260 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());
1261
1262 const auto comparisonEdge{
1263 [lowerIter, i]() {
1264 auto compEdge{*lowerIter};
1265 compEdge.target_node(i);
1266 return compEdge;
1267 }()
1268 };
1269
1270 auto eqrange{std::ranges::equal_range(orderedEdges.partition(target), comparisonEdge, graph_impl::edge_comparer{})};
1271
1272 if constexpr(clusterEdges)
1273 {
1274 auto clusters{std::views::drop_while(eqrange, [&wt{lowerIter->weight()}](const auto& e) { return e.weight() != wt; })};
1275 eqrange = {clusters.begin(), find_cluster_end(clusters)};
1276 }
1277
1278 perLink(i, target, {lowerIter, upperIter}, eqrange);
1279 }
1280
1281 lowerIter = upperIter;
1282 }
1283 }
1284 }
1285
1286 template<alloc... Allocators>
1287 [[nodiscard]]
1288 edge_storage_type copy_edges(const connectivity_base& in, const Allocators&... as)
1289 requires direct_copy_v
1290 {
1291 return edge_storage_type{in.m_Edges, as...};
1292 }
1293
1294 [[nodiscard]]
1295 edge_storage_type copy_edges(const connectivity_base& in)
1296 requires (!direct_copy_v)
1297 {
1298 if constexpr(has_partitions_allocator<edge_storage_type>)
1299 {
1300 return copy_edges(in, in.m_Edges.get_allocator(), in.m_Edges.get_partitions_allocator());
1301 }
1302 else
1303 {
1304 return copy_edges(in, in.m_Edges.get_allocator());
1305 }
1306 }
1307
1308 template<alloc... Allocators>
1309 [[nodiscard]]
1310 edge_storage_type copy_edges(const connectivity_base& in, const Allocators&... as)
1311 requires (!direct_copy_v && (sizeof...(Allocators) > 0))
1312 {
1313 edge_storage_type storage({std::allocator_traits<Allocators>::select_on_container_copy_construction(as)}...);
1314
1315 if constexpr(edge_type::flavour == edge_flavour::partial)
1316 {
1317 copy_edges(in, storage, graph_impl::edge_maker<edge_type, edge_storage_type>{storage});
1318 }
1319 else if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1320 {
1321 auto processor{
1322 [this, &storage](const size_type node, const_edge_iterator first, const_edge_iterator i) {
1323 const auto dist{static_cast<edge_index_type>(std::ranges::distance(first, i))};
1324 const bool encountered{(i->target_node() < node)
1325 || ((i->target_node() == node) && (i->complementary_index() < dist))};
1326
1327 if(!encountered)
1328 {
1329 storage.push_back_to_partition(node, i->target_node(), i->complementary_index(), i->weight());
1330 }
1331 else
1332 {
1333 const auto compI{i->complementary_index()};
1334 storage.push_back_to_partition(node, i->target_node(), compI, *(cbegin_edges(i->target_node()) + compI));
1335 }
1336 }
1337 };
1338
1339 copy_edges(in, storage, processor);
1340 }
1341
1342 return storage;
1343 }
1344
1345 template<std::invocable<size_type, const_edge_iterator, const_edge_iterator> Processor>
1346 void copy_edges(const connectivity_base& in, edge_storage_type& storage, Processor processor)
1347 {
1348 reserve_nodes(in.m_Edges.num_partitions());
1349 if constexpr(!graph_impl::has_reservable_partitions<edge_storage_type>)
1350 {
1351 storage.reserve(in.m_Edges.size());
1352 }
1353 for(size_type i{}; i<in.order(); ++i)
1354 {
1355 storage.add_slot();
1356 if constexpr(graph_impl::has_reservable_partitions<edge_storage_type>)
1357 {
1358 storage.reserve_partition(i, std::ranges::distance(in.cbegin_edges(i), in.cend_edges(i)));
1359 }
1360 for(auto inIter{in.cbegin_edges(i)}; inIter != in.cend_edges(i); ++inIter)
1361 {
1362 processor(i, in.cbegin_edges(i), inIter);
1363 }
1364 }
1365 }
1366
1367 [[nodiscard]]
1368 constexpr const_edge_iterator to_const_edge_iterator(const_reverse_edge_iterator criter) const
1369 {
1370 const auto source{criter.partition_index()};
1371 return m_Edges.cbegin_partition(source) + std::ranges::distance(criter, m_Edges.crend_partition(source)) - 1;
1372 }
1373
1374 [[nodiscard]]
1375 constexpr edge_iterator to_edge_iterator(const_edge_iterator citer)
1376 {
1377 const auto source{citer.partition_index()};
1378 const auto dist{std::ranges::distance(cbegin_edges(source), citer)};
1379 return m_Edges.begin_partition(source) + dist;
1380 }
1381
1382 template<std::invocable<edge_weight_type&> Fn>
1383 constexpr std::invoke_result_t<Fn, edge_weight_type&> mutate_source_edge_weight(const_edge_iterator citer, Fn fn)
1384 {
1385 return fn(to_edge_iterator(citer)->weight());
1386 }
1387
1388 template<class... Args>
1389 constexpr void set_source_edge_weight(edge_iterator iter, Args&&... args)
1390 {
1391 iter->weight(std::forward<Args>(args)...);
1392 }
1393
1394 template<class Setter>
1395 requires std::is_invocable_r_v<edge_iterator, Setter, edge_iterator>
1396 constexpr const_edge_iterator manipulate_partner_edge_weight(const_edge_iterator citer, Setter setter)
1397 {
1398 const auto partner{citer->target_node()};
1399
1400 if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1401 {
1402 const auto comp{citer->complementary_index()};
1403 return setter(m_Edges.begin_partition(partner) + comp);
1404 }
1405 else
1406 {
1407
1408 auto found{end_edges(partner)};
1409 if(const auto source{citer.partition_index()}; source == partner)
1410 {
1411 for(auto iter{m_Edges.begin_partition(partner)}; iter != m_Edges.end_partition(partner); ++iter)
1412 {
1413 if(std::ranges::distance(m_Edges.begin_partition(partner), iter) == std::ranges::distance(cbegin_edges(partner), citer))
1414 continue;
1415
1416 if(*iter == *citer)
1417 {
1418 found = setter(iter);
1419 break;
1420 }
1421 }
1422 }
1423 else
1424 {
1425 for(auto iter{m_Edges.begin_partition(partner)}; iter != m_Edges.end_partition(partner); ++iter)
1426 {
1427 if((iter->target_node() == source) && (iter->weight() == citer->weight()))
1428 {
1429 found = setter(iter);
1430 break;
1431 }
1432 }
1433 }
1434
1435 if(found != end_edges(partner))
1436 {
1437 return found;
1438 }
1439 else
1440 {
1441 const auto node{citer.partition_index()};
1442 const auto edgeIndex{static_cast<std::size_t>(std::ranges::distance(cbegin_edges(node), citer))};
1443 throw std::logic_error{ graph_errors::absent_partner_weight_message("manipulate_partner_edge_weight", {node, edgeIndex}) };
1444 }
1445 }
1446 }
1447
1448 template<std::invocable<edge_weight_type&> Fn>
1449 constexpr void mutate_partner_edge_weight(const_edge_iterator citer, Fn fn)
1450 {
1451 manipulate_partner_edge_weight(citer, [fn](edge_iterator iter) -> edge_iterator { fn(iter->weight()); return iter; });
1452 }
1453
1454 template<class... Args>
1455 requires initializable_from<edge_weight_type, Args...>
1456 constexpr const_edge_iterator set_partner_edge_weight(const_edge_iterator citer, Args&&... args)
1457 {
1458 return manipulate_partner_edge_weight(citer, [&args...](edge_iterator iter) -> edge_iterator { iter->weight(std::forward<Args>(args)...); return iter; });
1459 }
1460
1461 template<class... MetaData>
1462 requires std::is_copy_constructible_v<edge_type> && (std::is_same_v<MetaData, edge_meta_data_type> && ...)
1463 void reciprocal_join(const edge_index_type node1, const edge_index_type node2, MetaData... md)
1464 {
1465 graph_impl::join_sentinel sentinel{m_Edges, node1, m_Edges.size_of_partition(node1) - 1};
1466 if constexpr(edge_type::flavour == edge_flavour::partial)
1467 {
1468 m_Edges.push_back_to_partition(node2, node1, std::move(md)..., *crbegin_edges(node1));
1469 }
1470 else if constexpr(edge_type::flavour == edge_flavour::partial_embedded)
1471 {
1472 const edge_index_type compIndex(std::ranges::distance(cbegin_edges(node1), cend_edges(node1)) - 1);
1473 m_Edges.push_back_to_partition(node2, node1, compIndex, std::move(md)..., *crbegin_edges(node1));
1474 }
1475 }
1476
1477
1478 template<class... MetaData>
1479 requires (is_embedded(flavour) && std::is_copy_constructible_v<edge_type> && (std::is_same_v<edge_meta_data_type, MetaData> && ...))
1480 std::pair<const_edge_iterator, const_edge_iterator>
1481 insert_reciprocal_join(const_edge_iterator citer1, const edge_index_type pos2, MetaData... args)
1482 {
1483 const auto node{citer1.partition_index()};
1484
1485 auto pos1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node), citer1))};
1486 if(pos2 <= pos1) ++pos1;
1487
1488 const auto dist1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node), citer1))};
1489 graph_impl::join_sentinel sentinel{m_Edges, node, dist1};
1490
1491 auto citer2{m_Edges.insert_to_partition(cbegin_edges(node) + pos2, node, pos1, std::move(args)..., *citer1)};
1492 if(pos2 > pos1)
1493 {
1494 increment_comp_indices(++to_edge_iterator(citer2), end_edges(node), 1);
1495 }
1496 else
1497 {
1498 auto iter1{begin_edges(node) + pos1};
1499 increment_comp_indices(++to_edge_iterator(citer2), iter1, 1);
1500 increment_comp_indices(iter1 + 1, end_edges(node), 1);
1501 }
1502
1503 citer1 = cbegin_edges(node) + pos1;
1504 return {citer1, citer2};
1505 }
1506
1507 template<class... MetaData>
1508 requires (is_embedded(flavour) && std::is_copy_constructible_v<edge_type> && (std::is_same_v<edge_meta_data_type, MetaData> && ...))
1509 std::pair<const_edge_iterator, const_edge_iterator>
1510 insert_reciprocal_join(const_edge_iterator citer1, const_edge_iterator citer2, MetaData... md)
1511 {
1512 const auto node1{citer1.partition_index()}, node2{citer2.partition_index()};
1513 const auto dist1{static_cast<edge_index_type>(std::ranges::distance(cbegin_edges(node1), citer1))};
1514
1515 graph_impl::join_sentinel sentinel{m_Edges, node1, dist1};
1516 citer2 = m_Edges.insert_to_partition(citer2, node1, dist1, md..., *citer1);
1517 increment_comp_indices(++to_edge_iterator(citer2), end_edges(node2), 1);
1518
1519 citer1 = cbegin_edges(node1) + dist1;
1520 return {citer1, citer2};
1521 }
1522
1523 template<class... Args>
1524 void add_to_partition(const edge_index_type node1, const edge_index_type node2, Args&&... args)
1525 {
1526 if constexpr (edge_type::flavour == edge_flavour::partial)
1527 {
1528 m_Edges.push_back_to_partition(node1, node2, std::forward<Args>(args)...);
1529 }
1530 else if constexpr (edge_type::flavour == edge_flavour::partial_embedded)
1531 {
1532 const edge_index_type dist(std::ranges::distance(cbegin_edges(node2), cend_edges(node2)));
1533 const edge_index_type i{ node1 == node2 ? dist + 1 : dist };
1534 m_Edges.push_back_to_partition(node1, node2, i, std::forward<Args>(args)...);
1535 }
1536 }
1537
1538 template<class... Args>
1539 const_edge_iterator insert_to_partition(const_edge_iterator citer1, const edge_index_type node2, const edge_index_type pos2, Args&&... args)
1540 {
1541 const auto node1{ citer1.partition_index() };
1542 citer1 = m_Edges.insert_to_partition(citer1, node2, pos2, std::forward<Args>(args)...);
1543 increment_comp_indices(++to_edge_iterator(citer1), end_edges(node1), 1);
1544
1545 return citer1;
1546 }
1547
1548 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel, class Fn>
1549 requires std::is_invocable_r_v<edge_index_type, Fn, edge_index_type>
1550 constexpr void modify_comp_indices(Iter first, Sentinel last, Fn fn) noexcept
1551 {
1552 while(first != last)
1553 {
1554 const auto source{first.partition_index()};
1555 const auto other{first->target_node()};
1556
1557 auto setPartnerCompIndex{
1558 [this, other, fn](edge_index_type cmpIndex){
1559 const auto edgeIter{m_Edges.begin_partition(other) + cmpIndex};
1560 edgeIter->complementary_index(fn(edgeIter->complementary_index()));
1561 }
1562 };
1563
1564 const auto compIndex{first->complementary_index()};
1565 if(source != other)
1566 {
1567 setPartnerCompIndex(compIndex);
1568 }
1569 else
1570 {
1571 const auto dist{static_cast<edge_index_type>(std::ranges::distance(begin_edges(source), first))};
1572 if(const auto shiftedCompIndex{fn(compIndex)}; shiftedCompIndex <= dist)
1573 {
1574 setPartnerCompIndex(compIndex);
1575 }
1576 else
1577 {
1578 setPartnerCompIndex(shiftedCompIndex);
1579 }
1580 }
1581
1582 ++first;
1583 }
1584 }
1585
1586 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel>
1587 constexpr void decrement_comp_indices(Iter first, Sentinel last, const edge_index_type num) noexcept
1588 {
1589 modify_comp_indices(first, last, [num](const auto compIndex) { return compIndex >= num ? compIndex - num : edge_index_type{}; });
1590 }
1591
1592 template<std::input_or_output_iterator Iter, std::sentinel_for<Iter> Sentinel>
1593 constexpr void increment_comp_indices(Iter first, Sentinel last, const edge_index_type num) noexcept
1594 {
1595 modify_comp_indices(first, last, [num](const auto compIndex) { return compIndex + num; });
1596 }
1597
1598 template<class Pred, class Modifier>
1599 constexpr void fix_edge_data(Pred pred, Modifier modifier) noexcept
1600 {
1601 for(size_type i{}; i < m_Edges.num_partitions(); ++i)
1602 {
1603 for(auto& edge : m_Edges.partition(i))
1604 {
1605 const auto target{edge.target_node()};
1606 if(pred(target)) edge.target_node(modifier(target));
1607 }
1608 }
1609 }
1610 };
1611
1612 template<std::input_or_output_iterator Iterator>
1614 {
1615 public:
1616 constexpr static bool is_const{is_const_reference_v<typename std::iterator_traits<Iterator>::reference>};
1617
1618 using value_type = typename std::iterator_traits<Iterator>::value_type::weight_type;
1619 using reference = std::conditional_t<is_const, const value_type&, value_type&>;
1620
1621 constexpr edge_weight_dereference_policy() = default;
1623
1624 [[nodiscard]]
1625 friend constexpr bool operator==(const edge_weight_dereference_policy&, const edge_weight_dereference_policy&) noexcept = default;
1626
1627 [[nodiscard]]
1628 static reference get(Iterator i)
1629 {
1630 return i->weight();
1631 }
1632 protected:
1633 constexpr edge_weight_dereference_policy(edge_weight_dereference_policy&&) noexcept = default;
1634
1636
1637 constexpr edge_weight_dereference_policy& operator=(const edge_weight_dereference_policy&) = default;
1638 constexpr edge_weight_dereference_policy& operator=(edge_weight_dereference_policy&&) noexcept = default;
1639 };
1640
1641 template<graph_flavour Flavour, class EdgeStorage>
1642 class connectivity : public connectivity_base<Flavour, EdgeStorage>
1643 {
1644 protected:
1645 using connectivity_base<Flavour, EdgeStorage>::connectivity_base;
1646 };
1647
1648 template<class EdgeStorage>
1649 requires (!std::is_empty_v<typename EdgeStorage::value_type::weight_type>)
1651 {
1652 public:
1654
1659 using edge_weights_range = std::ranges::subrange<edge_weight_iterator>;
1660 using const_edge_weights_range = std::ranges::subrange<const_edge_weight_iterator>;
1661
1662 using edge_index_type = typename base_type::edge_index_type;
1663
1664 [[nodiscard]]
1665 constexpr edge_weight_iterator begin_edge_weights(const edge_index_type node)
1666 {
1667 return edge_weight_iterator{this->begin_edges(node)};
1668 }
1669
1670 [[nodiscard]]
1671 constexpr edge_weight_iterator end_edge_weights(const edge_index_type node)
1672 {
1673 return edge_weight_iterator{this->end_edges(node)};
1674 }
1675
1676 [[nodiscard]]
1677 constexpr const_edge_weight_iterator begin_edge_weights(const edge_index_type node) const
1678 {
1679 return const_edge_weight_iterator{this->begin_edges(node)};
1680 }
1681
1682 [[nodiscard]]
1683 constexpr const_edge_weight_iterator end_edge_weights(const edge_index_type node) const
1684 {
1685 return const_edge_weight_iterator{this->end_edges(node)};
1686 }
1687
1688 [[nodiscard]]
1689 constexpr const_edge_weight_iterator cbegin_edge_weights(const edge_index_type node) const
1690 {
1691 return const_edge_weight_iterator{this->begin_edges(node)};
1692 }
1693
1694 [[nodiscard]]
1695 constexpr const_edge_weight_iterator cend_edge_weights(const edge_index_type node) const
1696 {
1697 return const_edge_weight_iterator{this->end_edges(node)};
1698 }
1699
1700 [[nodiscard]]
1701 constexpr reverse_edge_weight_iterator rbegin_edge_weights(const edge_index_type node)
1702 {
1703 return reverse_edge_weight_iterator{this->rbegin_edges(node)};
1704 }
1705
1706 [[nodiscard]]
1707 constexpr reverse_edge_weight_iterator rend_edge_weights(const edge_index_type node)
1708 {
1709 return reverse_edge_weight_iterator{this->rend_edges(node)};
1710 }
1711
1712 [[nodiscard]]
1713 constexpr const_reverse_edge_weight_iterator rbegin_edge_weights(const edge_index_type node) const
1714 {
1715 return const_reverse_edge_weight_iterator{this->rbegin_edges(node)};
1716 }
1717
1718 [[nodiscard]]
1719 constexpr const_reverse_edge_weight_iterator rend_edge_weights(const edge_index_type node) const
1720 {
1721 return const_reverse_edge_weight_iterator{this->rend_edges(node)};
1722 }
1723
1724 [[nodiscard]]
1725 constexpr const_reverse_edge_weight_iterator crbegin_edge_weights(const edge_index_type node) const
1726 {
1727 return const_reverse_edge_weight_iterator{this->rbegin_edges(node)};
1728 }
1729
1730 [[nodiscard]]
1731 constexpr const_reverse_edge_weight_iterator crend_edge_weights(const edge_index_type node) const
1732 {
1733 return const_reverse_edge_weight_iterator{this->rend_edges(node)};
1734 }
1735
1736 [[nodiscard]]
1737 constexpr edge_weights_range edge_weights(const edge_index_type node) { return {begin_edge_weights(node), end_edge_weights(node)}; }
1738
1739 [[nodiscard]]
1740 constexpr const_edge_weights_range edge_weights(const edge_index_type node) const { return {begin_edge_weights(node), end_edge_weights(node)}; }
1741
1742 [[nodiscard]]
1743 constexpr const_edge_weights_range cedge_weights(const edge_index_type node) const { return {begin_edge_weights(node), end_edge_weights(node)}; }
1744 protected:
1745 using connectivity_base<graph_flavour::directed, EdgeStorage>::connectivity_base;
1746 };
1747 }
1748}
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:144
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.
Definition: PartitionedData.hpp:991
Graph connectivity_base, used as a building block for concrete graphs.
Definition: Connectivity.hpp:184
Definition: Connectivity.hpp:1643
Definition: Connectivity.hpp:1614
Definition: Connectivity.hpp:52
Definition: Connectivity.hpp:127
Definition: Connectivity.hpp:96
Definition: TestLogger.hpp:277
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