CDT++ 1.0.0
Causal Dynamical Triangulations in C++
Loading...
Searching...
No Matches
Ergodic_moves_3.hpp
Go to the documentation of this file.
1/*******************************************************************************
2 Causal Dynamical Triangulations in C++ using CGAL
3
4 Copyright © 2019 Adam Getchell
5 ******************************************************************************/
6
18
19#ifndef CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
20#define CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
21
22#include <algorithm>
23#include <array>
24#include <bit>
25#include <concepts>
26#include <cstddef>
27#include <expected>
28#include <limits>
29#include <optional>
30#include <random>
31#include <ranges>
32#include <string>
33#include <unordered_set>
34#include <utility>
35#include <vector>
36
37#include "Manifold.hpp"
38#include "Move_outcome.hpp"
39#include "Move_tracker.hpp"
40
41namespace cdt::ergodic_moves
42{
50 using Cell_container = std::vector<Cell_handle>;
54 using Edge_container = std::vector<Edge_handle>;
58 using Vertex_container = std::vector<Vertex_handle>;
61
62 namespace detail
63 {
64 using Cell_points = std::array<Point_t<3>, 4>;
65 using Edge_points = std::array<Point_t<3>, 2>;
66 using Execution = std::expected<void, MoveError>;
67
71 class ApplicableTwoThreeMove
72 {
73 Cell_points m_cell;
74 Point_t<3> m_opposite;
75
76 ApplicableTwoThreeMove(Cell_points cell, Point_t<3> opposite) noexcept
77 : m_cell{cell}, m_opposite{opposite}
78 {}
79
80 friend auto prepare_two_three(Delaunay const& triangulation,
81 Cell_handle const& candidate)
82 -> std::expected<ApplicableTwoThreeMove, MoveError>;
83 friend auto execute(Delaunay& triangulation,
84 ApplicableTwoThreeMove const& move) -> Execution;
85 };
86
88 class ApplicableThreeTwoMove
89 {
90 Edge_points m_edge;
91
92 explicit ApplicableThreeTwoMove(Edge_points edge) noexcept : m_edge{edge}
93 {}
94
95 friend auto prepare_three_two(Delaunay const& triangulation,
96 Edge_handle const& candidate)
97 -> std::expected<ApplicableThreeTwoMove, MoveError>;
98 friend auto execute(Delaunay& triangulation,
99 ApplicableThreeTwoMove const& move) -> Execution;
100 };
101
103 class ApplicableTwoSixMove
104 {
105 Cell_points m_bottom;
106 Point_t<3> m_opposite;
107
108 ApplicableTwoSixMove(Cell_points bottom, Point_t<3> opposite) noexcept
109 : m_bottom{bottom}, m_opposite{opposite}
110 {}
111
112 friend auto prepare_two_six(Delaunay const& triangulation,
113 Cell_handle const& candidate)
114 -> std::expected<ApplicableTwoSixMove, MoveError>;
115
116 public:
117 [[nodiscard]] auto bottom_points() const noexcept -> Cell_points const&
118 { return m_bottom; }
119
120 [[nodiscard]] auto opposite_point() const noexcept -> Point_t<3> const&
121 { return m_opposite; }
122 };
123
125 class ApplicableSixTwoMove
126 {
127 Point_t<3> m_vertex;
128
129 explicit ApplicableSixTwoMove(Point_t<3> vertex) noexcept
130 : m_vertex{vertex}
131 {}
132
133 friend auto prepare_six_two(Delaunay const& triangulation,
134 Vertex_handle const& candidate)
135 -> std::expected<ApplicableSixTwoMove, MoveError>;
136
137 public:
138 [[nodiscard]] auto vertex_point() const noexcept -> Point_t<3> const&
139 { return m_vertex; }
140 };
141
143 class ApplicableFourFourMove
144 {
145 Edge_points m_edge;
146 Point_t<3> m_top;
147 Point_t<3> m_bottom;
148
149 ApplicableFourFourMove(Edge_points edge, Point_t<3> top,
150 Point_t<3> bottom) noexcept
151 : m_edge{edge}, m_top{top}, m_bottom{bottom}
152 {}
153
154 friend auto prepare_four_four(Delaunay const& triangulation,
155 Edge_handle const& candidate)
156 -> std::expected<ApplicableFourFourMove, MoveError>;
157 friend auto prepare_bistellar_flip(Delaunay const& triangulation,
158 Edge_handle const& candidate,
159 Vertex_handle const& top,
160 Vertex_handle const& bottom)
161 -> std::expected<ApplicableFourFourMove, MoveError>;
162
163 public:
164 [[nodiscard]] auto edge_points() const noexcept -> Edge_points const&
165 { return m_edge; }
166
167 [[nodiscard]] auto top_point() const noexcept -> Point_t<3> const&
168 { return m_top; }
169
170 [[nodiscard]] auto bottom_point() const noexcept -> Point_t<3> const&
171 { return m_bottom; }
172 };
173
174 [[nodiscard]] constexpr auto move_error(
175 MoveFailure const reason, move_tracker::MoveType const move) noexcept
176 -> std::unexpected<MoveError>
177 {
178 return std::unexpected{
179 MoveError{.category = reason, .requested_move = move}
180 };
181 }
182
186 [[nodiscard]] inline auto same_configuration_value(
187 double const first, double const second) noexcept -> bool
188 {
189 using Representation = std::array<std::byte, sizeof(double)>;
190 return std::bit_cast<Representation>(first) ==
191 std::bit_cast<Representation>(second);
192 }
193
195 [[nodiscard]] inline auto accept_post_mutation(
196 [[maybe_unused]] Delaunay const& triangulation) noexcept -> bool
197 { return true; }
198
200 [[nodiscard]] inline auto make_manifold(Delaunay triangulation,
201 Manifold const& source) -> Manifold
202 {
203 return Manifold{
205 std::move(triangulation), source.initial_radius(),
206 source.foliation_spacing()}
207 };
208 }
209
212 [[nodiscard]] inline auto is_well_formed_edge(
213 Edge_handle const& edge) noexcept -> bool
214 {
215 static constexpr auto vertex_count = Int_precision{4};
216 auto const valid_index = [](Int_precision const index) {
217 return index >= 0 && index < vertex_count;
218 };
219
220 return edge.first != nullptr && valid_index(edge.second) &&
221 valid_index(edge.third) && edge.second != edge.third;
222 }
223
227 [[nodiscard]] inline auto finite_incident_cells(
228 Delaunay const& triangulation, Edge_handle const& edge)
229 -> std::optional<Cell_container>
230 {
231 if (!is_well_formed_edge(edge) || triangulation.dimension() != 3 ||
232 !triangulation.tds().is_edge(edge.first, edge.second, edge.third))
233 {
234 return std::nullopt;
235 }
236
237 auto circulator = triangulation.incident_cells(edge, edge.first);
238 Cell_container incident_cells;
239 do
240 { // NOLINT(cppcoreguidelines-avoid-do-while)
241 if (triangulation.is_infinite(circulator)) { return std::nullopt; }
242 incident_cells.emplace_back(circulator);
243 }
244 while (++circulator != edge.first);
245 return incident_cells;
246 }
247
248 [[nodiscard]] inline auto point_less(Point_t<3> const& left,
249 Point_t<3> const& right) -> bool
250 { return CGAL::lexicographically_xyz_smaller(left, right); }
251
252 [[nodiscard]] inline auto canonical_cell_points(Cell_handle const& cell)
253 -> std::array<Point_t<3>, 4>
254 {
255 std::array points{cell->vertex(0)->point(), cell->vertex(1)->point(),
256 cell->vertex(2)->point(), cell->vertex(3)->point()};
257 std::ranges::sort(points, point_less);
258 return points;
259 }
260
261 [[nodiscard]] inline auto canonical_edge_points(Edge_handle const& edge)
262 -> std::array<Point_t<3>, 2>
263 {
264 std::array points{edge.first->vertex(edge.second)->point(),
265 edge.first->vertex(edge.third)->point()};
266 std::ranges::sort(points, point_less);
267 return points;
268 }
269
270 [[nodiscard]] inline auto resolve_vertex(Delaunay const& triangulation,
271 Point_t<3> const& point)
272 -> std::optional<Vertex_handle>
273 {
274 Vertex_handle vertex;
275 if (triangulation.is_vertex(point, vertex)) { return vertex; }
276 return std::nullopt;
277 }
278
279 [[nodiscard]] inline auto resolve_cell(Delaunay const& triangulation,
280 Cell_points const& points)
281 -> std::optional<Cell_handle>
282 {
283 std::array<Vertex_handle, 4> vertices;
284 for (auto index = std::size_t{}; index < points.size(); ++index)
285 {
286 auto const vertex = resolve_vertex(triangulation, points[index]);
287 if (!vertex) { return std::nullopt; }
288 vertices[index] = *vertex;
289 }
290
291 Cell_handle cell;
292 if (triangulation.is_cell(vertices[0], vertices[1], vertices[2],
293 vertices[3], cell))
294 {
295 return cell;
296 }
297 return std::nullopt;
298 }
299
300 [[nodiscard]] inline auto resolve_edge(Delaunay const& triangulation,
301 Edge_points const& points)
302 -> std::optional<Edge_handle>
303 {
304 auto const first = resolve_vertex(triangulation, points[0]);
305 auto const second = resolve_vertex(triangulation, points[1]);
306 if (!first || !second) { return std::nullopt; }
307
308 Cell_handle cell;
309 int first_index{};
310 int second_index{};
311 if (triangulation.is_edge(*first, *second, cell, first_index,
312 second_index))
313 {
314 return Edge_handle{cell, first_index, second_index};
315 }
316 return std::nullopt;
317 }
318
319 [[nodiscard]] inline auto cell_precedes(Cell_handle const& left,
320 Cell_handle const& right) -> bool
321 {
322 auto const left_points = canonical_cell_points(left);
323 auto const right_points = canonical_cell_points(right);
324 return std::ranges::lexicographical_compare(left_points, right_points,
325 point_less);
326 }
327
328 [[nodiscard]] inline auto edge_precedes(Edge_handle const& left,
329 Edge_handle const& right) -> bool
330 {
331 auto const left_points = canonical_edge_points(left);
332 auto const right_points = canonical_edge_points(right);
333 return std::ranges::lexicographical_compare(left_points, right_points,
334 point_less);
335 }
336
337 inline void canonicalize(Cell_container& cells)
338 { std::ranges::sort(cells, cell_precedes); }
339
340 inline void canonicalize(Edge_container& edges)
341 { std::ranges::sort(edges, edge_precedes); }
342
343 inline void canonicalize(Vertex_container& vertices)
344 {
345 std::ranges::sort(vertices, [](auto const& left, auto const& right) {
346 return point_less(left->point(), right->point());
347 });
348 }
349
355 [[nodiscard]] inline auto canonical_edge_descriptor(
356 Delaunay const& triangulation, Edge_handle const& edge)
357 -> std::optional<Edge_handle>
358 {
359 if (!is_well_formed_edge(edge)) { return std::nullopt; }
360 auto incident_cells = finite_incident_cells(triangulation, edge);
361 if (!incident_cells || incident_cells->empty()) { return std::nullopt; }
362 canonicalize(*incident_cells);
363
364 auto first = edge.first->vertex(edge.second);
365 auto second = edge.first->vertex(edge.third);
366 if (point_less(second->point(), first->point()))
367 {
368 std::swap(first, second);
369 }
370 auto const cell = incident_cells->front();
371 return Edge_handle{cell, cell->index(first), cell->index(second)};
372 }
373
374 [[nodiscard]] inline auto vertex_precedes(Vertex_handle const& left,
375 Vertex_handle const& right)
376 -> bool
377 {
378 if (left->info() != right->info())
379 {
380 return left->info() < right->info();
381 }
382 return point_less(left->point(), right->point());
383 }
384
386 template <typename Container, std::uniform_random_bit_generator Generator>
387 [[nodiscard]] inline auto random_element(Container const& candidates,
388 Generator& generator)
389 -> std::optional<typename Container::value_type>
390 {
391 if (candidates.empty()) { return std::nullopt; }
392 std::uniform_int_distribution<std::size_t> distribution{
393 0, candidates.size() - 1};
394 return candidates[distribution(generator)];
395 }
396
399 template <typename Container, std::uniform_random_bit_generator Generator,
400 typename Comparator>
401 [[nodiscard]] inline auto canonical_random_element(Container& candidates,
402 Generator& generator,
403 Comparator comparator)
404 -> std::optional<typename Container::value_type>
405 {
406 if (candidates.empty()) { return std::nullopt; }
407 std::uniform_int_distribution<std::size_t> distribution{
408 0, candidates.size() - 1};
409 auto const index = distribution(generator);
410 auto const nth =
411 candidates.begin() + static_cast<Container::difference_type>(index);
412 std::ranges::nth_element(candidates, nth, comparator);
413 return candidates[index];
414 }
415
416 template <std::uniform_random_bit_generator Generator>
417 [[nodiscard]] inline auto canonical_random_element(Cell_container& cells,
418 Generator& generator)
419 -> std::optional<Cell_handle>
420 { return canonical_random_element(cells, generator, cell_precedes); }
421
422 template <std::uniform_random_bit_generator Generator>
423 [[nodiscard]] inline auto canonical_random_element(Edge_container& edges,
424 Generator& generator)
425 -> std::optional<Edge_handle>
426 { return canonical_random_element(edges, generator, edge_precedes); }
427
428 template <std::uniform_random_bit_generator Generator>
429 [[nodiscard]] inline auto canonical_random_element(
430 Vertex_container& vertices, Generator& generator)
431 -> std::optional<Vertex_handle>
432 {
433 return canonical_random_element(
434 vertices, generator, [](auto const& left, auto const& right) {
435 return point_less(left->point(), right->point());
436 });
437 }
438
439 [[nodiscard]] inline auto try_23_move(Delaunay& triangulation,
440 Cell_handle const& to_be_moved)
441 -> bool;
442
443 [[nodiscard]] inline auto prepare_two_three(Delaunay const& triangulation,
444 Cell_handle const& candidate)
445 -> std::expected<ApplicableTwoThreeMove, MoveError>;
446
447 [[nodiscard]] inline auto execute(Delaunay& triangulation,
448 ApplicableTwoThreeMove const& move)
449 -> Execution;
450
451 [[nodiscard]] inline auto try_32_move(Delaunay& triangulation,
452 Edge_handle const& to_be_moved)
453 -> bool;
454
455 [[nodiscard]] inline auto prepare_three_two(Delaunay const& triangulation,
456 Edge_handle const& candidate)
457 -> std::expected<ApplicableThreeTwoMove, MoveError>;
458
459 [[nodiscard]] inline auto execute(Delaunay& triangulation,
460 ApplicableThreeTwoMove const& move)
461 -> Execution;
462
463 [[nodiscard]] inline auto find_adjacent_31_cell(Cell_handle const& cell)
464 -> std::optional<int>;
465
466 [[nodiscard]] inline auto prepare_two_six(Delaunay const& triangulation,
467 Cell_handle const& candidate)
468 -> std::expected<ApplicableTwoSixMove, MoveError>;
469
470 template <typename Post_mutation_validator>
471 requires std::predicate<Post_mutation_validator&, Delaunay const&>
472 [[nodiscard]] inline auto execute(
473 Delaunay& triangulation, ApplicableTwoSixMove const& move,
474 Post_mutation_validator post_mutation_validator) -> Execution;
475
476 [[nodiscard]] inline auto is_62_movable(Delaunay const& triangulation,
477 Vertex_handle const& candidate)
478 -> bool;
479
480 [[nodiscard]] inline auto prepare_six_two(Delaunay const& triangulation,
481 Vertex_handle const& candidate)
482 -> std::expected<ApplicableSixTwoMove, MoveError>;
483
484 template <std::uniform_random_bit_generator Generator,
485 typename Post_mutation_validator>
486 requires std::predicate<Post_mutation_validator&, Delaunay const&>
487 [[nodiscard]] inline auto execute(
488 Delaunay const& source_triangulation, ApplicableSixTwoMove const& move,
489 Generator& generator, Post_mutation_validator post_mutation_validator)
490 -> std::expected<Delaunay, MoveError>;
491
492 template <std::uniform_random_bit_generator Generator>
493 [[nodiscard]] inline auto try_62_move(Delaunay const& source_triangulation,
494 Vertex_handle const source_candidate,
495 Generator& generator)
496 -> std::optional<Delaunay>;
497
498 [[nodiscard]] inline auto incident_cells_from_edge(
499 Delaunay const& triangulation, Edge_handle const& edge)
500 -> std::optional<Cell_container>;
501
502 [[nodiscard]] inline auto find_bistellar_flip_location(
503 Delaunay const& triangulation, Edge_handle const& candidate)
504 -> std::optional<Cell_container>;
505
506 [[nodiscard]] inline auto prepare_four_four(Delaunay const& triangulation,
507 Edge_handle const& candidate)
508 -> std::expected<ApplicableFourFourMove, MoveError>;
509
510 [[nodiscard]] inline auto prepare_bistellar_flip(
511 Delaunay const& triangulation, Edge_handle const& candidate,
512 Vertex_handle const& top, Vertex_handle const& bottom)
513 -> std::expected<ApplicableFourFourMove, MoveError>;
514
515 template <typename Post_mutation_validator>
516 requires std::predicate<Post_mutation_validator&, Delaunay const&>
517 [[nodiscard]] inline auto execute(
518 Delaunay const& source_triangulation,
519 ApplicableFourFourMove const& move,
520 Post_mutation_validator post_mutation_validator)
521 -> std::expected<Delaunay, MoveError>;
522
523 [[nodiscard]] inline auto bistellar_flip(
524 Delaunay const& source_triangulation, Edge_handle source_edge,
525 Vertex_handle source_top, Vertex_handle source_bottom)
526 -> std::optional<Delaunay>;
527
528 [[nodiscard]] inline auto find_pivot_edge(Delaunay const& triangulation,
529 Edge_container const& edges)
530 -> std::optional<Edge_handle>;
531
532 [[nodiscard]] inline auto get_vertices(Cell_container const& cells)
534
535 [[nodiscard]] inline auto check_move(Manifold const& before,
536 Manifold const& after,
537 move_tracker::MoveType const& move)
538 -> bool;
539 } // namespace detail
540
545 [[nodiscard]] inline auto null_move(Manifold const& t_manifold) -> Expected
546 { return t_manifold; } // null_move
547
554 [[nodiscard]] inline auto detail::prepare_two_three(
555 Delaunay const& triangulation, Cell_handle const& candidate)
556 -> std::expected<ApplicableTwoThreeMove, MoveError>
557 {
558 using enum move_tracker::MoveType;
559 if (candidate == nullptr || triangulation.dimension() != 3 ||
560 !triangulation.tds().is_cell(candidate))
561 {
562 return move_error(MoveFailure::INVALID_TOPOLOGY, TWO_THREE);
563 }
567 {
568 return move_error(MoveFailure::CAUSAL_INVALIDITY, TWO_THREE);
569 }
570
571 std::array facet_indices{0, 1, 2, 3};
572 std::ranges::sort(facet_indices, [&](auto const left, auto const right) {
573 return detail::point_less(candidate->vertex(left)->point(),
574 candidate->vertex(right)->point());
575 });
576
577 for (auto const i : facet_indices)
578 {
579 auto const neighbor = candidate->neighbor(i);
580 if (triangulation.is_infinite(neighbor)) { continue; }
581
582 auto const neighbor_type =
585 (neighbor_type != CellType::THREE_ONE &&
586 neighbor_type != CellType::ONE_THREE))
587 {
588 continue;
589 }
590
591 // A causal (2,3) move must replace the facet with a timelike edge.
592 // CGAL also permits topological flips that create a spacelike edge.
593 auto const mirror_index = neighbor->index(candidate);
594 auto const first_time =
595 static_cast<long long>(candidate->vertex(i)->info());
596 auto const second_time =
597 static_cast<long long>(neighbor->vertex(mirror_index)->info());
598 auto const time_difference = first_time > second_time
599 ? first_time - second_time
600 : second_time - first_time;
601 if (time_difference != 1) { continue; }
602
603 return ApplicableTwoThreeMove{canonical_cell_points(candidate),
604 candidate->vertex(i)->point()};
605 }
606
607 return move_error(MoveFailure::CAUSAL_INVALIDITY, TWO_THREE);
608 }
609
615 [[nodiscard]] inline auto detail::execute(Delaunay& triangulation,
616 ApplicableTwoThreeMove const& move)
617 -> Execution
618 {
619 using enum move_tracker::MoveType;
620 auto const cell = resolve_cell(triangulation, move.m_cell);
621 auto const opposite = resolve_vertex(triangulation, move.m_opposite);
622 if (!cell || !opposite)
623 {
624 return move_error(MoveFailure::STALE_CANDIDATE, TWO_THREE);
625 }
626
627 auto opposite_index = -1;
628 for (auto index = 0; index < 4; ++index)
629 {
630 if ((*cell)->vertex(index) == *opposite)
631 {
632 opposite_index = index;
633 break;
634 }
635 }
636 if (opposite_index < 0)
637 {
638 return move_error(MoveFailure::STALE_CANDIDATE, TWO_THREE);
639 }
640 if (!triangulation.flip(*cell, opposite_index))
641 {
642 return move_error(MoveFailure::EXECUTION_FAILURE, TWO_THREE);
643 }
644 return {};
645 }
646
648 [[nodiscard]] inline auto detail::try_23_move(Delaunay& triangulation,
649 Cell_handle const& to_be_moved)
650 -> bool
651 {
652 auto const prepared = prepare_two_three(triangulation, to_be_moved);
653 return prepared && execute(triangulation, *prepared).has_value();
654 } // try_23_move
655
673 template <std::uniform_random_bit_generator Generator>
674 [[nodiscard]] inline auto do_23_move(Manifold const& t_manifold,
675 Generator& generator) -> Expected
676 {
677 Delaunay triangulation{t_manifold.delaunay_snapshot()};
681 detail::canonicalize(two_two);
682 // Shuffle the container to create a random sequence of (2,2) cells
683 std::ranges::shuffle(two_two, generator);
684 if (two_two.empty())
685 {
686 return detail::move_error(MoveFailure::NO_CANDIDATE,
688 }
689
690 auto last_error =
692 .requested_move = move_tracker::MoveType::TWO_THREE};
693 for (auto const& cell : two_two)
694 {
695 auto const prepared = detail::prepare_two_three(triangulation, cell);
696 if (!prepared)
697 {
698 last_error = prepared.error();
699 continue;
700 }
701 auto const executed = detail::execute(triangulation, *prepared);
702 if (executed)
703 {
704 return detail::make_manifold(std::move(triangulation), t_manifold);
705 }
706 last_error = executed.error();
707 }
708 return std::unexpected{last_error};
709 }
710
719 template <std::uniform_random_bit_generator Generator>
720 [[nodiscard]] inline auto propose_23_move(Manifold const& t_manifold,
721 Generator& generator) -> Expected
722 {
723 auto triangulation = t_manifold.delaunay_snapshot();
727 auto const candidate = detail::canonical_random_element(two_two, generator);
728 if (!candidate)
729 {
730 return detail::move_error(MoveFailure::NO_CANDIDATE,
732 }
733 auto const prepared = detail::prepare_two_three(triangulation, *candidate);
734 if (!prepared) { return std::unexpected{prepared.error()}; }
735 auto const executed = detail::execute(triangulation, *prepared);
736 if (!executed) { return std::unexpected{executed.error()}; }
737 return detail::make_manifold(std::move(triangulation), t_manifold);
738 }
739
740 namespace detail
741 {
743 [[nodiscard]] inline auto is_32_movable(Delaunay const& triangulation,
744 Edge_handle const& candidate)
745 -> bool
746 {
747 auto const incident_cells =
748 finite_incident_cells(triangulation, candidate);
749 if (!incident_cells || incident_cells->size() != 3) { return false; }
750
751 auto const first_time = static_cast<long long>(
752 candidate.first->vertex(candidate.second)->info());
753 auto const second_time = static_cast<long long>(
754 candidate.first->vertex(candidate.third)->info());
755 auto const time_difference = first_time > second_time
756 ? first_time - second_time
757 : second_time - first_time;
758 if (time_difference != 1) { return false; }
759
760 if (!std::ranges::all_of(*incident_cells, [](auto const& cell) {
762 }))
763 {
764 return false;
765 }
766
767 auto const cell_type_count = [&](CellType const type) {
768 return std::ranges::count_if(*incident_cells, [&](auto const& cell) {
770 });
771 };
772 auto const incident_31 = cell_type_count(CellType::THREE_ONE);
773 auto const incident_22 = cell_type_count(CellType::TWO_TWO);
774 auto const incident_13 = cell_type_count(CellType::ONE_THREE);
775 return incident_22 == 2 && ((incident_31 == 1 && incident_13 == 0) ||
776 (incident_31 == 0 && incident_13 == 1));
777 }
778 } // namespace detail
779
784 [[nodiscard]] inline auto detail::prepare_three_two(
785 Delaunay const& triangulation, Edge_handle const& candidate)
786 -> std::expected<ApplicableThreeTwoMove, MoveError>
787 {
788 using enum move_tracker::MoveType;
789 if (!is_well_formed_edge(candidate) || triangulation.dimension() != 3 ||
790 !triangulation.tds().is_edge(candidate.first, candidate.second,
791 candidate.third))
792 {
793 return move_error(MoveFailure::INVALID_TOPOLOGY, THREE_TWO);
794 }
795 if (!is_32_movable(triangulation, candidate))
796 {
797 return move_error(MoveFailure::CAUSAL_INVALIDITY, THREE_TWO);
798 }
799 return ApplicableThreeTwoMove{canonical_edge_points(candidate)};
800 }
801
803 [[nodiscard]] inline auto detail::execute(Delaunay& triangulation,
804 ApplicableThreeTwoMove const& move)
805 -> Execution
806 {
807 using enum move_tracker::MoveType;
808 auto const edge = resolve_edge(triangulation, move.m_edge);
809 if (!edge) { return move_error(MoveFailure::STALE_CANDIDATE, THREE_TWO); }
810 auto const canonical_edge = canonical_edge_descriptor(triangulation, *edge);
811 if (!canonical_edge ||
812 !triangulation.flip(canonical_edge->first, canonical_edge->second,
813 canonical_edge->third))
814 {
815 return move_error(MoveFailure::EXECUTION_FAILURE, THREE_TWO);
816 }
817 return {};
818 }
819
821 [[nodiscard]] inline auto detail::try_32_move(Delaunay& triangulation,
822 Edge_handle const& to_be_moved)
823 -> bool
824 {
825 auto const prepared = prepare_three_two(triangulation, to_be_moved);
826 return prepared && execute(triangulation, *prepared).has_value();
827 } // try_32_move
828
842 template <std::uniform_random_bit_generator Generator>
843 [[nodiscard]] inline auto do_32_move(Manifold const& t_manifold,
844 Generator& generator) -> Expected
845 {
846 Delaunay triangulation{t_manifold.delaunay_snapshot()};
847 auto timelike_edges = foliated_triangulations::filter_edges<3>(
850 detail::canonicalize(timelike_edges);
851 // Shuffle the container to create a random sequence of edges
852 std::ranges::shuffle(timelike_edges, generator);
853 if (timelike_edges.empty())
854 {
855 return detail::move_error(MoveFailure::NO_CANDIDATE,
857 }
858
859 auto last_error =
861 .requested_move = move_tracker::MoveType::THREE_TWO};
862 for (auto const& edge : timelike_edges)
863 {
864 auto const prepared = detail::prepare_three_two(triangulation, edge);
865 if (!prepared)
866 {
867 last_error = prepared.error();
868 continue;
869 }
870 auto const executed = detail::execute(triangulation, *prepared);
871 if (executed)
872 {
873 return detail::make_manifold(std::move(triangulation), t_manifold);
874 }
875 last_error = executed.error();
876 }
877 return std::unexpected{last_error};
878 } // do_32_move()
879
887 template <std::uniform_random_bit_generator Generator>
888 [[nodiscard]] inline auto propose_32_move(Manifold const& t_manifold,
889 Generator& generator) -> Expected
890 {
891 auto triangulation = t_manifold.delaunay_snapshot();
892 auto timelike_edges = foliated_triangulations::filter_edges<3>(
895 auto const candidate =
896 detail::canonical_random_element(timelike_edges, generator);
897 if (!candidate)
898 {
899 return detail::move_error(MoveFailure::NO_CANDIDATE,
901 }
902 auto const prepared = detail::prepare_three_two(triangulation, *candidate);
903 if (!prepared) { return std::unexpected{prepared.error()}; }
904 auto const executed = detail::execute(triangulation, *prepared);
905 if (!executed) { return std::unexpected{executed.error()}; }
906 return detail::make_manifold(std::move(triangulation), t_manifold);
907 }
908
914 [[nodiscard]] inline auto detail::find_adjacent_31_cell(
915 Cell_handle const& t_cell) -> std::optional<int>
916 {
917 if (t_cell == nullptr ||
921 {
922 return std::nullopt;
923 }
924 std::vector<int> candidates;
925 for (auto i = 0; i < 4; ++i)
926 {
927 auto const neighbor = t_cell->neighbor(i);
931 {
932 candidates.emplace_back(i);
933 }
934 }
935 if (candidates.empty()) { return std::nullopt; }
936 std::ranges::sort(candidates, [&](auto const left, auto const right) {
937 auto const left_points =
938 detail::canonical_cell_points(t_cell->neighbor(left));
939 auto const right_points =
940 detail::canonical_cell_points(t_cell->neighbor(right));
941 return std::lexicographical_compare(
942 left_points.begin(), left_points.end(), right_points.begin(),
943 right_points.end(), detail::point_less);
944 });
945 return candidates.front();
946 } // find_26_move()
947
951 [[nodiscard]] inline auto detail::prepare_two_six(
952 Delaunay const& triangulation, Cell_handle const& candidate)
953 -> std::expected<ApplicableTwoSixMove, MoveError>
954 {
955 using enum move_tracker::MoveType;
956 if (candidate == nullptr || triangulation.dimension() != 3 ||
957 !triangulation.tds().is_cell(candidate))
958 {
959 return move_error(MoveFailure::INVALID_TOPOLOGY, TWO_SIX);
960 }
961
962 auto const neighboring_31_index = find_adjacent_31_cell(candidate);
963 if (!neighboring_31_index)
964 {
965 return move_error(MoveFailure::CAUSAL_INVALIDITY, TWO_SIX);
966 }
967
968 auto const top = candidate->neighbor(*neighboring_31_index);
969 auto common_face_index = std::numeric_limits<int>::max();
970 if (top == nullptr || !candidate->has_neighbor(top, common_face_index))
971 {
972 return move_error(MoveFailure::INVALID_TOPOLOGY, TWO_SIX);
973 }
974
975 auto const first = (common_face_index + 1) % 4;
976 auto const second = (common_face_index + 2) % 4;
977 auto const third = (common_face_index + 3) % 4;
978 if (candidate->vertex(first)->info() != candidate->vertex(second)->info() ||
979 candidate->vertex(second)->info() != candidate->vertex(third)->info())
980 {
981 return move_error(MoveFailure::CAUSAL_INVALIDITY, TWO_SIX);
982 }
983
984 return ApplicableTwoSixMove{canonical_cell_points(candidate),
985 candidate->vertex(common_face_index)->point()};
986 }
987
991 template <typename Post_mutation_validator>
992 requires std::predicate<Post_mutation_validator&, Delaunay const&>
993 [[nodiscard]] inline auto detail::execute(
994 Delaunay& triangulation, ApplicableTwoSixMove const& move,
995 Post_mutation_validator post_mutation_validator) -> Execution
996 {
997 using enum move_tracker::MoveType;
998 static constexpr auto incident_cell_count = std::size_t{6};
999
1000 auto const bottom = resolve_cell(triangulation, move.bottom_points());
1001 auto const opposite = resolve_vertex(triangulation, move.opposite_point());
1002 if (!bottom || !opposite)
1003 {
1004 return move_error(MoveFailure::STALE_CANDIDATE, TWO_SIX);
1005 }
1006
1007 auto common_face_index = -1;
1008 for (auto index = 0; index < 4; ++index)
1009 {
1010 if ((*bottom)->vertex(index) == *opposite)
1011 {
1012 common_face_index = index;
1013 break;
1014 }
1015 }
1016 if (common_face_index < 0)
1017 {
1018 return move_error(MoveFailure::STALE_CANDIDATE, TWO_SIX);
1019 }
1020
1021 auto const first = (common_face_index + 1) % 4;
1022 auto const second = (common_face_index + 2) % 4;
1023 auto const third = (common_face_index + 3) % 4;
1024 auto const v_1 = (*bottom)->vertex(first);
1025 auto const v_2 = (*bottom)->vertex(second);
1026 auto const v_3 = (*bottom)->vertex(third);
1027 std::array face_points{v_1->point(), v_2->point(), v_3->point()};
1028 std::ranges::sort(face_points, point_less);
1029 auto const center_point =
1030 CGAL::centroid(face_points[0], face_points[1], face_points[2]);
1031 if (std::ranges::any_of(triangulation.finite_vertex_handles(),
1032 [&](auto const& vertex) {
1033 return vertex->point() == center_point;
1034 }))
1035 {
1036 return move_error(MoveFailure::INVARIANT_VIOLATION, TWO_SIX);
1037 }
1038 auto const center =
1039 triangulation.tds().insert_in_facet(*bottom, common_face_index);
1040
1041 Cell_container incident_cells;
1042 triangulation.tds().incident_cells(center,
1043 std::back_inserter(incident_cells));
1044 if (incident_cells.size() != incident_cell_count ||
1045 !std::ranges::all_of(incident_cells,
1046 [&triangulation](auto const& cell) {
1047 return triangulation.tds().is_cell(cell);
1048 }))
1049 {
1050 return move_error(MoveFailure::INVARIANT_VIOLATION, TWO_SIX);
1051 }
1052
1053 center->set_point(center_point);
1054 center->info() = v_1->info();
1055
1056 if (!post_mutation_validator(static_cast<Delaunay const&>(triangulation)) ||
1057 !triangulation.tds().is_valid(center, true, 1))
1058 {
1059 return move_error(MoveFailure::INVARIANT_VIOLATION, TWO_SIX);
1060 }
1061 return {};
1062 }
1063
1064 namespace detail
1065 {
1066 template <std::uniform_random_bit_generator Generator,
1067 typename Post_mutation_validator>
1068 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1069 [[nodiscard]] inline auto do_26_move_impl(
1070 Manifold const& t_manifold, Generator& generator,
1071 bool const only_first_site,
1072 Post_mutation_validator post_mutation_validator) -> Expected;
1073 } // namespace detail
1074
1075 // Internal validation seam used to test rejection after mutation.
1076 template <std::uniform_random_bit_generator Generator,
1077 typename Post_mutation_validator>
1078 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1079 [[nodiscard]] inline auto detail::do_26_move_impl(
1080 Manifold const& t_manifold, Generator& generator,
1081 bool const only_first_site,
1082 Post_mutation_validator post_mutation_validator) -> Expected
1083 {
1084 Delaunay triangulation{t_manifold.delaunay_snapshot()};
1088 if (one_three.empty())
1089 {
1090 return move_error(MoveFailure::NO_CANDIDATE,
1091 move_tracker::MoveType::TWO_SIX);
1092 }
1093
1094 if (only_first_site)
1095 {
1096 auto const candidate =
1097 detail::canonical_random_element(one_three, generator);
1098 if (!candidate)
1099 {
1100 return move_error(MoveFailure::NO_CANDIDATE,
1101 move_tracker::MoveType::TWO_SIX);
1102 }
1103 one_three = {*candidate};
1104 }
1105 else
1106 {
1107 detail::canonicalize(one_three);
1108 // Shuffle the container to pick a random sequence of (1,3) cells to
1109 // try.
1110 std::ranges::shuffle(one_three, generator);
1111 }
1112
1113 auto last_error =
1115 .requested_move = move_tracker::MoveType::TWO_SIX};
1116 for (auto const& bottom : one_three)
1117 {
1118 auto const prepared = prepare_two_six(triangulation, bottom);
1119 if (!prepared)
1120 {
1121 last_error = prepared.error();
1122 continue;
1123 }
1124 auto const executed =
1125 execute(triangulation, *prepared, post_mutation_validator);
1126 if (!executed) { return std::unexpected{executed.error()}; }
1127 return make_manifold(std::move(triangulation), t_manifold);
1128 }
1129 return std::unexpected{last_error};
1130 } // do_26_move_impl()
1131
1151 template <std::uniform_random_bit_generator Generator>
1152 [[nodiscard]] inline auto do_26_move(Manifold const& t_manifold,
1153 Generator& generator) -> Expected
1154 {
1155 return detail::do_26_move_impl(t_manifold, generator, false,
1157 }
1158
1167 template <std::uniform_random_bit_generator Generator>
1168 [[nodiscard]] inline auto propose_26_move(Manifold const& t_manifold,
1169 Generator& generator) -> Expected
1170 {
1171 return detail::do_26_move_impl(t_manifold, generator, true,
1173 }
1174
1183 [[nodiscard]] inline auto detail::is_62_movable(
1184 Delaunay const& triangulation, Vertex_handle const& candidate) -> bool
1185 {
1186 if (triangulation.dimension() != 3) { return false; }
1187
1188 if (!triangulation.tds().is_vertex(candidate)) { return false; }
1189
1190 // We must have 5 incident edges to have 6 incident cells
1191 if (auto incident_edges = triangulation.degree(candidate);
1192 incident_edges != 5) // NOLINT
1193 {
1194 return false;
1195 }
1196
1197 // Obtain all incident cells
1198 Cell_container incident_cells;
1199 triangulation.tds().incident_cells(candidate,
1200 std::back_inserter(incident_cells));
1201
1202 // We must have 6 cells incident to the vertex to make a (6,2) move
1203 if (incident_cells.size() != 6) // NOLINT
1204 {
1205 return false;
1206 }
1207
1208 // Check that none of the incident cells are infinite
1209 for (auto const& cell : incident_cells)
1210 {
1211 if (triangulation.is_infinite(cell)) { return false; }
1212 }
1213
1214 auto const cell_type_count = [&](CellType const type) {
1215 return std::ranges::count_if(incident_cells, [&](auto const& cell) {
1217 });
1218 };
1219 auto const incident_31 = cell_type_count(CellType::THREE_ONE);
1220 auto const incident_22 = cell_type_count(CellType::TWO_TWO);
1221 auto const incident_13 = cell_type_count(CellType::ONE_THREE);
1222
1223 // All cells should be causally classified and carry matching metadata.
1224 if (incident_13 + incident_22 + incident_31 != 6 || // NOLINT
1225 !std::ranges::all_of(incident_cells, [](auto const& cell) {
1227 }))
1228 {
1229 return false;
1230 }
1231
1232 return incident_31 == 3 && incident_22 == 0 && incident_13 == 3;
1233
1234 } // find_62_moves()
1235
1239 [[nodiscard]] inline auto detail::prepare_six_two(
1240 Delaunay const& triangulation, Vertex_handle const& candidate)
1241 -> std::expected<ApplicableSixTwoMove, MoveError>
1242 {
1243 using enum move_tracker::MoveType;
1244 if (candidate == nullptr || triangulation.dimension() != 3 ||
1245 !triangulation.tds().is_vertex(candidate) ||
1246 triangulation.is_infinite(candidate))
1247 {
1248 return move_error(MoveFailure::INVALID_TOPOLOGY, SIX_TWO);
1249 }
1250 if (!is_62_movable(triangulation, candidate))
1251 {
1252 return move_error(MoveFailure::CAUSAL_INVALIDITY, SIX_TWO);
1253 }
1254 return ApplicableSixTwoMove{candidate->point()};
1255 }
1256
1257 namespace detail
1258 {
1259 template <std::uniform_random_bit_generator Generator,
1260 typename Post_mutation_validator>
1261 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1262 [[nodiscard]] inline auto try_62_move_impl(
1263 Delaunay const& source_triangulation,
1264 Vertex_handle const source_candidate, Generator& generator,
1265 Post_mutation_validator post_mutation_validator)
1266 -> std::optional<Delaunay>;
1267
1268 } // namespace detail
1269
1271 template <std::uniform_random_bit_generator Generator,
1272 typename Post_mutation_validator>
1273 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1274 [[nodiscard]] inline auto detail::execute(
1275 Delaunay const& source_triangulation, ApplicableSixTwoMove const& move,
1276 Generator& generator, Post_mutation_validator post_mutation_validator)
1277 -> std::expected<Delaunay, MoveError>
1278 {
1279 using enum move_tracker::MoveType;
1280 Delaunay triangulation{source_triangulation};
1281 auto const copied_candidate = foliated_triangulations::find_vertex<3>(
1282 triangulation, move.vertex_point());
1283 if (!copied_candidate)
1284 {
1285 return move_error(MoveFailure::STALE_CANDIDATE, SIX_TWO);
1286 }
1287
1288 auto const candidate = *copied_candidate;
1289 auto& tds = triangulation.tds();
1290 auto const old_cells = triangulation.number_of_finite_cells();
1291 auto const old_vertices = triangulation.number_of_vertices();
1292
1293 Edge_container incident_edges;
1294 triangulation.finite_incident_edges(candidate,
1295 std::back_inserter(incident_edges));
1296 detail::canonicalize(incident_edges);
1297 std::ranges::shuffle(incident_edges, generator);
1298
1299 auto const is_timelike = [](Edge_handle const& edge) {
1300 auto const first_time = edge.first->vertex(edge.second)->info();
1301 auto const second_time = edge.first->vertex(edge.third)->info();
1302 return first_time != second_time;
1303 };
1304 auto flipped = false;
1305 for (auto const& edge : incident_edges)
1306 {
1307 auto const canonical_edge =
1308 canonical_edge_descriptor(triangulation, edge);
1309 if (is_timelike(edge) && canonical_edge && tds.flip(*canonical_edge))
1310 {
1311 flipped = true;
1312 break;
1313 }
1314 }
1315 if (!flipped || tds.degree(candidate) != 4 || !tds.is_valid())
1316 {
1317 return move_error(MoveFailure::EXECUTION_FAILURE, SIX_TWO);
1318 }
1319
1320 tds.remove_from_maximal_dimension_simplex(candidate);
1321 if (!post_mutation_validator(static_cast<Delaunay const&>(triangulation)) ||
1322 !tds.is_valid() ||
1323 triangulation.number_of_finite_cells() + 4 != old_cells ||
1324 triangulation.number_of_vertices() + 1 != old_vertices)
1325 {
1326 return move_error(MoveFailure::INVARIANT_VIOLATION, SIX_TWO);
1327 }
1328
1329 for (auto const cell : triangulation.finite_cell_handles())
1330 {
1331 auto const type = foliated_triangulations::expected_cell_type<3>(cell);
1332 if (type == CellType::ACAUSAL || type == CellType::UNCLASSIFIED)
1333 {
1334 return move_error(MoveFailure::INVARIANT_VIOLATION, SIX_TWO);
1335 }
1336 cell->info() = static_cast<Int_precision>(type);
1337 }
1338
1339 return triangulation;
1340 } // execute()
1341
1342 // Internal validation seam used to test rejection after mutation.
1343 template <std::uniform_random_bit_generator Generator,
1344 typename Post_mutation_validator>
1345 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1346 [[nodiscard]] inline auto detail::try_62_move_impl(
1347 Delaunay const& source_triangulation,
1348 Vertex_handle const source_candidate, Generator& generator,
1349 Post_mutation_validator post_mutation_validator)
1350 -> std::optional<Delaunay>
1351 {
1352 auto const prepared =
1353 prepare_six_two(source_triangulation, source_candidate);
1354 if (!prepared) { return std::nullopt; }
1355 auto moved = execute(source_triangulation, *prepared, generator,
1356 post_mutation_validator);
1357 if (!moved) { return std::nullopt; }
1358 return std::move(*moved);
1359 } // try_62_move_impl()
1360
1371 template <std::uniform_random_bit_generator Generator>
1372 [[nodiscard]] inline auto detail::try_62_move(
1373 Delaunay const& source_triangulation,
1374 Vertex_handle const source_candidate, Generator& generator)
1375 -> std::optional<Delaunay>
1376 {
1377 return detail::try_62_move_impl(source_triangulation, source_candidate,
1378 generator, detail::accept_post_mutation);
1379 } // try_62_move()
1380
1403 template <std::uniform_random_bit_generator Generator>
1404 [[nodiscard]] inline auto do_62_move(Manifold const& t_manifold,
1405 Generator& generator) -> Expected
1406 {
1407 auto triangulation = t_manifold.delaunay_snapshot();
1408 auto vertices = foliated_triangulations::collect_vertices<3>(triangulation);
1409 detail::canonicalize(vertices);
1410 // Shuffle the container to create a random sequence of vertices
1411 std::ranges::shuffle(vertices, generator);
1412 if (vertices.empty())
1413 {
1414 return detail::move_error(MoveFailure::NO_CANDIDATE,
1416 }
1417
1418 auto last_error =
1420 .requested_move = move_tracker::MoveType::SIX_TWO};
1421 for (auto const& vertex : vertices)
1422 {
1423 auto const prepared = detail::prepare_six_two(triangulation, vertex);
1424 if (!prepared)
1425 {
1426 last_error = prepared.error();
1427 continue;
1428 }
1429 auto moved = detail::execute(triangulation, *prepared, generator,
1431 if (moved)
1432 {
1433 return detail::make_manifold(std::move(*moved), t_manifold);
1434 }
1435 last_error = moved.error();
1436 }
1437 return std::unexpected{last_error};
1438 } // do_62_move()
1439
1446 template <std::uniform_random_bit_generator Generator>
1447 [[nodiscard]] inline auto propose_62_move(Manifold const& t_manifold,
1448 Generator& generator) -> Expected
1449 {
1450 auto triangulation = t_manifold.delaunay_snapshot();
1451 auto vertices = foliated_triangulations::collect_vertices<3>(triangulation);
1452 auto const candidate =
1453 detail::canonical_random_element(vertices, generator);
1454 if (!candidate)
1455 {
1456 return detail::move_error(MoveFailure::NO_CANDIDATE,
1458 }
1459 auto const prepared = detail::prepare_six_two(triangulation, *candidate);
1460 if (!prepared) { return std::unexpected{prepared.error()}; }
1461 auto moved = detail::execute(triangulation, *prepared, generator,
1463 if (!moved) { return std::unexpected{moved.error()}; }
1464 return detail::make_manifold(std::move(*moved), t_manifold);
1465 }
1466
1473 [[nodiscard]] inline auto detail::incident_cells_from_edge(
1474 Delaunay const& triangulation, Edge_handle const& edge)
1475 -> std::optional<Cell_container>
1476 {
1477 return detail::finite_incident_cells(triangulation, edge);
1478 } // incident_cells_from_edge()
1479
1488 [[nodiscard]] inline auto detail::find_bistellar_flip_location(
1489 Delaunay const& triangulation, Edge_handle const& t_edge_candidate)
1490 -> std::optional<Cell_container>
1491 {
1492 if (!detail::is_well_formed_edge(t_edge_candidate)) { return std::nullopt; }
1493 auto incident_cells =
1494 detail::incident_cells_from_edge(triangulation, t_edge_candidate);
1495 if (!incident_cells || incident_cells->size() != 4) { return std::nullopt; }
1496
1497 auto const first_time =
1498 t_edge_candidate.first->vertex(t_edge_candidate.second)->info();
1499 auto const second_time =
1500 t_edge_candidate.first->vertex(t_edge_candidate.third)->info();
1501 if (first_time != second_time) { return std::nullopt; }
1502
1503 auto const cell_type_count = [&](CellType const type) {
1504 return std::ranges::count_if(*incident_cells, [&](auto const cell) {
1507 });
1508 };
1509 if (cell_type_count(CellType::THREE_ONE) == 2 &&
1510 cell_type_count(CellType::ONE_THREE) == 2)
1511 {
1512 return incident_cells;
1513 }
1514 return std::nullopt;
1515 } // find_bistellar_flip_location()
1516
1520 [[nodiscard]] inline auto detail::prepare_four_four(
1521 Delaunay const& triangulation, Edge_handle const& candidate)
1522 -> std::expected<ApplicableFourFourMove, MoveError>
1523 {
1524 using enum move_tracker::MoveType;
1525 if (!is_well_formed_edge(candidate) ||
1526 !triangulation.tds().is_edge(candidate.first, candidate.second,
1527 candidate.third))
1528 {
1529 return move_error(MoveFailure::INVALID_TOPOLOGY, FOUR_FOUR);
1530 }
1531
1532 auto const incident_cells =
1533 find_bistellar_flip_location(triangulation, candidate);
1534 if (!incident_cells)
1535 {
1536 return move_error(MoveFailure::CAUSAL_INVALIDITY, FOUR_FOUR);
1537 }
1538
1539 auto const first = candidate.first->vertex(candidate.second);
1540 auto const second = candidate.first->vertex(candidate.third);
1541 Vertex_handle top = nullptr;
1542 Vertex_handle bottom = nullptr;
1543 for (auto const& cell : *incident_cells)
1544 {
1545 for (auto index = 0; index < 4; ++index)
1546 {
1547 auto const vertex = cell->vertex(index);
1548 if (vertex == first || vertex == second) { continue; }
1549 if (top == nullptr || vertex_precedes(top, vertex)) { top = vertex; }
1550 if (bottom == nullptr || vertex_precedes(vertex, bottom))
1551 {
1552 bottom = vertex;
1553 }
1554 }
1555 }
1556
1557 if (top == nullptr || bottom == nullptr || top == bottom || top == first ||
1558 top == second || bottom == first || bottom == second)
1559 {
1560 return move_error(MoveFailure::CAUSAL_INVALIDITY, FOUR_FOUR);
1561 }
1562
1563 auto const incident_count = [&](Vertex_handle const vertex) {
1564 return std::ranges::count_if(
1565 *incident_cells,
1566 [&](Cell_handle const cell) { return cell->has_vertex(vertex); });
1567 };
1568 if (incident_count(top) != 2 || incident_count(bottom) != 2)
1569 {
1570 return move_error(MoveFailure::CAUSAL_INVALIDITY, FOUR_FOUR);
1571 }
1572
1573 return ApplicableFourFourMove{canonical_edge_points(candidate),
1574 top->point(), bottom->point()};
1575 }
1576
1581 [[nodiscard]] inline auto detail::prepare_bistellar_flip(
1582 Delaunay const& triangulation, Edge_handle const& candidate,
1583 Vertex_handle const& top, Vertex_handle const& bottom)
1584 -> std::expected<ApplicableFourFourMove, MoveError>
1585 {
1586 using enum move_tracker::MoveType;
1587 if (!is_well_formed_edge(candidate) || top == nullptr ||
1588 bottom == nullptr ||
1589 !triangulation.tds().is_edge(candidate.first, candidate.second,
1590 candidate.third) ||
1591 !triangulation.tds().is_vertex(top) ||
1592 !triangulation.tds().is_vertex(bottom) ||
1593 triangulation.is_infinite(top) || triangulation.is_infinite(bottom))
1594 {
1595 return move_error(MoveFailure::INVALID_TOPOLOGY, FOUR_FOUR);
1596 }
1597
1598 auto const first = candidate.first->vertex(candidate.second);
1599 auto const second = candidate.first->vertex(candidate.third);
1600 auto const incident_cells =
1601 incident_cells_from_edge(triangulation, candidate);
1602 if (!incident_cells || incident_cells->size() != 4 || top == bottom ||
1603 top == first || top == second || bottom == first || bottom == second ||
1604 std::ranges::any_of(*incident_cells, [&](auto const& cell) {
1605 return triangulation.is_infinite(cell) || !cell->is_valid();
1606 }))
1607 {
1608 return move_error(MoveFailure::INVALID_TOPOLOGY, FOUR_FOUR);
1609 }
1610
1611 auto const incident_count = [&](Vertex_handle const vertex) {
1612 return std::ranges::count_if(
1613 *incident_cells,
1614 [&](Cell_handle const cell) { return cell->has_vertex(vertex); });
1615 };
1616 if (incident_count(top) != 2 || incident_count(bottom) != 2)
1617 {
1618 return move_error(MoveFailure::INVALID_TOPOLOGY, FOUR_FOUR);
1619 }
1620
1621 return ApplicableFourFourMove{canonical_edge_points(candidate),
1622 top->point(), bottom->point()};
1623 }
1624
1625 namespace detail
1626 {
1627 template <typename Post_mutation_validator>
1628 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1629 [[nodiscard]] inline auto bistellar_flip_impl(
1630 Delaunay const& source_triangulation, Edge_handle const source_edge,
1631 Vertex_handle const source_top, Vertex_handle const source_bottom,
1632 Post_mutation_validator post_mutation_validator)
1633 -> std::optional<Delaunay>;
1634
1635 } // namespace detail
1636
1638 template <typename Post_mutation_validator>
1639 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1640 [[nodiscard]] inline auto detail::execute(
1641 Delaunay const& source_triangulation, ApplicableFourFourMove const& move,
1642 Post_mutation_validator post_mutation_validator)
1643 -> std::expected<Delaunay, MoveError>
1644 {
1645 using enum move_tracker::MoveType;
1646 Delaunay triangulation{source_triangulation};
1647 auto const edge = resolve_edge(triangulation, move.edge_points());
1648 auto const pivot_from_1 =
1649 resolve_vertex(triangulation, move.edge_points()[0]);
1650 auto const pivot_from_2 =
1651 resolve_vertex(triangulation, move.edge_points()[1]);
1652 auto const top = resolve_vertex(triangulation, move.top_point());
1653 auto const bottom = resolve_vertex(triangulation, move.bottom_point());
1654 if (!edge || !pivot_from_1 || !pivot_from_2 || !top || !bottom)
1655 {
1656 return move_error(MoveFailure::STALE_CANDIDATE, FOUR_FOUR);
1657 }
1658
1659 // A 3D 4-to-4 bistellar move is the composition of CGAL's checked TDS
1660 // 2-to-3 facet flip and checked 3-to-2 edge flip. The TDS operations are
1661 // intentional: either geometrically checked Triangulation_3 facet flip
1662 // can reject the transient, degenerate intermediate even when the final
1663 // 4-to-4 configuration is valid. Flipping a facet that contains the old
1664 // edge and either boundary vertex creates the new diagonal; the old edge
1665 // then has degree three and can be removed by the second flip.
1666 Cell_handle boundary_facet_cell = nullptr;
1667 int pivot_from_1_index{};
1668 int pivot_from_2_index{};
1669 int boundary_index{};
1670 if (!triangulation.is_facet(*pivot_from_1, *pivot_from_2, *bottom,
1671 boundary_facet_cell, pivot_from_1_index,
1672 pivot_from_2_index, boundary_index))
1673 {
1674 return move_error(MoveFailure::STALE_CANDIDATE, FOUR_FOUR);
1675 }
1676 constexpr auto cell_index_sum = 0 + 1 + 2 + 3;
1677 auto boundary_facet_index = cell_index_sum - pivot_from_1_index -
1678 pivot_from_2_index - boundary_index;
1679 auto const other_boundary_cell =
1680 boundary_facet_cell->neighbor(boundary_facet_index);
1681 if (other_boundary_cell == nullptr ||
1682 triangulation.is_infinite(boundary_facet_cell) ||
1683 triangulation.is_infinite(other_boundary_cell))
1684 {
1685 return move_error(MoveFailure::EXECUTION_FAILURE, FOUR_FOUR);
1686 }
1687 if (cell_precedes(other_boundary_cell, boundary_facet_cell))
1688 {
1689 boundary_facet_index = other_boundary_cell->index(boundary_facet_cell);
1690 boundary_facet_cell = other_boundary_cell;
1691 }
1692 if (!triangulation.tds().flip(
1693 Delaunay::Facet{boundary_facet_cell, boundary_facet_index}))
1694 {
1695 return move_error(MoveFailure::EXECUTION_FAILURE, FOUR_FOUR);
1696 }
1697
1698 auto const old_edge = resolve_edge(triangulation, move.edge_points());
1699 if (!old_edge)
1700 {
1701 return move_error(MoveFailure::EXECUTION_FAILURE, FOUR_FOUR);
1702 }
1703 auto const canonical_old_edge =
1704 canonical_edge_descriptor(triangulation, *old_edge);
1705 if (!canonical_old_edge)
1706 {
1707 return move_error(MoveFailure::EXECUTION_FAILURE, FOUR_FOUR);
1708 }
1709 if (!triangulation.tds().flip(*canonical_old_edge))
1710 {
1711 return move_error(MoveFailure::EXECUTION_FAILURE, FOUR_FOUR);
1712 }
1713
1714 if (!post_mutation_validator(static_cast<Delaunay const&>(triangulation)) ||
1715 !triangulation.tds().is_valid())
1716 {
1717 return move_error(MoveFailure::INVARIANT_VIOLATION, FOUR_FOUR);
1718 }
1719
1720 for (auto const cell : triangulation.finite_cell_handles())
1721 {
1722 cell->info() = static_cast<Int_precision>(
1724 }
1725
1726 return triangulation;
1727 } // execute()
1728
1729 // Internal validation seam used to test rejection after mutation.
1730 template <typename Post_mutation_validator>
1731 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1732 [[nodiscard]] inline auto detail::bistellar_flip_impl(
1733 Delaunay const& source_triangulation, Edge_handle const source_edge,
1734 Vertex_handle const source_top, Vertex_handle const source_bottom,
1735 Post_mutation_validator post_mutation_validator)
1736 -> std::optional<Delaunay>
1737 {
1738 auto const prepared = prepare_bistellar_flip(
1739 source_triangulation, source_edge, source_top, source_bottom);
1740 if (!prepared) { return std::nullopt; }
1741 auto moved =
1742 execute(source_triangulation, *prepared, post_mutation_validator);
1743 if (!moved) { return std::nullopt; }
1744 return std::move(*moved);
1745 } // bistellar_flip_impl()
1746
1756 [[nodiscard]] inline auto detail::bistellar_flip(
1757 Delaunay const& source_triangulation, Edge_handle const source_edge,
1758 Vertex_handle const source_top, Vertex_handle const source_bottom)
1759 -> std::optional<Delaunay>
1760 {
1761 return detail::bistellar_flip_impl(source_triangulation, source_edge,
1762 source_top, source_bottom,
1764 } // bistellar_flip()
1765
1767 [[nodiscard]] inline auto detail::find_pivot_edge(
1768 Delaunay const& triangulation, Edge_container const& edges)
1769 -> std::optional<Edge_handle>
1770 {
1771 for (auto const& edge : edges)
1772 {
1773 if (auto incident_cells =
1774 detail::incident_cells_from_edge(triangulation, edge))
1775 {
1776 if (incident_cells->size() == 4) { return edge; }
1777 }
1778 }
1779 return std::nullopt;
1780 } // find_pivot_edge()
1781
1785 [[nodiscard]] inline auto detail::get_vertices(Cell_container const& cells)
1787 {
1788 std::unordered_set<Vertex_handle> vertices;
1789 auto get_vertices = [&vertices](auto const& cell) {
1790 for (int i = 0; i < 4; ++i) { vertices.emplace(cell->vertex(i)); }
1791 };
1792 std::ranges::for_each(cells, get_vertices);
1793 Vertex_container result(vertices.begin(), vertices.end());
1794 return result;
1795 } // get_vertices()
1796
1821 template <std::uniform_random_bit_generator Generator>
1822 [[nodiscard]] inline auto do_44_move(Manifold const& t_manifold,
1823 Generator& generator) -> Expected
1824 {
1825 auto triangulation = t_manifold.delaunay_snapshot();
1826 auto spacelike_edges = foliated_triangulations::filter_edges<3>(
1829 detail::canonicalize(spacelike_edges);
1830 // Shuffle the container to pick a random sequence of edges to try
1831 std::ranges::shuffle(spacelike_edges, generator);
1832 if (spacelike_edges.empty())
1833 {
1834 return detail::move_error(MoveFailure::NO_CANDIDATE,
1836 }
1837
1838 auto last_error =
1840 .requested_move = move_tracker::MoveType::FOUR_FOUR};
1841 for (auto const& edge : spacelike_edges)
1842 {
1843 auto const prepared = detail::prepare_four_four(triangulation, edge);
1844 if (!prepared)
1845 {
1846 last_error = prepared.error();
1847 continue;
1848 }
1849 auto flipped = detail::execute(triangulation, *prepared,
1851 if (flipped)
1852 {
1853 return detail::make_manifold(std::move(*flipped), t_manifold);
1854 }
1855 last_error = flipped.error();
1856 }
1857 return std::unexpected{last_error};
1858 } // do_44_move()
1859
1867 template <std::uniform_random_bit_generator Generator>
1868 [[nodiscard]] inline auto propose_44_move(Manifold const& t_manifold,
1869 Generator& generator) -> Expected
1870 {
1871 auto triangulation = t_manifold.delaunay_snapshot();
1872 auto spacelike_edges = foliated_triangulations::filter_edges<3>(
1875 auto const candidate =
1876 detail::canonical_random_element(spacelike_edges, generator);
1877 if (!candidate)
1878 {
1879 return detail::move_error(MoveFailure::NO_CANDIDATE,
1881 }
1882
1883 auto const prepared = detail::prepare_four_four(triangulation, *candidate);
1884 if (!prepared) { return std::unexpected{prepared.error()}; }
1885 auto flipped =
1886 detail::execute(triangulation, *prepared, detail::accept_post_mutation);
1887 if (flipped)
1888 {
1889 return detail::make_manifold(std::move(*flipped), t_manifold);
1890 }
1891 return std::unexpected{flipped.error()};
1892 }
1893
1903 [[nodiscard]] inline auto detail::check_move(
1904 Manifold const& t_before, Manifold const& t_after,
1905 move_tracker::MoveType const& t_move) -> bool
1906 {
1907 if (!t_after.is_structurally_correct() ||
1908 !detail::same_configuration_value(t_after.initial_radius(),
1909 t_before.initial_radius()) ||
1910 !detail::same_configuration_value(t_after.foliation_spacing(),
1911 t_before.foliation_spacing()))
1912 {
1913 return false;
1914 }
1915
1916 switch (t_move)
1917 {
1919 return t_after.N3() == t_before.N3() &&
1920 t_after.N3_31() == t_before.N3_31() &&
1921 t_after.N3_22() == t_before.N3_22() &&
1922 t_after.N3_13() == t_before.N3_13() &&
1923 t_after.N2() == t_before.N2() && t_after.N1() == t_before.N1() &&
1924 t_after.N1_TL() == t_before.N1_TL() &&
1925 t_after.N1_SL() == t_before.N1_SL() &&
1926 t_after.N0() == t_before.N0() &&
1927 t_after.max_time() == t_before.max_time() &&
1928 t_after.min_time() == t_before.min_time();
1930 return t_after.N3() == t_before.N3() + 1 &&
1931 t_after.N3_31() == t_before.N3_31() &&
1932 t_after.N3_22() == t_before.N3_22() + 1 &&
1933 t_after.N3_13() == t_before.N3_13() &&
1934 t_after.N2() == t_before.N2() + 2 &&
1935 t_after.N1() == t_before.N1() + 1 &&
1936 t_after.N1_TL() == t_before.N1_TL() + 1 &&
1937 t_after.N1_SL() == t_before.N1_SL() &&
1938 t_after.N0() == t_before.N0() &&
1939 t_after.max_time() == t_before.max_time() &&
1940 t_after.min_time() == t_before.min_time();
1942 return t_after.N3() == t_before.N3() - 1 &&
1943 t_after.N3_31() == t_before.N3_31() &&
1944 t_after.N3_22() == t_before.N3_22() - 1 &&
1945 t_after.N3_13() == t_before.N3_13() &&
1946 t_after.N2() == t_before.N2() - 2 &&
1947 t_after.N1() == t_before.N1() - 1 &&
1948 t_after.N1_TL() == t_before.N1_TL() - 1 &&
1949 t_after.N1_SL() == t_before.N1_SL() &&
1950 t_after.N0() == t_before.N0() &&
1951 t_after.max_time() == t_before.max_time() &&
1952 t_after.min_time() == t_before.min_time();
1954 return t_after.N3() == t_before.N3() + 4 &&
1955 t_after.N3_31() == t_before.N3_31() + 2 &&
1956 t_after.N3_22() == t_before.N3_22() &&
1957 t_after.N3_13() == t_before.N3_13() + 2 &&
1958 t_after.N2() == t_before.N2() + 8 && // NOLINT
1959 t_after.N1() == t_before.N1() + 5 && // NOLINT
1960 t_after.N1_TL() == t_before.N1_TL() + 2 &&
1961 t_after.N1_SL() == t_before.N1_SL() + 3 &&
1962 t_after.N0() == t_before.N0() + 1 &&
1963 t_after.max_time() == t_before.max_time() &&
1964 t_after.min_time() == t_before.min_time();
1966 return t_after.N3() == t_before.N3() - 4 &&
1967 t_after.N3_31() == t_before.N3_31() - 2 &&
1968 t_after.N3_22() == t_before.N3_22() &&
1969 t_after.N3_13() == t_before.N3_13() - 2 &&
1970 t_after.N2() == t_before.N2() - 8 && // NOLINT
1971 t_after.N1() == t_before.N1() - 5 && // NOLINT
1972 t_after.N1_TL() == t_before.N1_TL() - 2 &&
1973 t_after.N1_SL() == t_before.N1_SL() - 3 &&
1974 t_after.N0() == t_before.N0() - 1 &&
1975 t_after.max_time() == t_before.max_time() &&
1976 t_after.min_time() == t_before.min_time();
1977 default: return false;
1978 }
1979 } // check_move()
1980
1981} // namespace cdt::ergodic_moves
1982
1983#endif // CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
auto is_32_movable(Delaunay const &triangulation, Edge_handle const &candidate) -> bool
Check for the causal cavity inverse to a (2,3) move.
auto try_32_move(Delaunay &triangulation, Edge_handle const &to_be_moved) -> bool
Compatibility seam that prepares and immediately executes (3,2).
auto try_62_move(Delaunay const &source_triangulation, Vertex_handle const source_candidate, Generator &generator) -> std::optional< Delaunay >
Apply the combinatorial (6,2) retriangulation on a private copy.
auto do_26_move(Manifold const &t_manifold, Generator &generator) -> Expected
Perform a (2,6) move.
auto incident_cells_from_edge(Delaunay const &triangulation, Edge_handle const &edge) -> std::optional< Cell_container >
Find all cells incident to the edge.
auto prepare_bistellar_flip(Delaunay const &triangulation, Edge_handle const &candidate, Vertex_handle const &top, Vertex_handle const &bottom) -> std::expected< ApplicableFourFourMove, MoveError >
Prepare the generic topological seam used by bistellar_flip().
auto propose_44_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one spacelike edge as a (4,4) site.
auto find_bistellar_flip_location(Delaunay const &triangulation, Edge_handle const &candidate) -> std::optional< Cell_container >
Find a bistellar flip location.
auto execute(Delaunay &triangulation, ApplicableTwoThreeMove const &move) -> Execution
Consume a prepared (2,3) value at the mutation boundary.
auto prepare_three_two(Delaunay const &triangulation, Edge_handle const &candidate) -> std::expected< ApplicableThreeTwoMove, MoveError >
Parse a raw edge into an applicable causal (3,2) move.
auto find_pivot_edge(Delaunay const &triangulation, Edge_container const &edges) -> std::optional< Edge_handle >
Vertex_handle_t< 3 > Vertex_handle
Three-dimensional CGAL vertex handle.
auto propose_62_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one vertex as a (6,2) site for Metropolis-Hastings.
auto propose_23_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one (2,3) site for Metropolis-Hastings.
auto do_32_move(Manifold const &t_manifold, Generator &generator) -> Expected
Perform a (3,2) move.
auto is_well_formed_edge(Edge_handle const &edge) noexcept -> bool
Check an edge handle without dereferencing its cell handle.
auto same_configuration_value(double const first, double const second) noexcept -> bool
Compare preserved floating-point configuration state exactly.
auto bistellar_flip(Delaunay const &source_triangulation, Edge_handle source_edge, Vertex_handle source_top, Vertex_handle source_bottom) -> std::optional< Delaunay >
Perform a bistellar flip on triangulation via the given edge.
auto make_manifold(Delaunay triangulation, Manifold const &source) -> Manifold
Rebuild all derived topology and geometry state around a value.
auto get_vertices(Cell_container const &cells) -> Vertex_container
Return a container of all vertices in a container of cells.
auto prepare_two_six(Delaunay const &triangulation, Cell_handle const &candidate) -> std::expected< ApplicableTwoSixMove, MoveError >
Parse a raw (1,3) cell into an applicable (2,6) move.
auto propose_32_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one (3,2) site for Metropolis-Hastings.
Edge_handle_t< 3 > Edge_handle
Three-dimensional CGAL edge descriptor.
std::vector< Cell_handle > Cell_container
Collection of three-dimensional cell handles.
auto canonical_edge_descriptor(Delaunay const &triangulation, Edge_handle const &edge) -> std::optional< Edge_handle >
Rebind an edge to its canonical finite incident cell.
auto null_move(Manifold const &t_manifold) -> Expected
Perform a null move.
std::vector< Edge_handle > Edge_container
Collection of three-dimensional edge descriptors.
auto finite_incident_cells(Delaunay const &triangulation, Edge_handle const &edge) -> std::optional< Cell_container >
Collect the finite cells incident to a checked edge.
std::vector< Vertex_handle > Vertex_container
Collection of three-dimensional vertex handles.
auto is_62_movable(Delaunay const &triangulation, Vertex_handle const &candidate) -> bool
Find a (6,2) move location.
auto find_adjacent_31_cell(Cell_handle const &cell) -> std::optional< int >
Find a (2,6) move location.
auto check_move(Manifold const &before, Manifold const &after, move_tracker::MoveType const &move) -> bool
Check tracked move deltas and essential CDT manifold invariants.
MoveResult< Manifold > Expected
Fallible manifold transformation returned by public move functions.
auto accept_post_mutation(Delaunay const &triangulation) noexcept -> bool
Default validator for internal post-mutation test seams.
auto try_23_move(Delaunay &triangulation, Cell_handle const &to_be_moved) -> bool
Compatibility seam that prepares and immediately executes (2,3).
auto do_44_move(Manifold const &t_manifold, Generator &generator) -> Expected
Perform a (4,4) move.
auto prepare_two_three(Delaunay const &triangulation, Cell_handle const &candidate) -> std::expected< ApplicableTwoThreeMove, MoveError >
Parse a raw (2,2) cell into an applicable causal (2,3) move.
manifolds::Manifold_3 Manifold
Three-dimensional spherical CDT manifold operated on by this move set.
auto do_62_move(Manifold const &t_manifold, Generator &generator) -> Expected
Perform a (6,2) move.
Cell_handle_t< 3 > Cell_handle
Three-dimensional CGAL cell handle.
auto random_element(Container const &candidates, Generator &generator) -> std::optional< typename Container::value_type >
Select exactly one raw proposal site uniformly in container order.
auto propose_26_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose a uniformly selected (2,6) site.
auto canonical_random_element(Container &candidates, Generator &generator, Comparator comparator) -> std::optional< typename Container::value_type >
auto prepare_six_two(Delaunay const &triangulation, Vertex_handle const &candidate) -> std::expected< ApplicableSixTwoMove, MoveError >
Parse a raw vertex into an applicable causal (6,2) move.
auto prepare_four_four(Delaunay const &triangulation, Edge_handle const &candidate) -> std::expected< ApplicableFourFourMove, MoveError >
Parse a raw spacelike edge into an applicable (4,4) move.
auto do_23_move(Manifold const &t_manifold, Generator &generator) -> Expected
Perform a (2,3) move.
Delaunay_t< 3 > Delaunay
Three-dimensional Delaunay triangulation used by move implementations.
Data structures for manifolds.
Manifold< 3 > Manifold_3
Three-dimensional spherical CDT manifold.
Definition Manifold.hpp:373
Structured Pachner-move failures and transition outcomes.
std::expected< ManifoldType, MoveError > MoveResult
Value returned by a fallible Pachner-move transformation.
MoveFailure
Actionable reasons a raw move request cannot produce a new state.
@ STALE_CANDIDATE
A prepared proposal no longer resolves.
@ NO_CANDIDATE
No raw proposal site is available.
@ INVARIANT_VIOLATION
A post-mutation manifold check failed.
@ CAUSAL_INVALIDITY
The proposal violates a causal move invariant.
@ INVALID_TOPOLOGY
The proposal site is not in the triangulation.
@ EXECUTION_FAILURE
CGAL rejected the prepared mutation.
Track ergodic moves.
MoveType
The types of 3D ergodic moves.
@ TWO_THREE
Replace two tetrahedra with three.
@ SIX_TWO
Replace six tetrahedra with two.
@ THREE_TWO
Replace three tetrahedra with two.
@ FOUR_FOUR
Exchange a causal four-tetrahedron diamond.
@ TWO_SIX
Replace two tetrahedra with six.
auto collect_cells(Delaunay_t< dimension > const &t_triangulation) -> std::vector< Cell_handle_t< dimension > >
Obtain all finite cells in the Delaunay triangulation.
auto collect_edges(Delaunay_t< dimension > const &delaunay)
Returns a container of all the finite edges in the triangulation.
auto filter_edges(std::vector< Edge_handle_t< dimension > > const &t_edges, EdgeType const edge_type) -> std::vector< Edge_handle_t< dimension > >
auto expected_cell_type(Cell_handle_t< dimension > const &t_cell)
Classifies cells by their timevalues.
auto is_cell_type_correct(Cell_handle_t< dimension > const &t_cell) -> bool
Checks if a cell is classified correctly.
auto find_vertex(Delaunay_t< dimension > const &delaunay, Point_t< dimension > const &point) -> std::optional< Vertex_handle_t< dimension > >
Find the vertex whose stored point equals the requested point.
auto filter_cells(std::vector< Cell_handle_t< dimension > > const &t_cells, CellType const &t_cell_type) -> std::vector< Cell_handle_t< dimension > >
auto collect_vertices(Delaunay_t< dimension > const &t_triangulation)
Obtain all finite vertices in the Delaunay triangulation.
typename detail::TriangulationTraits< dimension >::Cell_handle Cell_handle_t
Mutable CGAL cell handle for a triangulation dimension.
typename detail::TriangulationTraits< dimension >::Point Point_t
Cartesian point type used by a triangulation dimension.
typename detail::TriangulationTraits< dimension >::Delaunay Delaunay_t
Delaunay triangulation type for dimension spatial dimensions.
std::int32_t Int_precision
Definition Settings.hpp:30
typename detail::TriangulationTraits< dimension >::Vertex_handle Vertex_handle_t
Mutable CGAL vertex handle for a triangulation dimension.
typename detail::TriangulationTraits< dimension >::Edge_handle Edge_handle_t
CGAL edge descriptor for a triangulation dimension.
CellType
(n,m) is number of vertices on (lower, higher) timeslice
@ THREE_ONE
Three lower-slice and one upper-slice vertices.
@ ACAUSAL
Vertex times differ by more than one or are all equal.
@ UNCLASSIFIED
Classification could not determine a causal type.
@ ONE_THREE
One lower-slice and three upper-slice vertices.
@ TWO_TWO
Two vertices on each adjacent slice.
@ TIMELIKE
Endpoints lie on adjacent timeslices.
@ SPACELIKE
Both endpoints lie on the same timeslice.
Typed error returned by move preparation or private execution.