18#include <doctest/doctest.h>
22using namespace manifolds;
23using namespace ergodic_moves;
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;
31auto create_test_triangulation() -> Delaunay
38 Point_t<3>{ INV_SQRT_2, 0, INV_SQRT_2},
39 Point_t<3>{ 0, INV_SQRT_2, INV_SQRT_2},
40 Point_t<3>{-INV_SQRT_2, 0, INV_SQRT_2},
41 Point_t<3>{ 0, -INV_SQRT_2, INV_SQRT_2},
46 Delaunay triangulation(vertices.begin(), vertices.end());
49 for (
auto vit = triangulation.finite_vertices_begin();
50 vit != triangulation.finite_vertices_end(); ++vit)
52 auto z = vit->point().z();
68auto verify_triangulation_validity(Delaunay
const& triangulation) ->
bool
70 return triangulation.is_valid() &&
71 triangulation.tds().is_valid();
75auto verify_neighbor_relationships(Delaunay
const& triangulation) ->
bool
77 for (
auto cit = triangulation.finite_cells_begin();
78 cit != triangulation.finite_cells_end(); ++cit)
80 for (
int i = 0; i < 4; ++i)
82 auto neighbor = cit->neighbor(i);
83 if (triangulation.is_infinite(neighbor))
87 bool has_reciprocal_neighbor =
false;
88 for (
int j = 0; j < 4; ++j)
90 Cell_handle cell_handle = cit;
91 if (neighbor->neighbor(j) == cell_handle)
93 has_reciprocal_neighbor =
true;
98 if (!has_reciprocal_neighbor)
107SCENARIO(
"Perform basic bistellar flip on Delaunay triangulation" *
108 doctest::test_suite(
"bistellar_flip"))
110 GIVEN(
"A triangulation setup for a bistellar flip")
112 auto triangulation = create_test_triangulation();
114 WHEN(
"We have a valid triangulation")
116 REQUIRE(triangulation.is_valid());
117 THEN(
"We can find a pivot edge for the bistellar flip")
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);
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));
128 AND_THEN(
"We can perform a bistellar flip")
131 triangulation, pivot_edge.value(), top, bottom);
133 REQUIRE_MESSAGE(flipped_triangulation,
"Bistellar flip failed.");
136 CHECK(flipped_triangulation->is_valid());
137 CHECK(flipped_triangulation->tds().is_valid());
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());
152SCENARIO(
"Verify neighbor relationships after bistellar flip" *
153 doctest::test_suite(
"bistellar_flip"))
155 GIVEN(
"A triangulation setup for a bistellar flip")
157 auto triangulation = create_test_triangulation();
159 WHEN(
"We perform a bistellar flip")
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);
166 REQUIRE_MESSAGE(pivot_edge,
"No pivot edge found.");
169 triangulation, pivot_edge.value(), top, bottom);
171 REQUIRE_MESSAGE(flipped_triangulation,
"Bistellar flip failed.");
173 THEN(
"All neighbor relationships are bidirectional")
175 CHECK(verify_neighbor_relationships(flipped_triangulation.value()));
177 AND_THEN(
"Each cell has exactly four neighbors")
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)
183 int neighbor_count = 0;
184 for (
int i = 0; i < 4; ++i)
186 if (!flipped_triangulation->is_infinite(cit->neighbor(i)))
190 if (neighbor_count != 4 && neighbor_count != 3)
192 all_cells_have_four_neighbors =
false;
197 CHECK(all_cells_have_four_neighbors);
204SCENARIO(
"Verify cell orientation and vertex ordering after bistellar flip" *
205 doctest::test_suite(
"bistellar_flip"))
207 GIVEN(
"A triangulation setup for a bistellar flip")
209 auto triangulation = create_test_triangulation();
211 WHEN(
"We perform a bistellar flip")
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);
218 REQUIRE_MESSAGE(pivot_edge,
"No pivot edge found.");
221 triangulation, pivot_edge.value(), top, bottom);
223 REQUIRE_MESSAGE(flipped_triangulation,
"Bistellar flip failed.");
225 THEN(
"All cells have correct orientation")
231 AND_THEN(
"Vertex ordering is consistent")
233 bool consistent_ordering =
true;
234 for (
auto cit = flipped_triangulation->finite_cells_begin();
235 cit != flipped_triangulation->finite_cells_end(); ++cit)
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();
246 if (v0 == v1 || v0 == v2 || v0 == v3 ||
247 v1 == v2 || v1 == v3 || v2 == v3)
249 consistent_ordering =
false;
254 CHECK(consistent_ordering);
261SCENARIO(
"Test edge cases and error conditions for bistellar flip" *
262 doctest::test_suite(
"bistellar_flip"))
264 GIVEN(
"A triangulation setup for a bistellar flip")
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);
272 REQUIRE_MESSAGE(pivot_edge,
"No pivot edge found.");
274 WHEN(
"We provide an invalid edge")
277 Edge_handle invalid_edge;
278 invalid_edge.first =
nullptr;
279 invalid_edge.second = 0;
280 invalid_edge.third = 1;
282 auto result =
bistellar_flip(triangulation, invalid_edge, top, bottom);
284 THEN(
"The bistellar flip should fail")
286 CHECK_FALSE(result.has_value());
290 WHEN(
"We provide null vertex handles")
292 auto result =
bistellar_flip(triangulation, pivot_edge.value(),
nullptr, bottom);
294 THEN(
"The bistellar flip should fail")
296 CHECK_FALSE(result.has_value());
299 auto result2 =
bistellar_flip(triangulation, pivot_edge.value(), top,
nullptr);
301 THEN(
"The bistellar flip should fail")
303 CHECK_FALSE(result2.has_value());
307 WHEN(
"We provide infinite vertices")
309 auto infinite_vertex = triangulation.infinite_vertex();
310 auto result =
bistellar_flip(triangulation, pivot_edge.value(), infinite_vertex, bottom);
312 THEN(
"The bistellar flip should fail")
314 CHECK_FALSE(result.has_value());
317 auto result2 =
bistellar_flip(triangulation, pivot_edge.value(), top, infinite_vertex);
319 THEN(
"The bistellar flip should fail")
321 CHECK_FALSE(result2.has_value());
327SCENARIO(
"Verify that a flipped triangulation can be used in a Manifold" *
328 doctest::test_suite(
"bistellar_flip"))
330 GIVEN(
"A valid triangulation that has been flipped")
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);
338 REQUIRE_MESSAGE(pivot_edge,
"No pivot edge found.");
341 triangulation, pivot_edge.value(), top, bottom);
343 REQUIRE_MESSAGE(flipped_triangulation,
"Bistellar flip failed.");
345 WHEN(
"We create a foliated triangulation from the flipped triangulation")
349 flipped_triangulation.value(), 0, 1);
351 THEN(
"The foliated triangulation is valid")
355 CHECK(foliated_triangulation.is_correct());
357 AND_THEN(
"We can create a manifold from the foliated triangulation")
361 CHECK(manifold.is_correct());
362 CHECK(manifold.is_valid());
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"))