19#ifndef CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
20#define CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
33#include <unordered_set>
41namespace cdt::ergodic_moves
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>;
71 class ApplicableTwoThreeMove
76 ApplicableTwoThreeMove(Cell_points cell,
Point_t<3> opposite) noexcept
77 : m_cell{cell}, m_opposite{opposite}
80 friend auto prepare_two_three(
Delaunay const& triangulation,
82 -> std::expected<ApplicableTwoThreeMove, MoveError>;
83 friend auto execute(
Delaunay& triangulation,
84 ApplicableTwoThreeMove
const& move) -> Execution;
88 class ApplicableThreeTwoMove
92 explicit ApplicableThreeTwoMove(Edge_points edge) noexcept : m_edge{edge}
95 friend auto prepare_three_two(
Delaunay const& triangulation,
97 -> std::expected<ApplicableThreeTwoMove, MoveError>;
98 friend auto execute(
Delaunay& triangulation,
99 ApplicableThreeTwoMove
const& move) -> Execution;
103 class ApplicableTwoSixMove
105 Cell_points m_bottom;
108 ApplicableTwoSixMove(Cell_points bottom,
Point_t<3> opposite) noexcept
109 : m_bottom{bottom}, m_opposite{opposite}
112 friend auto prepare_two_six(
Delaunay const& triangulation,
114 -> std::expected<ApplicableTwoSixMove, MoveError>;
117 [[nodiscard]]
auto bottom_points()
const noexcept -> Cell_points
const&
120 [[nodiscard]]
auto opposite_point()
const noexcept ->
Point_t<3> const&
121 {
return m_opposite; }
125 class ApplicableSixTwoMove
129 explicit ApplicableSixTwoMove(
Point_t<3> vertex) noexcept
133 friend auto prepare_six_two(
Delaunay const& triangulation,
135 -> std::expected<ApplicableSixTwoMove, MoveError>;
138 [[nodiscard]]
auto vertex_point()
const noexcept ->
Point_t<3> const&
143 class ApplicableFourFourMove
149 ApplicableFourFourMove(Edge_points edge,
Point_t<3> top,
151 : m_edge{edge}, m_top{top}, m_bottom{bottom}
154 friend auto prepare_four_four(
Delaunay const& triangulation,
156 -> std::expected<ApplicableFourFourMove, MoveError>;
157 friend auto prepare_bistellar_flip(
Delaunay const& triangulation,
161 -> std::expected<ApplicableFourFourMove, MoveError>;
164 [[nodiscard]]
auto edge_points()
const noexcept -> Edge_points
const&
167 [[nodiscard]]
auto top_point()
const noexcept ->
Point_t<3> const&
170 [[nodiscard]]
auto bottom_point()
const noexcept ->
Point_t<3> const&
174 [[nodiscard]]
constexpr auto move_error(
176 -> std::unexpected<MoveError>
178 return std::unexpected{
179 MoveError{.category = reason, .requested_move = move}
187 double const first,
double const second)
noexcept ->
bool
189 using Representation = std::array<std::byte,
sizeof(double)>;
190 return std::bit_cast<Representation>(first) ==
191 std::bit_cast<Representation>(second);
196 [[maybe_unused]]
Delaunay const& triangulation)
noexcept ->
bool
205 std::move(triangulation), source.initial_radius(),
206 source.foliation_spacing()}
217 return index >= 0 && index < vertex_count;
220 return edge.first !=
nullptr && valid_index(edge.second) &&
221 valid_index(edge.third) && edge.second != edge.third;
229 -> std::optional<Cell_container>
232 !triangulation.tds().is_edge(edge.first, edge.second, edge.third))
237 auto circulator = triangulation.incident_cells(edge, edge.first);
241 if (triangulation.is_infinite(circulator)) {
return std::nullopt; }
242 incident_cells.emplace_back(circulator);
244 while (++circulator != edge.first);
245 return incident_cells;
248 [[nodiscard]]
inline auto point_less(
Point_t<3> const& left,
250 {
return CGAL::lexicographically_xyz_smaller(left, right); }
252 [[nodiscard]]
inline auto canonical_cell_points(Cell_handle
const& cell)
253 -> std::array<Point_t<3>, 4>
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);
261 [[nodiscard]]
inline auto canonical_edge_points(Edge_handle
const& edge)
262 -> std::array<Point_t<3>, 2>
264 std::array points{edge.first->vertex(edge.second)->point(),
265 edge.first->vertex(edge.third)->point()};
266 std::ranges::sort(points, point_less);
270 [[nodiscard]]
inline auto resolve_vertex(Delaunay
const& triangulation,
272 -> std::optional<Vertex_handle>
275 if (triangulation.is_vertex(point, vertex)) {
return vertex; }
279 [[nodiscard]]
inline auto resolve_cell(Delaunay
const& triangulation,
280 Cell_points
const& points)
281 -> std::optional<Cell_handle>
283 std::array<Vertex_handle, 4> vertices;
284 for (
auto index = std::size_t{}; index < points.size(); ++index)
286 auto const vertex = resolve_vertex(triangulation, points[index]);
287 if (!vertex) {
return std::nullopt; }
288 vertices[index] = *vertex;
292 if (triangulation.is_cell(vertices[0], vertices[1], vertices[2],
300 [[nodiscard]]
inline auto resolve_edge(Delaunay
const& triangulation,
301 Edge_points
const& points)
302 -> std::optional<Edge_handle>
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; }
311 if (triangulation.is_edge(*first, *second, cell, first_index,
314 return Edge_handle{cell, first_index, second_index};
319 [[nodiscard]]
inline auto cell_precedes(Cell_handle
const& left,
320 Cell_handle
const& right) ->
bool
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,
328 [[nodiscard]]
inline auto edge_precedes(Edge_handle
const& left,
329 Edge_handle
const& right) ->
bool
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,
337 inline void canonicalize(Cell_container& cells)
338 { std::ranges::sort(cells, cell_precedes); }
340 inline void canonicalize(Edge_container& edges)
341 { std::ranges::sort(edges, edge_precedes); }
343 inline void canonicalize(Vertex_container& vertices)
345 std::ranges::sort(vertices, [](
auto const& left,
auto const& right) {
346 return point_less(left->point(), right->point());
357 -> std::optional<Edge_handle>
361 if (!incident_cells || incident_cells->empty()) {
return std::nullopt; }
362 canonicalize(*incident_cells);
364 auto first = edge.first->vertex(edge.second);
365 auto second = edge.first->vertex(edge.third);
366 if (point_less(second->point(), first->point()))
368 std::swap(first, second);
370 auto const cell = incident_cells->front();
371 return Edge_handle{cell, cell->index(first), cell->index(second)};
374 [[nodiscard]]
inline auto vertex_precedes(
Vertex_handle const& left,
378 if (left->info() != right->info())
380 return left->info() < right->info();
382 return point_less(left->point(), right->point());
386 template <
typename Container, std::uniform_random_bit_generator Generator>
388 Generator& generator)
389 -> std::optional<typename Container::value_type>
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)];
399 template <
typename Container, std::uniform_random_bit_generator Generator,
402 Generator& generator,
403 Comparator comparator)
404 -> std::optional<typename Container::value_type>
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);
411 candidates.begin() +
static_cast<Container::difference_type
>(index);
412 std::ranges::nth_element(candidates, nth, comparator);
413 return candidates[index];
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); }
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); }
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>
433 return canonical_random_element(
434 vertices, generator, [](
auto const& left,
auto const& right) {
435 return point_less(left->point(), right->point());
439 [[nodiscard]]
inline auto try_23_move(Delaunay& triangulation,
440 Cell_handle
const& to_be_moved)
444 Cell_handle
const& candidate)
445 -> std::expected<ApplicableTwoThreeMove, MoveError>;
447 [[nodiscard]]
inline auto execute(Delaunay& triangulation,
448 ApplicableTwoThreeMove
const& move)
451 [[nodiscard]]
inline auto try_32_move(Delaunay& triangulation,
452 Edge_handle
const& to_be_moved)
456 Edge_handle
const& candidate)
457 -> std::expected<ApplicableThreeTwoMove, MoveError>;
459 [[nodiscard]]
inline auto execute(Delaunay& triangulation,
460 ApplicableThreeTwoMove
const& move)
464 -> std::optional<int>;
466 [[nodiscard]]
inline auto prepare_two_six(Delaunay
const& triangulation,
467 Cell_handle
const& candidate)
468 -> std::expected<ApplicableTwoSixMove, MoveError>;
470 template <
typename Post_mutation_val
idator>
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;
476 [[nodiscard]]
inline auto is_62_movable(Delaunay
const& triangulation,
477 Vertex_handle
const& candidate)
480 [[nodiscard]]
inline auto prepare_six_two(Delaunay
const& triangulation,
481 Vertex_handle
const& candidate)
482 -> std::expected<ApplicableSixTwoMove, MoveError>;
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>;
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>;
499 Delaunay
const& triangulation, Edge_handle
const& edge)
500 -> std::optional<Cell_container>;
503 Delaunay
const& triangulation, Edge_handle
const& candidate)
504 -> std::optional<Cell_container>;
507 Edge_handle
const& candidate)
508 -> std::expected<ApplicableFourFourMove, MoveError>;
511 Delaunay
const& triangulation, Edge_handle
const& candidate,
512 Vertex_handle
const& top, Vertex_handle
const& bottom)
513 -> std::expected<ApplicableFourFourMove, MoveError>;
515 template <
typename Post_mutation_val
idator>
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>;
524 Delaunay
const& source_triangulation, Edge_handle source_edge,
525 Vertex_handle source_top, Vertex_handle source_bottom)
526 -> std::optional<Delaunay>;
528 [[nodiscard]]
inline auto find_pivot_edge(Delaunay
const& triangulation,
529 Edge_container
const& edges)
530 -> std::optional<Edge_handle>;
532 [[nodiscard]]
inline auto get_vertices(Cell_container
const& cells)
535 [[nodiscard]]
inline auto check_move(Manifold
const& before,
536 Manifold
const& after,
537 move_tracker::MoveType
const& move)
546 {
return t_manifold; }
556 -> std::expected<ApplicableTwoThreeMove, MoveError>
559 if (candidate ==
nullptr || triangulation.dimension() != 3 ||
560 !triangulation.tds().is_cell(candidate))
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());
577 for (
auto const i : facet_indices)
579 auto const neighbor = candidate->neighbor(i);
580 if (triangulation.is_infinite(neighbor)) {
continue; }
582 auto const neighbor_type =
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; }
603 return ApplicableTwoThreeMove{canonical_cell_points(candidate),
604 candidate->vertex(i)->point()};
616 ApplicableTwoThreeMove
const& move)
620 auto const cell = resolve_cell(triangulation, move.m_cell);
621 auto const opposite = resolve_vertex(triangulation, move.m_opposite);
622 if (!cell || !opposite)
627 auto opposite_index = -1;
628 for (
auto index = 0; index < 4; ++index)
630 if ((*cell)->vertex(index) == *opposite)
632 opposite_index = index;
636 if (opposite_index < 0)
640 if (!triangulation.flip(*cell, opposite_index))
653 return prepared &&
execute(triangulation, *prepared).has_value();
673 template <std::uniform_random_bit_generator Generator>
677 Delaunay triangulation{t_manifold.delaunay_snapshot()};
681 detail::canonicalize(two_two);
683 std::ranges::shuffle(two_two, generator);
693 for (
auto const& cell : two_two)
698 last_error = prepared.error();
706 last_error = executed.error();
708 return std::unexpected{last_error};
719 template <std::uniform_random_bit_generator Generator>
723 auto triangulation = t_manifold.delaunay_snapshot();
734 if (!prepared) {
return std::unexpected{prepared.error()}; }
736 if (!executed) {
return std::unexpected{executed.error()}; }
747 auto const incident_cells =
749 if (!incident_cells || incident_cells->size() != 3) {
return false; }
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; }
760 if (!std::ranges::all_of(*incident_cells, [](
auto const& cell) {
767 auto const cell_type_count = [&](
CellType const type) {
768 return std::ranges::count_if(*incident_cells, [&](
auto const& cell) {
775 return incident_22 == 2 && ((incident_31 == 1 && incident_13 == 0) ||
776 (incident_31 == 0 && incident_13 == 1));
786 -> std::expected<ApplicableThreeTwoMove, MoveError>
790 !triangulation.tds().is_edge(candidate.first, candidate.second,
799 return ApplicableThreeTwoMove{canonical_edge_points(candidate)};
804 ApplicableThreeTwoMove
const& move)
808 auto const edge = resolve_edge(triangulation, move.m_edge);
811 if (!canonical_edge ||
812 !triangulation.flip(canonical_edge->first, canonical_edge->second,
813 canonical_edge->third))
826 return prepared &&
execute(triangulation, *prepared).has_value();
842 template <std::uniform_random_bit_generator Generator>
846 Delaunay triangulation{t_manifold.delaunay_snapshot()};
850 detail::canonicalize(timelike_edges);
852 std::ranges::shuffle(timelike_edges, generator);
853 if (timelike_edges.empty())
862 for (
auto const& edge : timelike_edges)
867 last_error = prepared.error();
875 last_error = executed.error();
877 return std::unexpected{last_error};
887 template <std::uniform_random_bit_generator Generator>
891 auto triangulation = t_manifold.delaunay_snapshot();
895 auto const candidate =
903 if (!prepared) {
return std::unexpected{prepared.error()}; }
905 if (!executed) {
return std::unexpected{executed.error()}; }
917 if (t_cell ==
nullptr ||
924 std::vector<int> candidates;
925 for (
auto i = 0; i < 4; ++i)
927 auto const neighbor = t_cell->neighbor(i);
932 candidates.emplace_back(i);
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);
945 return candidates.front();
953 -> std::expected<ApplicableTwoSixMove, MoveError>
956 if (candidate ==
nullptr || triangulation.dimension() != 3 ||
957 !triangulation.tds().is_cell(candidate))
963 if (!neighboring_31_index)
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))
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())
984 return ApplicableTwoSixMove{canonical_cell_points(candidate),
985 candidate->vertex(common_face_index)->point()};
991 template <
typename Post_mutation_val
idator>
992 requires std::predicate<Post_mutation_validator&, Delaunay const&>
994 Delaunay& triangulation, ApplicableTwoSixMove
const& move,
995 Post_mutation_validator post_mutation_validator) -> Execution
998 static constexpr auto incident_cell_count = std::size_t{6};
1000 auto const bottom = resolve_cell(triangulation, move.bottom_points());
1001 auto const opposite = resolve_vertex(triangulation, move.opposite_point());
1002 if (!bottom || !opposite)
1007 auto common_face_index = -1;
1008 for (
auto index = 0; index < 4; ++index)
1010 if ((*bottom)->vertex(index) == *opposite)
1012 common_face_index = index;
1016 if (common_face_index < 0)
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;
1039 triangulation.tds().insert_in_facet(*bottom, common_face_index);
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);
1053 center->set_point(center_point);
1054 center->info() = v_1->info();
1056 if (!post_mutation_validator(
static_cast<Delaunay const&
>(triangulation)) ||
1057 !triangulation.tds().is_valid(center,
true, 1))
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;
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
1084 Delaunay triangulation{t_manifold.delaunay_snapshot()};
1088 if (one_three.empty())
1090 return move_error(MoveFailure::NO_CANDIDATE,
1091 move_tracker::MoveType::TWO_SIX);
1094 if (only_first_site)
1096 auto const candidate =
1101 move_tracker::MoveType::TWO_SIX);
1103 one_three = {*candidate};
1107 detail::canonicalize(one_three);
1110 std::ranges::shuffle(one_three, generator);
1115 .requested_move = move_tracker::MoveType::TWO_SIX};
1116 for (
auto const& bottom : one_three)
1121 last_error = prepared.error();
1124 auto const executed =
1125 execute(triangulation, *prepared, post_mutation_validator);
1126 if (!executed) {
return std::unexpected{executed.error()}; }
1129 return std::unexpected{last_error};
1151 template <std::uniform_random_bit_generator Generator>
1155 return detail::do_26_move_impl(t_manifold, generator,
false,
1167 template <std::uniform_random_bit_generator Generator>
1171 return detail::do_26_move_impl(t_manifold, generator,
true,
1186 if (triangulation.dimension() != 3) {
return false; }
1188 if (!triangulation.tds().is_vertex(candidate)) {
return false; }
1191 if (
auto incident_edges = triangulation.degree(candidate);
1192 incident_edges != 5)
1199 triangulation.tds().incident_cells(candidate,
1200 std::back_inserter(incident_cells));
1203 if (incident_cells.size() != 6)
1209 for (
auto const& cell : incident_cells)
1211 if (triangulation.is_infinite(cell)) {
return false; }
1214 auto const cell_type_count = [&](
CellType const type) {
1215 return std::ranges::count_if(incident_cells, [&](
auto const& cell) {
1224 if (incident_13 + incident_22 + incident_31 != 6 ||
1225 !std::ranges::all_of(incident_cells, [](
auto const& cell) {
1232 return incident_31 == 3 && incident_22 == 0 && incident_13 == 3;
1241 -> std::expected<ApplicableSixTwoMove, MoveError>
1244 if (candidate ==
nullptr || triangulation.dimension() != 3 ||
1245 !triangulation.tds().is_vertex(candidate) ||
1246 triangulation.is_infinite(candidate))
1254 return ApplicableSixTwoMove{candidate->point()};
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,
1265 Post_mutation_validator post_mutation_validator)
1266 -> std::optional<Delaunay>;
1271 template <std::uniform_random_bit_generator Generator,
1272 typename Post_mutation_validator>
1273 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1275 Delaunay const& source_triangulation, ApplicableSixTwoMove
const& move,
1276 Generator& generator, Post_mutation_validator post_mutation_validator)
1277 -> std::expected<Delaunay, MoveError>
1280 Delaunay triangulation{source_triangulation};
1282 triangulation, move.vertex_point());
1283 if (!copied_candidate)
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();
1294 triangulation.finite_incident_edges(candidate,
1295 std::back_inserter(incident_edges));
1296 detail::canonicalize(incident_edges);
1297 std::ranges::shuffle(incident_edges, generator);
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;
1304 auto flipped =
false;
1305 for (
auto const& edge : incident_edges)
1307 auto const canonical_edge =
1309 if (is_timelike(edge) && canonical_edge && tds.flip(*canonical_edge))
1315 if (!flipped || tds.degree(candidate) != 4 || !tds.is_valid())
1320 tds.remove_from_maximal_dimension_simplex(candidate);
1321 if (!post_mutation_validator(
static_cast<Delaunay const&
>(triangulation)) ||
1323 triangulation.number_of_finite_cells() + 4 != old_cells ||
1324 triangulation.number_of_vertices() + 1 != old_vertices)
1329 for (
auto const cell : triangulation.finite_cell_handles())
1339 return triangulation;
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,
1349 Post_mutation_validator post_mutation_validator)
1350 -> std::optional<Delaunay>
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);
1371 template <std::uniform_random_bit_generator Generator>
1373 Delaunay const& source_triangulation,
1375 -> std::optional<Delaunay>
1377 return detail::try_62_move_impl(source_triangulation, source_candidate,
1403 template <std::uniform_random_bit_generator Generator>
1407 auto triangulation = t_manifold.delaunay_snapshot();
1409 detail::canonicalize(vertices);
1411 std::ranges::shuffle(vertices, generator);
1412 if (vertices.empty())
1421 for (
auto const& vertex : vertices)
1426 last_error = prepared.error();
1435 last_error = moved.error();
1437 return std::unexpected{last_error};
1446 template <std::uniform_random_bit_generator Generator>
1450 auto triangulation = t_manifold.delaunay_snapshot();
1452 auto const candidate =
1460 if (!prepared) {
return std::unexpected{prepared.error()}; }
1463 if (!moved) {
return std::unexpected{moved.error()}; }
1475 -> std::optional<Cell_container>
1490 -> std::optional<Cell_container>
1493 auto incident_cells =
1495 if (!incident_cells || incident_cells->size() != 4) {
return std::nullopt; }
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; }
1503 auto const cell_type_count = [&](
CellType const type) {
1504 return std::ranges::count_if(*incident_cells, [&](
auto const cell) {
1512 return incident_cells;
1514 return std::nullopt;
1522 -> std::expected<ApplicableFourFourMove, MoveError>
1526 !triangulation.tds().is_edge(candidate.first, candidate.second,
1532 auto const incident_cells =
1534 if (!incident_cells)
1539 auto const first = candidate.first->vertex(candidate.second);
1540 auto const second = candidate.first->vertex(candidate.third);
1543 for (
auto const& cell : *incident_cells)
1545 for (
auto index = 0; index < 4; ++index)
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))
1557 if (top ==
nullptr || bottom ==
nullptr || top == bottom || top == first ||
1558 top == second || bottom == first || bottom == second)
1563 auto const incident_count = [&](
Vertex_handle const vertex) {
1564 return std::ranges::count_if(
1566 [&](
Cell_handle const cell) {
return cell->has_vertex(vertex); });
1568 if (incident_count(top) != 2 || incident_count(bottom) != 2)
1573 return ApplicableFourFourMove{canonical_edge_points(candidate),
1574 top->point(), bottom->point()};
1584 -> std::expected<ApplicableFourFourMove, MoveError>
1588 bottom ==
nullptr ||
1589 !triangulation.tds().is_edge(candidate.first, candidate.second,
1591 !triangulation.tds().is_vertex(top) ||
1592 !triangulation.tds().is_vertex(bottom) ||
1593 triangulation.is_infinite(top) || triangulation.is_infinite(bottom))
1598 auto const first = candidate.first->vertex(candidate.second);
1599 auto const second = candidate.first->vertex(candidate.third);
1600 auto const incident_cells =
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();
1611 auto const incident_count = [&](
Vertex_handle const vertex) {
1612 return std::ranges::count_if(
1614 [&](
Cell_handle const cell) {
return cell->has_vertex(vertex); });
1616 if (incident_count(top) != 2 || incident_count(bottom) != 2)
1621 return ApplicableFourFourMove{canonical_edge_points(candidate),
1622 top->point(), bottom->point()};
1627 template <
typename Post_mutation_val
idator>
1628 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1629 [[nodiscard]]
inline auto bistellar_flip_impl(
1632 Post_mutation_validator post_mutation_validator)
1633 -> std::optional<Delaunay>;
1638 template <
typename Post_mutation_val
idator>
1639 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1641 Delaunay const& source_triangulation, ApplicableFourFourMove
const& move,
1642 Post_mutation_validator post_mutation_validator)
1643 -> std::expected<Delaunay, MoveError>
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)
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))
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))
1687 if (cell_precedes(other_boundary_cell, boundary_facet_cell))
1689 boundary_facet_index = other_boundary_cell->index(boundary_facet_cell);
1690 boundary_facet_cell = other_boundary_cell;
1692 if (!triangulation.tds().flip(
1693 Delaunay::Facet{boundary_facet_cell, boundary_facet_index}))
1698 auto const old_edge = resolve_edge(triangulation, move.edge_points());
1703 auto const canonical_old_edge =
1705 if (!canonical_old_edge)
1709 if (!triangulation.tds().flip(*canonical_old_edge))
1714 if (!post_mutation_validator(
static_cast<Delaunay const&
>(triangulation)) ||
1715 !triangulation.tds().is_valid())
1720 for (
auto const cell : triangulation.finite_cell_handles())
1726 return triangulation;
1730 template <
typename Post_mutation_val
idator>
1731 requires std::predicate<Post_mutation_validator&, Delaunay const&>
1732 [[nodiscard]]
inline auto detail::bistellar_flip_impl(
1735 Post_mutation_validator post_mutation_validator)
1736 -> std::optional<Delaunay>
1738 auto const prepared = prepare_bistellar_flip(
1739 source_triangulation, source_edge, source_top, source_bottom);
1740 if (!prepared) {
return std::nullopt; }
1742 execute(source_triangulation, *prepared, post_mutation_validator);
1743 if (!moved) {
return std::nullopt; }
1744 return std::move(*moved);
1759 -> std::optional<Delaunay>
1761 return detail::bistellar_flip_impl(source_triangulation, source_edge,
1762 source_top, source_bottom,
1769 -> std::optional<Edge_handle>
1771 for (
auto const& edge : edges)
1773 if (
auto incident_cells =
1776 if (incident_cells->size() == 4) {
return edge; }
1779 return std::nullopt;
1788 std::unordered_set<Vertex_handle> vertices;
1790 for (
int i = 0; i < 4; ++i) { vertices.emplace(cell->vertex(i)); }
1821 template <std::uniform_random_bit_generator Generator>
1825 auto triangulation = t_manifold.delaunay_snapshot();
1829 detail::canonicalize(spacelike_edges);
1831 std::ranges::shuffle(spacelike_edges, generator);
1832 if (spacelike_edges.empty())
1841 for (
auto const& edge : spacelike_edges)
1846 last_error = prepared.error();
1855 last_error = flipped.error();
1857 return std::unexpected{last_error};
1867 template <std::uniform_random_bit_generator Generator>
1871 auto triangulation = t_manifold.delaunay_snapshot();
1875 auto const candidate =
1884 if (!prepared) {
return std::unexpected{prepared.error()}; }
1891 return std::unexpected{flipped.error()};
1907 if (!t_after.is_structurally_correct() ||
1909 t_before.initial_radius()) ||
1911 t_before.foliation_spacing()))
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 &&
1959 t_after.N1() == t_before.N1() + 5 &&
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 &&
1971 t_after.N1() == t_before.N1() - 5 &&
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;
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.
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.
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
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.