CDT++
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
14
15#ifndef CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
16#define CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
17
18#include <expected>
19
20#include "Formatters.hpp"
21#include "Manifold.hpp"
22#include "Move_tracker.hpp"
23
24namespace ergodic_moves
25{
26 using Manifold = manifolds::Manifold_3;
27 using Expected = std::expected<Manifold, std::string>;
28 using Cell_handle = Cell_handle_t<3>;
29 using Cell_container = std::vector<Cell_handle>;
30 using Edge_handle = Edge_handle_t<3>;
31 using Edge_container = std::vector<Edge_handle>;
32 using Vertex_handle = Vertex_handle_t<3>;
33 using Vertex_container = std::vector<Vertex_handle>;
34 using Delaunay = Delaunay_t<3>;
35
40 [[nodiscard]] inline auto null_move(Manifold const& t_manifold) noexcept
41 -> Expected
42 {
43 return t_manifold;
44 } // null_move
45
52 [[nodiscard]] inline auto try_23_move(Manifold& t_manifold,
53 Cell_handle const& to_be_moved) -> bool
54 {
55 if (to_be_moved->info() != 22) { return false; } // NOLINT
56 auto flipped = false;
57 // Try every facet of the (2,2) cell
58 for (auto i = 0; i < 4; ++i)
59 {
60 if (t_manifold.triangulation().flip(to_be_moved, i))
61 {
62#ifndef NDEBUG
63 spdlog::trace("Facet {} was flippable.\n", i);
64#endif
65 flipped = true;
66 break;
67 }
68#ifndef NDEBUG
69 spdlog::trace("Facet {} was not flippable.\n", i);
70#endif
71 }
72 return flipped;
73 } // try_23_move
74
87 [[nodiscard]] inline auto do_23_move(Manifold& t_manifold) -> Expected
88 {
89#ifndef NDEBUG
90 spdlog::debug("{} called.\n", __PRETTY_FUNCTION__);
91#endif
92
93 auto two_two = t_manifold.get_triangulation().get_two_two();
94 // Shuffle the container to create a random sequence of (2,2) cells
95 std::ranges::shuffle(two_two, utilities::make_random_generator());
96 // Try a (2,3) move on successive cells in the sequence
97 if (std::ranges::any_of(
98 two_two, [&](auto& cell) { return try_23_move(t_manifold, cell); }))
99 {
100 return t_manifold;
101 }
102 // We've run out of (2,2) cells
103 std::string const msg = "No (2,3) move possible.\n";
104 spdlog::warn(msg);
105 return std::unexpected(msg);
106 }
107
114 [[nodiscard]] inline auto try_32_move(Manifold& t_manifold,
115 Edge_handle const& to_be_moved) -> bool
116 {
117 return t_manifold.triangulation().flip(
118 to_be_moved.first, to_be_moved.second, to_be_moved.third);
119 } // try_32_move
120
129 [[nodiscard]] inline auto do_32_move(Manifold& t_manifold) -> Expected
130 {
131#ifndef NDEBUG
132 spdlog::debug("{} called.\n", __PRETTY_FUNCTION__);
133#endif
134 auto timelike_edges = t_manifold.get_timelike_edges();
135 // Shuffle the container to create a random sequence of edges
136 std::ranges::shuffle(timelike_edges, utilities::make_random_generator());
137 // Try a (3,2) move on successive timelike edges in the sequence
138 if (std::ranges::any_of(timelike_edges, [&](auto& edge) {
139 return try_32_move(t_manifold, edge);
140 }))
141 {
142 return t_manifold;
143 }
144 // We've run out of edges to try
145 std::string const msg = "No (3,2) move possible.\n";
146 spdlog::warn(msg);
147 return std::unexpected(msg);
148 } // do_32_move()
149
155 [[nodiscard]] inline auto find_adjacent_31_cell(Cell_handle const& t_cell)
156 -> std::optional<int>
157 {
158 if (t_cell->info() != 13) { return std::nullopt; } // NOLINT
159 for (auto i = 0; i < 4; ++i)
160 {
161#ifndef NDEBUG
162 spdlog::trace("Neighbor {} is of type {}\n", i,
163 t_cell->neighbor(i)->info());
164#endif
165 if (foliated_triangulations::expected_cell_type<3>(t_cell->neighbor(i)) ==
166 Cell_type::THREE_ONE)
167 {
168 return std::make_optional(i);
169 }
170 }
171 return std::nullopt;
172 } // find_26_move()
173
188 [[nodiscard]] inline auto do_26_move(Manifold& t_manifold) -> Expected
189 {
190#ifndef NDEBUG
191 spdlog::debug("{} called.\n", __PRETTY_FUNCTION__);
192#endif
193 static auto constexpr INCIDENT_CELLS_FOR_6_2_MOVE = 6;
194 auto one_three = t_manifold.get_triangulation().get_one_three();
195 // Shuffle the container to pick a random sequence of (1,3) cells to try
196 std::ranges::shuffle(one_three, utilities::make_random_generator());
197 for (auto const& bottom : one_three)
198 {
199 if (auto neighboring_31_index = find_adjacent_31_cell(bottom);
200 neighboring_31_index)
201 {
202#ifndef NDEBUG
203 spdlog::trace("neighboring_31_index is {}.\n", *neighboring_31_index);
204#endif
205 Cell_handle const top = bottom->neighbor(neighboring_31_index.value());
206 // Calculate the common face with respect to the bottom cell
207 auto common_face_index = std::numeric_limits<int>::max();
208 if (!bottom->has_neighbor(top, common_face_index))
209 {
210 std::string const msg = "Bottom cell does not have a neighbor.\n";
211#ifndef NDEBUG
212 spdlog::trace(msg);
213#endif
214 return std::unexpected(msg);
215 }
216
217 // Get indices of vertices of common face with respect to bottom cell
218 // A face is denoted by the index of the opposite vertex
219 // Thus, the indices of the vertices of the face are all other indices
220 // except the common_face_index
221 // CGAL uses bitwise operations like (common_face_index +1) & 3
222 // We use % 4 which is equivalent and doesn't trigger clang-tidy
223 auto const i_1 = (common_face_index + 1) % 4;
224 auto const i_2 = (common_face_index + 2) % 4;
225 auto const i_3 = (common_face_index + 3) % 4;
226
227 // Get vertices of common face from indices
228 auto const v_1 = bottom->vertex(i_1);
229 auto const v_2 = bottom->vertex(i_2);
230 auto const v_3 = bottom->vertex(i_3);
231
232 // Timeslice of vertices should be same
233 if (v_1->info() != v_2->info() || v_2->info() != v_3->info())
234 {
235 std::string const msg = "Vertices have different timeslices.\n";
236#ifndef NDEBUG
237 spdlog::trace(msg);
238#endif
239 return std::unexpected(msg);
240 }
241
242 // Do the (2,6) move
243 // Insert new vertex
244 Vertex_handle const v_center =
245 t_manifold.triangulation().delaunay().tds().insert_in_facet(
246 bottom, *neighboring_31_index);
247
248 // Checks
249 Cell_container incident_cells;
250 t_manifold.triangulation().delaunay().tds().incident_cells(
251 v_center, std::back_inserter(incident_cells));
252 // the (2,6) center vertex should be bounded by 6 simplices
253 if (incident_cells.size() != INCIDENT_CELLS_FOR_6_2_MOVE)
254 {
255 std::string const msg =
256 "Center vertex is not bounded by 6 simplices.\n";
257#ifndef NDEBUG
258 spdlog::trace(msg);
259#endif
260 return std::unexpected(msg);
261 }
262
263 // Each incident cell should be combinatorially and geometrically valid
264 if (auto check_cells =
265 std::ranges::all_of(incident_cells,
266 [&t_manifold](auto const& cell) {
267 return t_manifold.get_triangulation()
268 .get_delaunay()
269 .tds()
270 .is_cell(cell);
271 });
272 !check_cells)
273 {
274 std::string const msg = "A cell is invalid.\n";
275#ifndef NDEBUG
276 spdlog::trace(msg);
277#endif
278 return std::unexpected(msg);
279 }
280
281 // Now assign a geometric point to the center vertex
282 auto center_point =
283 CGAL::centroid(v_1->point(), v_2->point(), v_3->point());
284#ifndef NDEBUG
285 spdlog::trace("Center point is: ({}).\n",
286 utilities::point_to_str(center_point));
287#endif
288 v_center->set_point(center_point);
289
290 // Assign a timevalue to the new vertex
291 auto timevalue = v_1->info();
292 v_center->info() = timevalue;
293
294#ifndef NDEBUG
295 if (t_manifold.is_vertex(v_center))
296 {
297 spdlog::trace("It's a vertex in the TDS.\n");
298 }
299 else { spdlog::trace("It's not a vertex in the TDS.\n"); }
300 spdlog::trace("Spacelike face timevalue is {}.\n", timevalue);
301 spdlog::trace("Inserted vertex ({}) with timevalue {}.\n",
302 utilities::point_to_str(v_center->point()),
303 v_center->info());
304#endif
305
306 // Final checks
307 // is_valid() checks for combinatorial and geometric validity
308 if (!t_manifold.get_delaunay().tds().is_valid(v_center, true, 1))
309 {
310 std::string const msg = "v_center is invalid.\n";
311#ifndef NDEBUG
312 spdlog::trace(msg);
313#endif
314 return std::unexpected(msg);
315 }
316
317 return t_manifold;
318 }
319 // Try next cell
320#ifndef NDEBUG
321 spdlog::debug("Cell not insertable.\n");
322#endif
323 }
324 // We've run out of (1,3) simplices to try
325 std::string const msg = "No (2,6) move possible.\n";
326 spdlog::warn(msg);
327 return std::unexpected(msg);
328 } // do_26_move()
329
338 [[nodiscard]] inline auto is_62_movable(Manifold const& manifold,
339 Vertex_handle const& candidate)
340 -> bool
341 {
342 if (manifold.dimensionality() != 3)
343 {
344#ifndef NDEBUG
345 spdlog::trace("Manifold is not 3-dimensional.\n");
346#endif
347 return false;
348 }
349
350 if (!manifold.is_vertex(candidate))
351 {
352#ifndef NDEBUG
353 spdlog::trace("Candidate is not a vertex.\n");
354#endif
355 return false;
356 }
357
358 // We must have 5 incident edges to have 6 incident cells
359 if (auto incident_edges = manifold.degree(candidate);
360 incident_edges != 5) // NOLINT
361 {
362#ifndef NDEBUG
363 spdlog::trace("Vertex has {} incident edges.\n", incident_edges);
364#endif
365 return false;
366 }
367
368 // Obtain all incident cells
369 auto const incident_cells = manifold.incident_cells(candidate);
370
371 // We must have 6 cells incident to the vertex to make a (6,2) move
372 if (incident_cells.size() != 6) // NOLINT
373 {
374#ifndef NDEBUG
375 spdlog::trace("Vertex has {} incident cells.\n", incident_cells.size());
376#endif
377 return false;
378 }
379
380 // Check that none of the incident cells are infinite
381 for (auto const& cell : incident_cells)
382 {
383 if (manifold.get_triangulation().is_infinite(cell))
384 {
385#ifndef NDEBUG
386 spdlog::trace("Cell is infinite.\n");
387#endif
388 return false;
389 }
390 }
391
392 // Run until all vertices are fixed
393 while (foliated_triangulations::fix_vertices<3>(
394 manifold.get_delaunay(), manifold.initial_radius(),
395 manifold.foliation_spacing()))
396 {
397 spdlog::warn("Fixing vertices found by is_62_movable().\n");
398 }
399
400 // Run until all cells fixed or 50 passes
401 // for (auto passes = 1; passes < foliated_triangulations::MAX_FIX_PASSES
402 // + 1;
403 // ++passes)
404 // {
405 // if (!foliated_triangulations::fix_cells<3>(manifold.get_delaunay()))
406 // {
407 // break;
408 // }
409 // spdlog::warn("Fixing cells found by is_62_movable() pass {}.\n",
410 // passes);
411 // }
412
413 auto const incident_31 = foliated_triangulations::filter_cells<3>(
414 incident_cells, Cell_type::THREE_ONE);
415 auto const incident_22 = foliated_triangulations::filter_cells<3>(
416 incident_cells, Cell_type::TWO_TWO);
417 auto const incident_13 = foliated_triangulations::filter_cells<3>(
418 incident_cells, Cell_type::ONE_THREE);
419
420 // All cells should be classified
421 if (incident_13.size() + incident_22.size() + incident_31.size() !=
422 6) // NOLINT
423 {
424 spdlog::warn("Some incident cells on this vertex need to be fixed.\n");
425 }
426
427#ifndef NDEBUG
428 spdlog::trace(
429 "Vertex has {} incident cells with {} incident (3,1) simplices and {} "
430 "incident (2,2) simplices and {} incident (1,3) simplices.\n",
431 incident_cells.size(), incident_31.size(), incident_22.size(),
432 incident_13.size());
433 foliated_triangulations::debug_print_cells<3>(std::span{incident_cells});
434#endif
435 return incident_31.size() == 3 && incident_22.empty() &&
436 incident_13.size() == 3;
437
438 } // find_62_moves()
439
458 [[nodiscard]] inline auto do_62_move(Manifold& t_manifold) -> Expected
459 {
460#ifndef NDEBUG
461 spdlog::debug("{} called.\n", __PRETTY_FUNCTION__);
462#endif
463 auto vertices = t_manifold.get_vertices();
464 // Shuffle the container to create a random sequence of vertices
465 std::ranges::shuffle(vertices, utilities::make_random_generator());
466 // Try a (6,2) move on successive vertices in the sequence
467 if (auto const movable_vertex_iterator =
468 std::ranges::find_if(vertices,
469 [&](auto const& vertex) {
470 return is_62_movable(t_manifold, vertex);
471 });
472 movable_vertex_iterator != vertices.end())
473 {
474 t_manifold.triangulation().delaunay().remove(*movable_vertex_iterator);
475 return t_manifold;
476 }
477 // We've run out of vertices to try
478 std::string const msg = "No (6,2) move possible.\n";
479 spdlog::warn(msg);
480 return std::unexpected(msg);
481 } // do_62_move()
482
489 [[nodiscard]] inline auto incident_cells_from_edge(
490 Delaunay_t<3> const& triangulation, Edge_handle const& edge)
491 -> std::optional<Cell_container>
492 {
493 if (!triangulation.tds().is_edge(edge.first, edge.second, edge.third))
494 {
495 return std::nullopt;
496 }
497 // Create the circulator of cells around the edge, starting with the cell
498 // containing the edge
499 auto circulator = triangulation.incident_cells(edge, edge.first);
500 Cell_container incident_cells;
501 // Add cells to the container until we get back to the first one in the
502 // circulator
503 do { // NOLINT(cppcoreguidelines-avoid-do-while)
504 // Ignore cells containing the infinite vertex
505 if (triangulation.is_infinite(circulator)) { continue; }
506 incident_cells.emplace_back(circulator);
507 }
508 while (++circulator != edge.first);
509#ifndef NDEBUG
510 spdlog::trace("Found {} incident cells on edge.\n", incident_cells.size());
511#endif
512 return incident_cells;
513 } // incident_cells_from_edge()
514
523 [[nodiscard]] inline auto find_bistellar_flip_location(
524 Delaunay const& triangulation, Edge_handle const& t_edge_candidate)
525 -> std::optional<Cell_container>
526 {
527 if (auto incident_cells =
528 incident_cells_from_edge(triangulation, t_edge_candidate);
529 incident_cells.has_value() && incident_cells->size() == 4)
530 {
531 return incident_cells.value();
532 }
533 return std::nullopt;
534 } // find_bistellar_flip_location()
535
540 [[nodiscard]] inline auto get_incident_cells(Delaunay const& triangulation,
541 Edge_handle const edge)
542 -> std::optional<Cell_container>
543 {
544 // Check that the edge is valid
545 if (!triangulation.tds().is_edge(edge.first, edge.second, edge.third))
546 {
547 return std::nullopt;
548 }
549
550 Cell_container incident_cells;
551 auto circulator = triangulation.incident_cells(edge, edge.first);
552 do { // NOLINT(cppcoreguidelines-avoid-do-while)
553 // filter out boundary edges with incident infinite cells
554 if (triangulation.is_infinite(circulator)) { continue; }
555 incident_cells.emplace_back(circulator);
556 }
557 while (++circulator != edge.first);
558
559 return incident_cells;
560 } // get_incident_cells()
561
570 [[nodiscard]] inline auto bistellar_flip(Delaunay triangulation,
571 Edge_handle edge, Vertex_handle top,
572 Vertex_handle bottom)
573 -> std::optional<Delaunay>
574 {
575 // Get the cells incident to the edge
576 auto incident_cells = get_incident_cells(triangulation, edge);
577
578 // Check that there are exactly 4 incident cells
579 if (!incident_cells || incident_cells->size() != 4) { return std::nullopt; }
580
581 // Check incident cells are valid
582 if (std::ranges::any_of(*incident_cells,
583 [](auto const& cell) { return !cell->is_valid(); }))
584 {
585 return std::nullopt;
586 }
587
588 // Get vertices from pivot edge
589 auto const& pivot_from_1 = edge.first->vertex(edge.second);
590 auto const& pivot_from_2 = edge.first->vertex(edge.third);
591
592 // Get vertices from cells
593 auto vertices = foliated_triangulations::get_vertices_from_cells<3>(
594 incident_cells.value());
595
596 // Get vertices for new pivot edge
597 Vertex_container new_pivot_vertices;
598 std::ranges::copy_if(vertices, std::back_inserter(new_pivot_vertices),
599 [&](auto const& vertex) {
600 return vertex != pivot_from_1 &&
601 vertex != pivot_from_2 && vertex != top &&
602 vertex != bottom;
603 });
604
605 // Check that there are exactly 2 new pivot vertices
606 if (new_pivot_vertices.size() != 2) { return std::nullopt; }
607
608 // Label the vertices in the new pivot edge
609 auto const& pivot_to_1 = new_pivot_vertices[0];
610 auto const& pivot_to_2 = new_pivot_vertices[1];
611
612 // Verify all vertices exist and are not infinite
613 if (triangulation.is_infinite(pivot_from_1) ||
614 triangulation.is_infinite(pivot_from_2) ||
615 triangulation.is_infinite(pivot_to_1) ||
616 triangulation.is_infinite(pivot_to_2) ||
617 triangulation.is_infinite(top) || triangulation.is_infinite(bottom))
618 {
619 return std::nullopt;
620 }
621
622#ifndef NDEBUG
623 spdlog::debug(
624 "Pivot from: ({}, {}), Pivot to: ({}, {}), Top: {}, Bottom: {}",
625 pivot_from_1->point(), pivot_from_2->point(), pivot_to_1->point(),
626 pivot_to_2->point(), top->point(), bottom->point());
627#endif
628
629 // Now we need to classify the cells by the vertices they contain
630 Cell_handle before_1 =
631 nullptr; // top, pivot_from_1, pivot_from_2, pivot_to_1
632 Cell_handle before_2 =
633 nullptr; // top, pivot_from_1, pivot_from_2, pivot_to_2
634 Cell_handle before_3 =
635 nullptr; // bottom, pivot_from_1, pivot_from_2, pivot_to_1
636 Cell_handle before_4 =
637 nullptr; // bottom, pivot_from_1, pivot_from_2, pivot_to_2
638
639 for (auto const& cell : incident_cells.value())
640 {
641 if (cell->has_vertex(top))
642 {
643 if (cell->has_vertex(pivot_to_1)) { before_1 = cell; }
644 else { before_2 = cell; }
645 }
646 else
647 {
648 if (cell->has_vertex(pivot_to_1)) { before_3 = cell; }
649 else { before_4 = cell; }
650 }
651 }
652
653 // Verify all cells were found
654 if (before_1 == nullptr || before_2 == nullptr || before_3 == nullptr ||
655 before_4 == nullptr)
656 {
657#ifndef NDEBUG
658 spdlog::debug("Not all cells were properly identified.");
659#endif
660 return std::nullopt;
661 }
662
663 // Verify cells are not infinite and are valid
664 if (triangulation.is_infinite(before_1) ||
665 triangulation.is_infinite(before_2) ||
666 triangulation.is_infinite(before_3) ||
667 triangulation.is_infinite(before_4) || !before_1->is_valid() ||
668 !before_2->is_valid() || !before_3->is_valid() || !before_4->is_valid())
669 {
670#ifndef NDEBUG
671 spdlog::debug("Some cells are invalid or infinite.");
672#endif
673 return std::nullopt;
674 }
675
676#ifndef NDEBUG
677 spdlog::debug("Cells in the triangulation before deleting old cells: {}",
678 triangulation.number_of_cells());
679
680 // Debug info about cells
681 spdlog::debug("Before_1: vertices {}, {}, {}, {}",
682 before_1->vertex(0)->point(), before_1->vertex(1)->point(),
683 before_1->vertex(2)->point(), before_1->vertex(3)->point());
684 spdlog::debug("Before_2: vertices {}, {}, {}, {}",
685 before_2->vertex(0)->point(), before_2->vertex(1)->point(),
686 before_2->vertex(2)->point(), before_2->vertex(3)->point());
687 spdlog::debug("Before_3: vertices {}, {}, {}, {}",
688 before_3->vertex(0)->point(), before_3->vertex(1)->point(),
689 before_3->vertex(2)->point(), before_3->vertex(3)->point());
690 spdlog::debug("Before_4: vertices {}, {}, {}, {}",
691 before_4->vertex(0)->point(), before_4->vertex(1)->point(),
692 before_4->vertex(2)->point(), before_4->vertex(3)->point());
693#endif
694
695 // Find the exterior neighbors of the cells
696 Cell_handle n_1 = before_1->neighbor(before_1->index(pivot_from_2));
697 Cell_handle n_2 = before_1->neighbor(before_1->index(pivot_from_1));
698 Cell_handle n_3 = before_2->neighbor(before_2->index(pivot_from_1));
699 Cell_handle n_4 = before_2->neighbor(before_2->index(pivot_from_2));
700 Cell_handle n_5 = before_3->neighbor(before_3->index(pivot_from_2));
701 Cell_handle n_6 = before_3->neighbor(before_3->index(pivot_from_1));
702 Cell_handle n_7 = before_4->neighbor(before_4->index(pivot_from_1));
703 Cell_handle n_8 = before_4->neighbor(before_4->index(pivot_from_2));
704
705 // Store indices before deletion
706 int n1_idx = n_1->index(before_1);
707 int n2_idx = n_2->index(before_1);
708 int n3_idx = n_3->index(before_2);
709 int n4_idx = n_4->index(before_2);
710 int n5_idx = n_5->index(before_3);
711 int n6_idx = n_6->index(before_3);
712 int n7_idx = n_7->index(before_4);
713 int n8_idx = n_8->index(before_4);
714
715 // Verify all neighbors exist and are not cells we're about to delete
716 if (n_1 == nullptr || n_2 == nullptr || n_3 == nullptr || n_4 == nullptr ||
717 n_5 == nullptr || n_6 == nullptr || n_7 == nullptr || n_8 == nullptr ||
718 n_1 == before_1 || n_1 == before_2 || n_1 == before_3 ||
719 n_1 == before_4 || n_2 == before_1 || n_2 == before_2 ||
720 n_2 == before_3 || n_2 == before_4 || n_3 == before_1 ||
721 n_3 == before_2 || n_3 == before_3 || n_3 == before_4 ||
722 n_4 == before_1 || n_4 == before_2 || n_4 == before_3 ||
723 n_4 == before_4 || n_5 == before_1 || n_5 == before_2 ||
724 n_5 == before_3 || n_5 == before_4 || n_6 == before_1 ||
725 n_6 == before_2 || n_6 == before_3 || n_6 == before_4 ||
726 n_7 == before_1 || n_7 == before_2 || n_7 == before_3 ||
727 n_7 == before_4 || n_8 == before_1 || n_8 == before_2 ||
728 n_8 == before_3 || n_8 == before_4)
729 {
730#ifndef NDEBUG
731 spdlog::debug("Issue with neighbors");
732#endif
733 return std::nullopt;
734 }
735
736 // Temporarily break neighbor relationships to avoid potential issues
737 // This step is important to avoid having old cells pointing to new cells
738 // during deletion
739 triangulation.tds().set_adjacency(before_1, before_1->index(pivot_from_2),
740 nullptr, -1);
741 triangulation.tds().set_adjacency(before_1, before_1->index(pivot_from_1),
742 nullptr, -1);
743 triangulation.tds().set_adjacency(before_2, before_2->index(pivot_from_1),
744 nullptr, -1);
745 triangulation.tds().set_adjacency(before_2, before_2->index(pivot_from_2),
746 nullptr, -1);
747 triangulation.tds().set_adjacency(before_3, before_3->index(pivot_from_2),
748 nullptr, -1);
749 triangulation.tds().set_adjacency(before_3, before_3->index(pivot_from_1),
750 nullptr, -1);
751 triangulation.tds().set_adjacency(before_4, before_4->index(pivot_from_1),
752 nullptr, -1);
753 triangulation.tds().set_adjacency(before_4, before_4->index(pivot_from_2),
754 nullptr, -1);
755
756 // Next, delete the old cells
757 triangulation.tds().delete_cell(before_1);
758 triangulation.tds().delete_cell(before_2);
759 triangulation.tds().delete_cell(before_3);
760 triangulation.tds().delete_cell(before_4);
761
762#ifndef NDEBUG
763 spdlog::debug("Cells in the triangulation after deleting old cells: {}",
764 triangulation.number_of_cells());
765#endif
766
767 // Create new cells with consistent vertex ordering (counter-clockwise
768 // orientation) Use a consistent ordering scheme for all tetrahedra
769 Cell_handle after_1 = triangulation.tds().create_cell(
770 pivot_to_1, pivot_to_2, pivot_from_1, top);
771 Cell_handle after_2 = triangulation.tds().create_cell(
772 pivot_to_1, pivot_to_2, pivot_from_2, top);
773 Cell_handle after_3 = triangulation.tds().create_cell(
774 pivot_to_1, pivot_to_2, pivot_from_1, bottom);
775 Cell_handle after_4 = triangulation.tds().create_cell(
776 pivot_to_1, pivot_to_2, pivot_from_2, bottom);
777
778 // Verify each cell was created and is valid
779 if (after_1 == nullptr || after_2 == nullptr || after_3 == nullptr ||
780 after_4 == nullptr || !after_1->is_valid() || !after_2->is_valid() ||
781 !after_3->is_valid() || !after_4->is_valid())
782 {
783#ifndef NDEBUG
784 spdlog::debug("Failed to create new cells properly");
785#endif
786 return std::nullopt;
787 }
788
789 // Copy the cell type information from the original cells to maintain cell
790 // type
791 after_1->info() = before_1->info();
792 after_2->info() = before_2->info();
793 after_3->info() = before_3->info();
794 after_4->info() = before_4->info();
795
796#ifndef NDEBUG
797 spdlog::debug("After_1: vertices {}, {}, {}, {}",
798 after_1->vertex(0)->point(), after_1->vertex(1)->point(),
799 after_1->vertex(2)->point(), after_1->vertex(3)->point());
800 spdlog::debug("After_2: vertices {}, {}, {}, {}",
801 after_2->vertex(0)->point(), after_2->vertex(1)->point(),
802 after_2->vertex(2)->point(), after_2->vertex(3)->point());
803 spdlog::debug("After_3: vertices {}, {}, {}, {}",
804 after_3->vertex(0)->point(), after_3->vertex(1)->point(),
805 after_3->vertex(2)->point(), after_3->vertex(3)->point());
806 spdlog::debug("After_4: vertices {}, {}, {}, {}",
807 after_4->vertex(0)->point(), after_4->vertex(1)->point(),
808 after_4->vertex(2)->point(), after_4->vertex(3)->point());
809#endif
810
811 // Set up internal neighbor relationships between the new cells
812 // Find the correct indices for each cell based on the vertex that's not
813 // part of the shared face
814 int after_1_after_2_idx = after_1->index(pivot_from_1);
815 int after_2_after_1_idx = after_2->index(pivot_from_2);
816
817 int after_1_after_3_idx = after_1->index(top);
818 int after_3_after_1_idx = after_3->index(bottom);
819
820 int after_2_after_4_idx = after_2->index(top);
821 int after_4_after_2_idx = after_4->index(bottom);
822
823 int after_3_after_4_idx = after_3->index(pivot_from_1);
824 int after_4_after_3_idx = after_4->index(pivot_from_2);
825
826 // Set up internal neighbor relationships with proper indices
827 triangulation.tds().set_adjacency(after_1, after_1_after_2_idx, after_2,
828 after_2_after_1_idx);
829 triangulation.tds().set_adjacency(after_1, after_1_after_3_idx, after_3,
830 after_3_after_1_idx);
831 triangulation.tds().set_adjacency(after_2, after_2_after_4_idx, after_4,
832 after_4_after_2_idx);
833 triangulation.tds().set_adjacency(after_3, after_3_after_4_idx, after_4,
834 after_4_after_3_idx);
835
836 // Verify internal neighbor relationships are set up correctly
837 if (!after_1->has_neighbor(after_2) || !after_2->has_neighbor(after_1) ||
838 !after_1->has_neighbor(after_3) || !after_3->has_neighbor(after_1) ||
839 !after_2->has_neighbor(after_4) || !after_4->has_neighbor(after_2) ||
840 !after_3->has_neighbor(after_4) || !after_4->has_neighbor(after_3))
841 {
842#ifndef NDEBUG
843 spdlog::debug("Internal neighbor relationships are not set up correctly");
844#endif
845 return std::nullopt;
846 }
847
848 // Find the correct indices for external neighbor relationships
849 // These are the facets that are not shared with other new cells
850 int after_1_ext_1_idx = after_1->index(pivot_to_2);
851 int after_1_ext_2_idx = after_1->index(pivot_to_1);
852
853 int after_2_ext_1_idx = after_2->index(pivot_to_2);
854 int after_2_ext_2_idx = after_2->index(pivot_to_1);
855
856 int after_3_ext_1_idx = after_3->index(pivot_to_2);
857 int after_3_ext_2_idx = after_3->index(pivot_to_1);
858
859 int after_4_ext_1_idx = after_4->index(pivot_to_2);
860 int after_4_ext_2_idx = after_4->index(pivot_to_1);
861
862 // Set up external neighbor relationships
863 triangulation.tds().set_adjacency(after_1, after_1_ext_1_idx, n_1, n1_idx);
864 triangulation.tds().set_adjacency(after_1, after_1_ext_2_idx, n_2, n2_idx);
865 triangulation.tds().set_adjacency(after_2, after_2_ext_1_idx, n_3, n3_idx);
866 triangulation.tds().set_adjacency(after_2, after_2_ext_2_idx, n_4, n4_idx);
867 triangulation.tds().set_adjacency(after_3, after_3_ext_1_idx, n_5, n5_idx);
868 triangulation.tds().set_adjacency(after_3, after_3_ext_2_idx, n_6, n6_idx);
869 triangulation.tds().set_adjacency(after_4, after_4_ext_1_idx, n_7, n7_idx);
870 triangulation.tds().set_adjacency(after_4, after_4_ext_2_idx, n_8, n8_idx);
871
872 // Verify external neighbor relationships are set up correctly
873 bool external_neighbors_ok = after_1->neighbor(after_1_ext_1_idx) == n_1 &&
874 n_1->neighbor(n1_idx) == after_1 &&
875 after_1->neighbor(after_1_ext_2_idx) == n_2 &&
876 n_2->neighbor(n2_idx) == after_1 &&
877 after_2->neighbor(after_2_ext_1_idx) == n_3 &&
878 n_3->neighbor(n3_idx) == after_2 &&
879 after_2->neighbor(after_2_ext_2_idx) == n_4 &&
880 n_4->neighbor(n4_idx) == after_2 &&
881 after_3->neighbor(after_3_ext_1_idx) == n_5 &&
882 n_5->neighbor(n5_idx) == after_3 &&
883 after_3->neighbor(after_3_ext_2_idx) == n_6 &&
884 n_6->neighbor(n6_idx) == after_3 &&
885 after_4->neighbor(after_4_ext_1_idx) == n_7 &&
886 n_7->neighbor(n7_idx) == after_4 &&
887 after_4->neighbor(after_4_ext_2_idx) == n_8 &&
888 n_8->neighbor(n8_idx) == after_4;
889
890 if (!external_neighbors_ok)
891 {
892#ifndef NDEBUG
893 spdlog::debug("External neighbor relationships are not set up correctly");
894#endif
895 // Don't return null here - attempt to fix with reorientation
896 }
897
898#ifndef NDEBUG
899 spdlog::debug("Cells in the triangulation after adding new cells: {}",
900 triangulation.number_of_cells());
901
902 // Verify neighbor relationships
903 spdlog::debug("after_1 neighbors after_2: {}",
904 after_1->has_neighbor(after_2));
905 spdlog::debug("after_2 neighbors after_1: {}",
906 after_2->has_neighbor(after_1));
907 spdlog::debug("after_1 neighbors after_3: {}",
908 after_1->has_neighbor(after_3));
909 spdlog::debug("after_3 neighbors after_1: {}",
910 after_3->has_neighbor(after_1));
911 spdlog::debug("after_2 neighbors after_4: {}",
912 after_2->has_neighbor(after_4));
913 spdlog::debug("after_4 neighbors after_2: {}",
914 after_4->has_neighbor(after_2));
915 spdlog::debug("after_3 neighbors after_4: {}",
916 after_3->has_neighbor(after_4));
917 spdlog::debug("after_4 neighbors after_3: {}",
918 after_4->has_neighbor(after_3));
919#endif
920
921 // Attempt to fix orientation issues if any exist
922 triangulation.tds().reorient();
923
924 // Final validation
925 if (!triangulation.tds().is_valid(true, 4))
926 {
927#ifndef NDEBUG
928 spdlog::debug("Final triangulation is not valid after reorientation");
929#endif
930 return std::nullopt;
931 }
932
933 return std::make_optional(triangulation);
934 } // bistellar_flip()
935
937 [[nodiscard]] inline auto find_pivot_edge(Delaunay const& triangulation,
938 Edge_container const& edges)
939 -> std::optional<Edge_handle>
940 {
941 for (auto const& edge : edges)
942 {
943 auto circulator = triangulation.incident_cells(edge, edge.first);
944 Cell_container incident_cells;
945 do { // NOLINT(cppcoreguidelines-avoid-do-while)
946 // filter out boundary edges with incident infinite cells
947 if (triangulation.is_infinite(circulator)) { continue; }
948 incident_cells.emplace_back(circulator);
949 }
950 while (++circulator != edge.first);
951 fmt::print("Edge has {} incident finite cells\n", incident_cells.size());
952 if (incident_cells.size() == 4) { return edge; }
953 }
954 return std::nullopt;
955 } // find_pivot_edge()
956
960 [[nodiscard]] inline auto get_vertices(Cell_container const& cells)
961 {
962 std::unordered_set<Vertex_handle> vertices;
963 auto get_vertices = [&vertices](auto const& cell) {
964 for (int i = 0; i < 4; ++i) { vertices.emplace(cell->vertex(i)); }
965 };
966 std::ranges::for_each(cells, get_vertices);
967 Vertex_container result(vertices.begin(), vertices.end());
968 return result;
969 } // get_vertices()
970
991 [[nodiscard]] inline auto do_44_move(Manifold const& t_manifold) -> Expected
992 {
993#ifndef NDEBUG
994 spdlog::debug("{} called.\n", __PRETTY_FUNCTION__);
995#endif
996 auto spacelike_edges = t_manifold.get_spacelike_edges();
997 // Shuffle the container to pick a random sequence of edges to try
998 std::ranges::shuffle(spacelike_edges, utilities::make_random_generator());
999 for (auto const& edge : spacelike_edges)
1000 {
1001 // Obtain all incident cells
1002 if (auto const incident_cells = find_bistellar_flip_location(
1003 t_manifold.get_triangulation().get_delaunay(), edge);
1004 incident_cells)
1005 {
1006#ifndef NDEBUG
1007 for (auto const& cell : *incident_cells)
1008 {
1009 spdlog::trace("Incident cell is of type {}.\n", cell->info());
1010 }
1011#endif
1012
1013 // Get edge vertices
1014 auto const& v1 = edge.first->vertex(edge.second);
1015 auto const& v2 = edge.first->vertex(edge.third);
1016
1017 // Find top and bottom vertices
1018 Vertex_handle top = nullptr;
1019 Vertex_handle bottom = nullptr;
1020
1021 // Analyze cells to find the top and bottom vertices
1022 for (auto const& cell : *incident_cells)
1023 {
1024 for (int i = 0; i < 4; ++i)
1025 {
1026 auto vertex = cell->vertex(i);
1027 if (vertex != v1 && vertex != v2)
1028 {
1029 // Use timevalue to determine if it's a top or bottom vertex
1030 if (top == nullptr || vertex->info() > top->info())
1031 {
1032 top = vertex;
1033 }
1034 if (bottom == nullptr || vertex->info() < bottom->info())
1035 {
1036 bottom = vertex;
1037 }
1038 }
1039 }
1040 }
1041
1042 // Try the bistellar flip
1043 if (auto flipped_triangulation =
1044 bistellar_flip(t_manifold.get_triangulation().get_delaunay(),
1045 edge, top, bottom);
1046 flipped_triangulation.has_value())
1047 {
1048 // Create a new foliated triangulation with the flipped Delaunay
1049 // triangulation
1050 auto new_triangulation =
1052 flipped_triangulation.value(), t_manifold.initial_radius(),
1053 t_manifold.foliation_spacing());
1054
1055 // Create a new manifold with the updated triangulation
1056 auto new_manifold = Manifold(new_triangulation);
1057
1058 return new_manifold;
1059 }
1060
1061 // If we get here, the flip failed but we found potential cells
1062 // Log the reason and continue trying other edges
1063#ifndef NDEBUG
1064 spdlog::debug("Bistellar flip failed.");
1065#endif
1066 }
1067 // Try next edge
1068 }
1069 // We've run out of edges to try
1070 std::string const msg = "No (4,4) move possible.\n";
1071 spdlog::warn(msg);
1072 return std::unexpected(msg);
1073 } // do_44_move()
1074
1080 [[nodiscard]] inline auto check_move(Manifold const& t_before,
1081 Manifold const& t_after,
1082 move_tracker::move_type const& t_move)
1083 -> bool
1084 {
1085 switch (t_move)
1086 {
1087 case move_tracker::move_type::FOUR_FOUR:
1088 return t_after.is_valid() && t_after.N3() == t_before.N3() &&
1089 t_after.N3_31() == t_before.N3_31() &&
1090 t_after.N3_22() == t_before.N3_22() &&
1091 t_after.N3_13() == t_before.N3_13() &&
1092 t_after.N2() == t_before.N2() && t_after.N1() == t_before.N1() &&
1093 t_after.N1_TL() == t_before.N1_TL() &&
1094 t_after.N1_SL() == t_before.N1_SL() &&
1095 t_after.N0() == t_before.N0() &&
1096 t_after.max_time() == t_before.max_time() &&
1097 t_after.min_time() == t_before.min_time();
1098 case move_tracker::move_type::TWO_THREE:
1099 return t_after.is_valid() && t_after.N3() == t_before.N3() + 1 &&
1100 t_after.N3_31() == t_before.N3_31() &&
1101 t_after.N3_22() == t_before.N3_22() + 1 &&
1102 t_after.N3_13() == t_before.N3_13() &&
1103 t_after.N2() == t_before.N2() + 2 &&
1104 t_after.N1() == t_before.N1() + 1 &&
1105 t_after.N1_TL() == t_before.N1_TL() + 1 &&
1106 t_after.N1_SL() == t_before.N1_SL() &&
1107 t_after.N0() == t_before.N0() &&
1108 t_after.max_time() == t_before.max_time() &&
1109 t_after.min_time() == t_before.min_time();
1110 case move_tracker::move_type::THREE_TWO:
1111 return t_after.is_valid() && t_after.N3() == t_before.N3() - 1 &&
1112 t_after.N3_31() == t_before.N3_31() &&
1113 t_after.N3_22() == t_before.N3_22() - 1 &&
1114 t_after.N3_13() == t_before.N3_13() &&
1115 t_after.N2() == t_before.N2() - 2 &&
1116 t_after.N1() == t_before.N1() - 1 &&
1117 t_after.N1_TL() == t_before.N1_TL() - 1 &&
1118 t_after.N1_SL() == t_before.N1_SL() &&
1119 t_after.N0() == t_before.N0() &&
1120 t_after.max_time() == t_before.max_time() &&
1121 t_after.min_time() == t_before.min_time();
1122 case move_tracker::move_type::TWO_SIX:
1123 return t_after.is_valid() && t_after.N3() == t_before.N3() + 4 &&
1124 t_after.N3_31() == t_before.N3_31() + 2 &&
1125 t_after.N3_22() == t_before.N3_22() &&
1126 t_after.N3_13() == t_before.N3_13() + 2 &&
1127 t_after.N2() == t_before.N2() + 8 && // NOLINT
1128 t_after.N1() == t_before.N1() + 5 && // NOLINT
1129 t_after.N1_TL() == t_before.N1_TL() + 2 &&
1130 t_after.N1_SL() == t_before.N1_SL() + 3 &&
1131 t_after.N0() == t_before.N0() + 1 &&
1132 t_after.max_time() == t_before.max_time() &&
1133 t_after.min_time() == t_before.min_time();
1134 case move_tracker::move_type::SIX_TWO:
1135 return t_after.is_valid() && t_after.N3() == t_before.N3() - 4 &&
1136 t_after.N3_31() == t_before.N3_31() - 2 &&
1137 t_after.N3_22() == t_before.N3_22() &&
1138 t_after.N3_13() == t_before.N3_13() - 2 &&
1139 t_after.N2() == t_before.N2() - 8 && // NOLINT
1140 t_after.N1() == t_before.N1() - 5 && // NOLINT
1141 t_after.N1_TL() == t_before.N1_TL() - 2 &&
1142 t_after.N1_SL() == t_before.N1_SL() - 3 &&
1143 t_after.N0() == t_before.N0() - 1 &&
1144 t_after.max_time() == t_before.max_time() &&
1145 t_after.min_time() == t_before.min_time();
1146 default: return false;
1147 }
1148 } // check_move()
1149
1150} // namespace ergodic_moves
1151
1152#endif // CDT_PLUSPLUS_ERGODIC_MOVES_3_HPP
auto bistellar_flip(Delaunay triangulation, Edge_handle edge, Vertex_handle top, Vertex_handle bottom) -> std::optional< Delaunay >
Perform a bistellar flip on triangulation via the given edge.
auto find_adjacent_31_cell(Cell_handle const &t_cell) -> std::optional< int >
Find a (2,6) move location.
auto do_23_move(Manifold &t_manifold) -> Expected
Perform a (2,3) move.
auto try_23_move(Manifold &t_manifold, Cell_handle const &to_be_moved) -> bool
Perform a TriangulationDataStructure_3::flip on a facet.
auto do_32_move(Manifold &t_manifold) -> Expected
Perform a (3,2) move.
auto do_44_move(Manifold const &t_manifold) -> Expected
Perform a (4,4) move.
auto is_62_movable(Manifold const &manifold, Vertex_handle const &candidate) -> bool
Find a (6,2) move location.
auto null_move(Manifold const &t_manifold) noexcept -> Expected
Perform a null move.
auto get_incident_cells(Delaunay const &triangulation, Edge_handle const edge) -> std::optional< Cell_container >
Return a container of cells incident to an edge.
auto do_62_move(Manifold &t_manifold) -> Expected
Perform a (6,2) move.
auto check_move(Manifold const &t_before, Manifold const &t_after, move_tracker::move_type const &t_move) -> bool
Check move correctness.
auto do_26_move(Manifold &t_manifold) -> Expected
Perform a (2,6) move.
auto get_vertices(Cell_container const &cells)
Return a container of all vertices in a container of cells.
auto incident_cells_from_edge(Delaunay_t< 3 > const &triangulation, Edge_handle const &edge) -> std::optional< Cell_container >
Find all cells incident to the edge.
auto try_32_move(Manifold &t_manifold, Edge_handle const &to_be_moved) -> bool
Perform a TriangulationDataStructure_3::flip on an edge.
auto find_bistellar_flip_location(Delaunay const &triangulation, Edge_handle const &t_edge_candidate) -> std::optional< Cell_container >
Find a bistellar flip location.
auto find_pivot_edge(Delaunay const &triangulation, Edge_container const &edges) -> std::optional< Edge_handle >
Formatter specializations for various types.
Data structures for manifolds.
Track ergodic moves.
move_type
The types of 3D ergodic moves.