CDT++
Causal Dynamical Triangulations in C++
Loading...
Searching...
No Matches
Bistellar_flip_test.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Causal Dynamical Triangulations in C++ using CGAL
3
4 Copyright © 2025 Adam Getchell
5 ******************************************************************************/
6
15
16#include "Ergodic_moves_3.hpp"
17
18#include <doctest/doctest.h>
19#include <numbers>
20
21using namespace std;
22using namespace manifolds;
23using namespace ergodic_moves;
24
25// Constants for testing
26static inline std::floating_point auto constexpr SQRT_2 =
27 std::numbers::sqrt2_v<double>;
28static inline std::floating_point auto constexpr INV_SQRT_2 = 1 / SQRT_2;
29
30// Helper function to create a triangulation for testing
31auto create_test_triangulation() -> Delaunay
32{
33 // Create a 6-vertex triangulation that can support bistellar flips
34 // This uses the same configuration as the reference implementation
35
36 vector vertices{
37 Point_t<3>{ 0, 0, 0}, // Bottom vertex (t=0)
38 Point_t<3>{ INV_SQRT_2, 0, INV_SQRT_2}, // Middle layer (t=1)
39 Point_t<3>{ 0, INV_SQRT_2, INV_SQRT_2}, // Middle layer (t=1)
40 Point_t<3>{-INV_SQRT_2, 0, INV_SQRT_2}, // Middle layer (t=1)
41 Point_t<3>{ 0, -INV_SQRT_2, INV_SQRT_2}, // Middle layer (t=1)
42 Point_t<3>{ 0, 0, 2} // Top vertex (t=2)
43 };
44
45 // Create the Delaunay triangulation
46 Delaunay triangulation(vertices.begin(), vertices.end());
47
48 // Set time values for vertices based on z-coordinate
49 for (auto vit = triangulation.finite_vertices_begin();
50 vit != triangulation.finite_vertices_end(); ++vit)
51 {
52 auto z = vit->point().z();
53 if (z < 0.1) {
54 vit->info() = 0; // Bottom vertex
55 }
56 else if (z < 1.5) {
57 vit->info() = 1; // Middle layer vertices
58 }
59 else {
60 vit->info() = 2; // Top vertex
61 }
62 }
63
64 return triangulation;
65}
66
67// Helper function to verify that a triangulation is valid
68auto verify_triangulation_validity(Delaunay const& triangulation) -> bool
69{
70 return triangulation.is_valid() &&
71 triangulation.tds().is_valid();
72}
73
74// Helper function to verify that all neighbor relationships are bidirectional
75auto verify_neighbor_relationships(Delaunay const& triangulation) -> bool
76{
77 for (auto cit = triangulation.finite_cells_begin();
78 cit != triangulation.finite_cells_end(); ++cit)
79 {
80 for (int i = 0; i < 4; ++i)
81 {
82 auto neighbor = cit->neighbor(i);
83 if (triangulation.is_infinite(neighbor))
84 continue;
85
86 // Check if the neighbor has this cell as its neighbor
87 bool has_reciprocal_neighbor = false;
88 for (int j = 0; j < 4; ++j)
89 {
90 Cell_handle cell_handle = cit;
91 if (neighbor->neighbor(j) == cell_handle)
92 {
93 has_reciprocal_neighbor = true;
94 break;
95 }
96 }
97
98 if (!has_reciprocal_neighbor)
99 {
100 return false;
101 }
102 }
103 }
104 return true;
105}
106
107SCENARIO("Perform basic bistellar flip on Delaunay triangulation" *
108 doctest::test_suite("bistellar_flip"))
109{
110 GIVEN("A triangulation setup for a bistellar flip")
111 {
112 auto triangulation = create_test_triangulation();
113
114 WHEN("We have a valid triangulation")
115 {
116 REQUIRE(triangulation.is_valid());
117 THEN("We can find a pivot edge for the bistellar flip")
118 {
119 auto top = triangulation.insert(Point_t<3>{0, 0, 2});
120 auto bottom = triangulation.insert(Point_t<3>{0, 0, 0});
121 auto edges = foliated_triangulations::collect_edges<3>(triangulation);
122 auto pivot_edge = find_pivot_edge(triangulation, edges);
123
124 REQUIRE_MESSAGE(pivot_edge, "No pivot edge found.");
125 CHECK(triangulation.tds().is_edge(
126 pivot_edge->first, pivot_edge->second, pivot_edge->third));
127
128 AND_THEN("We can perform a bistellar flip")
129 {
130 auto flipped_triangulation = bistellar_flip(
131 triangulation, pivot_edge.value(), top, bottom);
132
133 REQUIRE_MESSAGE(flipped_triangulation, "Bistellar flip failed.");
134
135 // Basic validity check
136 CHECK(flipped_triangulation->is_valid());
137 CHECK(flipped_triangulation->tds().is_valid());
138
139 // Verify same number of vertices, edges, and cells
140 CHECK_EQ(flipped_triangulation->number_of_vertices(),
141 triangulation.number_of_vertices());
142 CHECK_EQ(flipped_triangulation->number_of_finite_edges(),
143 triangulation.number_of_finite_edges());
144 CHECK_EQ(flipped_triangulation->number_of_finite_cells(),
145 triangulation.number_of_finite_cells());
146 }
147 }
148 }
149 }
150}
151
152SCENARIO("Verify neighbor relationships after bistellar flip" *
153 doctest::test_suite("bistellar_flip"))
154{
155 GIVEN("A triangulation setup for a bistellar flip")
156 {
157 auto triangulation = create_test_triangulation();
158
159 WHEN("We perform a bistellar flip")
160 {
161 auto top = triangulation.insert(Point_t<3>{0, 0, 2});
162 auto bottom = triangulation.insert(Point_t<3>{0, 0, 0});
163 auto edges = foliated_triangulations::collect_edges<3>(triangulation);
164 auto pivot_edge = find_pivot_edge(triangulation, edges);
165
166 REQUIRE_MESSAGE(pivot_edge, "No pivot edge found.");
167
168 auto flipped_triangulation = bistellar_flip(
169 triangulation, pivot_edge.value(), top, bottom);
170
171 REQUIRE_MESSAGE(flipped_triangulation, "Bistellar flip failed.");
172
173 THEN("All neighbor relationships are bidirectional")
174 {
175 CHECK(verify_neighbor_relationships(flipped_triangulation.value()));
176
177 AND_THEN("Each cell has exactly four neighbors")
178 {
179 bool all_cells_have_four_neighbors = true;
180 for (auto cit = flipped_triangulation->finite_cells_begin();
181 cit != flipped_triangulation->finite_cells_end(); ++cit)
182 {
183 int neighbor_count = 0;
184 for (int i = 0; i < 4; ++i)
185 {
186 if (!flipped_triangulation->is_infinite(cit->neighbor(i)))
187 neighbor_count++;
188 }
189
190 if (neighbor_count != 4 && neighbor_count != 3) // Allow boundary cells with 3 neighbors
191 {
192 all_cells_have_four_neighbors = false;
193 break;
194 }
195 }
196
197 CHECK(all_cells_have_four_neighbors);
198 }
199 }
200 }
201 }
202}
203
204SCENARIO("Verify cell orientation and vertex ordering after bistellar flip" *
205 doctest::test_suite("bistellar_flip"))
206{
207 GIVEN("A triangulation setup for a bistellar flip")
208 {
209 auto triangulation = create_test_triangulation();
210
211 WHEN("We perform a bistellar flip")
212 {
213 auto top = triangulation.insert(Point_t<3>{0, 0, 2});
214 auto bottom = triangulation.insert(Point_t<3>{0, 0, 0});
215 auto edges = foliated_triangulations::collect_edges<3>(triangulation);
216 auto pivot_edge = find_pivot_edge(triangulation, edges);
217
218 REQUIRE_MESSAGE(pivot_edge, "No pivot edge found.");
219
220 auto flipped_triangulation = bistellar_flip(
221 triangulation, pivot_edge.value(), top, bottom);
222
223 REQUIRE_MESSAGE(flipped_triangulation, "Bistellar flip failed.");
224
225 THEN("All cells have correct orientation")
226 {
227 // CGAL's is_valid() checks for proper orientation
228 // FoliatedTriangulation doesn't have is_valid() method
229 // Validity will be checked through the Manifold_3 below
230
231 AND_THEN("Vertex ordering is consistent")
232 {
233 bool consistent_ordering = true;
234 for (auto cit = flipped_triangulation->finite_cells_begin();
235 cit != flipped_triangulation->finite_cells_end(); ++cit)
236 {
237 // CGAL ensures that vertices are ordered so that the orientation is positive
238 // This is implicitly checked by is_valid(), but we can verify the determinant is positive
239 auto v0 = cit->vertex(0)->point();
240 auto v1 = cit->vertex(1)->point();
241 auto v2 = cit->vertex(2)->point();
242 auto v3 = cit->vertex(3)->point();
243
244 // Orientation is checked by CGAL's is_valid, so we don't need to manually calculate it
245 // Just check that each vertex is unique
246 if (v0 == v1 || v0 == v2 || v0 == v3 ||
247 v1 == v2 || v1 == v3 || v2 == v3)
248 {
249 consistent_ordering = false;
250 break;
251 }
252 }
253
254 CHECK(consistent_ordering);
255 }
256 }
257 }
258 }
259}
260
261SCENARIO("Test edge cases and error conditions for bistellar flip" *
262 doctest::test_suite("bistellar_flip"))
263{
264 GIVEN("A triangulation setup for a bistellar flip")
265 {
266 auto triangulation = create_test_triangulation();
267 auto top = triangulation.insert(Point_t<3>{0, 0, 2});
268 auto bottom = triangulation.insert(Point_t<3>{0, 0, 0});
269 auto edges = foliated_triangulations::collect_edges<3>(triangulation);
270 auto pivot_edge = find_pivot_edge(triangulation, edges);
271
272 REQUIRE_MESSAGE(pivot_edge, "No pivot edge found.");
273
274 WHEN("We provide an invalid edge")
275 {
276 // Create an invalid edge
277 Edge_handle invalid_edge;
278 invalid_edge.first = nullptr;
279 invalid_edge.second = 0;
280 invalid_edge.third = 1;
281
282 auto result = bistellar_flip(triangulation, invalid_edge, top, bottom);
283
284 THEN("The bistellar flip should fail")
285 {
286 CHECK_FALSE(result.has_value());
287 }
288 }
289
290 WHEN("We provide null vertex handles")
291 {
292 auto result = bistellar_flip(triangulation, pivot_edge.value(), nullptr, bottom);
293
294 THEN("The bistellar flip should fail")
295 {
296 CHECK_FALSE(result.has_value());
297 }
298
299 auto result2 = bistellar_flip(triangulation, pivot_edge.value(), top, nullptr);
300
301 THEN("The bistellar flip should fail")
302 {
303 CHECK_FALSE(result2.has_value());
304 }
305 }
306
307 WHEN("We provide infinite vertices")
308 {
309 auto infinite_vertex = triangulation.infinite_vertex();
310 auto result = bistellar_flip(triangulation, pivot_edge.value(), infinite_vertex, bottom);
311
312 THEN("The bistellar flip should fail")
313 {
314 CHECK_FALSE(result.has_value());
315 }
316
317 auto result2 = bistellar_flip(triangulation, pivot_edge.value(), top, infinite_vertex);
318
319 THEN("The bistellar flip should fail")
320 {
321 CHECK_FALSE(result2.has_value());
322 }
323 }
324 }
325}
326
327SCENARIO("Verify that a flipped triangulation can be used in a Manifold" *
328 doctest::test_suite("bistellar_flip"))
329{
330 GIVEN("A valid triangulation that has been flipped")
331 {
332 auto triangulation = create_test_triangulation();
333 auto top = triangulation.insert(Point_t<3>{0, 0, 2});
334 auto bottom = triangulation.insert(Point_t<3>{0, 0, 0});
335 auto edges = foliated_triangulations::collect_edges<3>(triangulation);
336 auto pivot_edge = find_pivot_edge(triangulation, edges);
337
338 REQUIRE_MESSAGE(pivot_edge, "No pivot edge found.");
339
340 auto flipped_triangulation = bistellar_flip(
341 triangulation, pivot_edge.value(), top, bottom);
342
343 REQUIRE_MESSAGE(flipped_triangulation, "Bistellar flip failed.");
344
345 WHEN("We create a foliated triangulation from the flipped triangulation")
346 {
347 // Create a foliated triangulation from the flipped Delaunay triangulation
348 auto foliated_triangulation = foliated_triangulations::FoliatedTriangulation<3>(
349 flipped_triangulation.value(), 0, 1);
350
351 THEN("The foliated triangulation is valid")
352 {
353 // The FoliatedTriangulation class doesn't have is_valid() method
354 // Use is_correct() method which verifies the class invariants
355 CHECK(foliated_triangulation.is_correct());
356
357 AND_THEN("We can create a manifold from the foliated triangulation")
358 {
359 Manifold_3 manifold(foliated_triangulation);
360
361 CHECK(manifold.is_correct());
362 CHECK(manifold.is_valid());
363 }
364 }
365 }
366 }
367}
368
Pachner moves on 2+1 dimensional foliated Delaunay triangulations.
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_pivot_edge(Delaunay const &triangulation, Edge_container const &edges) -> std::optional< Edge_handle >
SCENARIO("Perform bistellar flip on Delaunay triangulation" *doctest::test_suite("bistellar"))