21#ifndef CDT_PLUSPLUS_FOLIATEDTRIANGULATION_HPP
22#define CDT_PLUSPLUS_FOLIATEDTRIANGULATION_HPP
24#include <CGAL/Bbox_3.h>
25#include <CGAL/Random.h>
44#include <unordered_set>
56 template <
int dimension>
57 using Delaunay_t =
typename detail::TriangulationTraits<dimension>::Delaunay;
61 template <
int dimension>
62 using Point_t =
typename detail::TriangulationTraits<dimension>::Point;
66 template <
int dimension>
74 template <
int dimension>
76 typename detail::TriangulationTraits<dimension>::Cell_handle;
82 template <
int dimension>
83 using Facet_t =
typename detail::TriangulationTraits<dimension>::Facet;
89 template <
int dimension>
91 typename detail::TriangulationTraits<dimension>::Edge_handle;
97 template <
int dimension>
99 typename detail::TriangulationTraits<dimension>::Vertex_handle;
103 template <
int dimension>
105 dimension>::Spherical_points_generator;
112 template <
typename C>
114 std::add_const_t<std::remove_reference_t<C>>>;
116 inline constexpr int MAX_FIX_PASSES = 50;
118#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
119 CDT_ENABLE_PARALLEL_TRIANGULATION
120 inline constexpr int LOCK_GRID_RESOLUTION = 50;
122 [[nodiscard]]
inline auto pad_locking_box(CGAL::Bbox_3
const& box)
125 auto const padding = std::max({1.0, (box.xmax() - box.xmin()) * 0.01,
126 (box.ymax() - box.ymin()) * 0.01,
127 (box.zmax() - box.zmin()) * 0.01});
128 return {box.xmin() - padding, box.ymin() - padding, box.zmin() - padding,
129 box.xmax() + padding, box.ymax() + padding, box.zmax() + padding};
132 [[nodiscard]]
inline auto default_locking_box() -> CGAL::Bbox_3
135 return {-extent, -extent, -extent, extent, extent, extent};
138 template <
typename Iterator,
typename Po
int_projection>
139 [[nodiscard]]
auto locking_box(Iterator first, Iterator last,
140 Point_projection point_for) -> CGAL::Bbox_3
142 if (first == last) {
return default_locking_box(); }
144 auto const box = std::accumulate(
145 std::next(first), last, std::invoke(point_for, *first).bbox(),
146 [&point_for](CGAL::Bbox_3 accumulated,
auto const& value) {
147 return accumulated + std::invoke(point_for, value).bbox();
149 return pad_locking_box(box);
152 template <
int dimension>
153 [[nodiscard]]
auto locking_box(
156 return locking_box(causal_vertices.begin(), causal_vertices.end(),
157 [](
auto const& causal_vertex) ->
auto const& {
158 return causal_vertex.first;
162 template <
int dimension>
166 auto const vertices = triangulation.finite_vertex_handles();
168 vertices.begin(), vertices.end(),
169 [](
auto const vertex) ->
auto const& { return vertex->point(); });
177 template <
int dimension>
182 using Kernel =
typename TriangulationTraits<dimension>::Kernel;
184#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
185 CDT_ENABLE_PARALLEL_TRIANGULATION
186 using Lock_data_structure =
typename Delaunay::Lock_data_structure;
187 using Lock_owner = std::unique_ptr<Lock_data_structure>;
194 noexcept(std::declval<Delaunay&>().swap(std::declval<Delaunay&>())),
195 "Delaunay_state swap requires CGAL's swap to be noexcept.");
196 static_assert(std::is_nothrow_swappable_v<Lock_owner>,
197 "Delaunay_state swap requires a non-throwing lock owner.");
198 static_assert(std::is_nothrow_move_constructible_v<Lock_owner> &&
199 std::is_nothrow_move_constructible_v<Delaunay>,
200 "Delaunay_state move construction requires non-throwing "
204 Lock_owner m_lock_data_structure;
205 Delaunay m_triangulation;
209 Lock_owner lock_data_structure;
210 Delaunay triangulation;
213 [[nodiscard]]
static auto make_empty_state() -> Pending_state
215#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
216 CDT_ENABLE_PARALLEL_TRIANGULATION
217 auto lock = std::make_unique<Lock_data_structure>(
218 locking_box<dimension>(Delaunay{}), LOCK_GRID_RESOLUTION);
219 Delaunay triangulation{Kernel{}, lock.get()};
220 return {std::move(lock), std::move(triangulation)};
222 return {Lock_owner{}, Delaunay{}};
226 [[nodiscard]]
static auto make_insertion_state(
229#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
230 CDT_ENABLE_PARALLEL_TRIANGULATION
231 auto lock = std::make_unique<Lock_data_structure>(
232 locking_box<dimension>(causal_vertices), LOCK_GRID_RESOLUTION);
233 Delaunay triangulation{Kernel{}, lock.get()};
234 return {std::move(lock), std::move(triangulation)};
236 static_cast<void>(causal_vertices);
237 return {Lock_owner{}, Delaunay{}};
241 [[nodiscard]]
static auto make_adopted_state(Delaunay source)
244#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
245 CDT_ENABLE_PARALLEL_TRIANGULATION
246 auto lock = std::make_unique<Lock_data_structure>(
247 locking_box<dimension>(source), LOCK_GRID_RESOLUTION);
248 source.set_lock_data_structure(lock.get());
249 return {std::move(lock), std::move(source)};
251 source.set_lock_data_structure(
nullptr);
252 return {Lock_owner{}, std::move(source)};
256 explicit Delaunay_state(Pending_state state) noexcept
257 : m_lock_data_structure{std::move(state.lock_data_structure)}
258 , m_triangulation{std::move(state.triangulation)}
259 { state.triangulation.set_lock_data_structure(
nullptr); }
262 Delaunay_state() : Delaunay_state{make_empty_state()} {}
264 explicit Delaunay_state(
266 : Delaunay_state{make_insertion_state(causal_vertices)}
268 auto const inserted = m_triangulation.insert(causal_vertices.begin(),
269 causal_vertices.end());
270 if (inserted != std::ssize(causal_vertices))
272 throw std::invalid_argument(
273 "Causal vertices must contain unique geometric points.");
277 explicit Delaunay_state(Delaunay source)
278 : Delaunay_state{make_adopted_state(std::move(source))}
281 Delaunay_state(Delaunay_state
const& other)
282 : Delaunay_state{Delaunay{other.m_triangulation}}
285 Delaunay_state(Delaunay_state&& other) noexcept
286 : m_lock_data_structure{std::move(other.m_lock_data_structure)}
287 , m_triangulation{std::move(other.m_triangulation)}
288 { other.m_triangulation.set_lock_data_structure(
nullptr); }
290 friend void swap(Delaunay_state& lhs, Delaunay_state& rhs)
noexcept
292 lhs.m_triangulation.swap(rhs.m_triangulation);
294 swap(lhs.m_lock_data_structure, rhs.m_lock_data_structure);
297 auto operator=(Delaunay_state
const& other) -> Delaunay_state&
301 Delaunay_state copy{other};
307 auto operator=(Delaunay_state&& other)
noexcept -> Delaunay_state&
309 if (
this != &other) { swap(*
this, other); }
313 ~Delaunay_state() =
default;
315 [[nodiscard]]
auto triangulation()
const noexcept -> Delaunay
const&
316 {
return m_triangulation; }
320 [[nodiscard]]
auto mutable_triangulation_unchecked()
noexcept -> Delaunay&
321 {
return m_triangulation; }
323 [[nodiscard]]
auto lock_data_structure()
const noexcept
325#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
326 CDT_ENABLE_PARALLEL_TRIANGULATION
327 return m_lock_data_structure.get();
333 [[nodiscard]]
auto has_consistent_lock_binding()
const noexcept ->
bool
335 return m_triangulation.get_lock_data_structure() ==
336 lock_data_structure();
339 [[nodiscard]]
auto into_detached_triangulation() && -> Delaunay
341 m_triangulation.set_lock_data_structure(
nullptr);
342#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
343 CDT_ENABLE_PARALLEL_TRIANGULATION
344 m_lock_data_structure.reset();
346 return std::move(m_triangulation);
391 template <
int dimension>
396 if (vertices.size() != timevalues.size())
398 throw std::length_error(
"Vertices and timevalues must be the same size.");
401 causal_vertices.reserve(vertices.size());
402 std::ranges::transform(
403 vertices, timevalues, std::back_inserter(causal_vertices),
405 if (!std::in_range<Int_precision>(time))
407 throw std::out_of_range(
"Timevalue does not fit Int_precision.");
411 return causal_vertices;
421 template <
int dimension>
424 assert(delaunay.is_valid());
425 std::vector<Edge_handle_t<dimension>> init_edges;
426 init_edges.reserve(delaunay.number_of_finite_edges());
427 for (
auto const& edge : delaunay.finite_edges())
429 assert(delaunay.tds().is_valid(edge.first, edge.second, edge.third));
430 init_edges.emplace_back(edge);
432 assert(init_edges.size() == delaunay.number_of_finite_edges());
445 template <
int dimension>
448 -> std::optional<Vertex_handle_t<dimension>>
451 delaunay.is_vertex(point, vertex))
471 template <
int dimension>
477 -> std::optional<Cell_handle_t<dimension>>
480 delaunay.is_cell(vh1, vh2, vh3, vh4, cell))
489 template <
int dimension>
492 return lhs->info() < rhs->info();
499 template <
int dimension, detail::ConstForwardRange Container>
503 if (std::ranges::empty(t_vertices))
505 throw std::invalid_argument(
"Cannot classify an empty triangulation.");
507 auto const max_element =
509 return (*max_element)->info();
516 template <
int dimension, detail::ConstForwardRange Container>
520 if (std::ranges::empty(t_vertices))
522 throw std::invalid_argument(
"Cannot classify an empty triangulation.");
524 auto const min_element =
526 return (*min_element)->info();
533 template <
int dimension>
540 auto const& cell = t_edge.first;
541 auto time1 = cell->vertex(t_edge.second)->info();
542 auto time2 = cell->vertex(t_edge.third)->info();
545 spdlog::trace(
"Edge: Vertex(1) timevalue: {} Vertex(2) timevalue: {}\n",
556 template <
int dimension>
559 EdgeType const edge_type) -> std::vector<Edge_handle_t<dimension>>
561 std::vector<Edge_handle_t<dimension>> filtered_edges;
562 filtered_edges.reserve(t_edges.size());
563 std::ranges::copy_if(t_edges, std::back_inserter(filtered_edges),
564 [&](
auto const& edge) {
567 return filtered_edges;
574 template <
int dimension>
577 CellType const& t_cell_type) -> std::vector<Cell_handle_t<dimension>>
579 std::vector<Cell_handle_t<dimension>> filtered_cells;
580 filtered_cells.reserve(t_cells.size());
581 std::ranges::copy_if(t_cells, std::back_inserter(filtered_cells),
582 [&t_cell_type](
auto const& cell) {
583 return cell->info() ==
static_cast<int>(t_cell_type);
585 return filtered_cells;
592 template <
int dimension>
596 typename detail::TriangulationTraits<dimension>::squared_distance
const r_2;
597 return r_2(t_vertex->point(),
598 detail::TriangulationTraits<dimension>::ORIGIN_POINT);
616 template <
int dimension>
623 std::lround((radius - t_initial_radius + t_foliation_spacing) /
624 t_foliation_spacing));
634 template <
int dimension>
637 double const t_foliation_spacing) ->
bool
640 t_vertex, t_initial_radius, t_foliation_spacing);
642 spdlog::trace(
"Vertex({}) timevalue {} has expected timevalue == {}\n",
646 return timevalue == t_vertex->info();
654 template <
int dimension>
658 std::vector<Vertex_handle_t<dimension>> vertices;
659 vertices.reserve(t_triangulation.number_of_vertices());
660 for (
auto const vertex : t_triangulation.finite_vertex_handles())
662 assert(t_triangulation.tds().is_vertex(vertex));
663 vertices.emplace_back(vertex);
675 template <
int dimension>
678 double t_foliation_spacing)
680 return std::ranges::all_of(
681 t_triangulation.finite_vertex_handles(), [&](
auto const vertex) {
682 return is_vertex_timevalue_correct<dimension>(
683 vertex, t_initial_radius, t_foliation_spacing);
692 template <
int dimension>
694 -> std::vector<Cell_handle_t<dimension>>
696 std::vector<Cell_handle_t<dimension>> cells;
697 cells.reserve(t_triangulation.number_of_finite_cells());
698 for (
auto const cell : t_triangulation.finite_cell_handles())
700 assert(t_triangulation.tds().is_cell(cell));
701 cells.emplace_back(cell);
710 template <
int dimension>
714 std::unordered_set<Vertex_handle_t<dimension>> cell_vertices;
715 auto get_vertices = [&cell_vertices](
auto const& t_cell) {
716 for (
int i = 0; i < dimension + 1; ++i)
718 cell_vertices.emplace(t_cell->vertex(i));
721 std::for_each(t_cells.begin(), t_cells.end(), get_vertices);
722 std::vector<Vertex_handle_t<dimension>> result(cell_vertices.begin(),
723 cell_vertices.end());
734 template <
int dimension>
737 double t_initial_radius,
double t_foliation_spacing)
740 std::vector<Vertex_handle_t<dimension>> incorrect_vertices;
742 std::copy_if(checked_vertices.begin(), checked_vertices.end(),
743 std::back_inserter(incorrect_vertices),
744 [&](
auto const& vertex) {
745 return !is_vertex_timevalue_correct<dimension>(
746 vertex, t_initial_radius, t_foliation_spacing);
748 return incorrect_vertices;
759 template <
int dimension>
762 double t_foliation_spacing)
766 t_foliation_spacing);
778 template <
int dimension>
781 double t_initial_radius,
double t_foliation_spacing)
784 t_cells, t_initial_radius, t_foliation_spacing);
785 std::for_each(incorrect_vertices.begin(), incorrect_vertices.end(),
786 [&](
auto const& vertex) {
787 vertex->info() = expected_timevalue<dimension>(
788 vertex, t_initial_radius, t_foliation_spacing);
790 return !incorrect_vertices.empty();
801 template <
int dimension>
803 double const t_initial_radius,
804 double const t_foliation_spacing) ->
bool
807 t_initial_radius, t_foliation_spacing);
814 template <
int dimension>
820 std::array<int, static_cast<std::size_t>(dimension) + 1>
823 for (
auto i = 0; i < dimension + 1; ++i)
826 vertex_timevalues.at(
static_cast<std::size_t
>(i)) =
827 t_cell->vertex(i)->info();
829 auto const maxtime_ref =
830 std::max_element(vertex_timevalues.begin(), vertex_timevalues.end());
831 auto const mintime_ref =
832 std::min_element(vertex_timevalues.begin(), vertex_timevalues.end());
833 auto maxtime = *maxtime_ref;
834 auto mintime = *mintime_ref;
836 if (maxtime - mintime != 1 || maxtime == mintime)
839 spdlog::trace(
"This simplex is acausal:\n");
840 spdlog::trace(
"Max timevalue is {} and min timevalue is {}.\n", maxtime,
842 spdlog::trace(
"--\n");
846 std::multiset<int>
const timevalues{vertex_timevalues.begin(),
847 vertex_timevalues.end()};
848 auto max_vertices = timevalues.count(maxtime);
849 auto min_vertices = timevalues.count(mintime);
858 spdlog::trace(
"This simplex has an error:\n");
859 spdlog::trace(
"Max timevalue is {} and min timevalue is {}.\n", maxtime,
862 "There are {} vertices with the max timevalue and {} vertices with "
863 "the min timevalue.\n",
864 max_vertices, min_vertices);
865 spdlog::trace(
"--\n");
874 template <
int dimension>
881 cell_type ==
static_cast<CellType>(t_cell->info());
889 template <
int dimension>
893 return std::ranges::all_of(
894 t_triangulation.finite_cell_handles(),
895 [](
auto const cell) { return is_cell_type_correct<dimension>(cell); });
903 template <
int dimension>
909 std::vector<Cell_handle_t<dimension>> incorrect_cells;
910 std::copy_if(checked_cells.begin(), checked_cells.end(),
911 std::back_inserter(incorrect_cells), [&](
auto const& cell) {
912 return !is_cell_type_correct<dimension>(cell);
914 return incorrect_cells;
923 template <
int dimension>
928 incorrect_cells.begin(), incorrect_cells.end(), [&](
auto const& cell) {
930 static_cast<Int_precision>(expected_cell_type<dimension>(cell));
932 return !incorrect_cells.empty();
938 template <
int dimension>
941 fmt::print(
"Cell info => {}\n", cell->info());
943 for (
int j = 0; j < dimension + 1; ++j)
945 fmt::print(
"Vertex({}) Point: ({}) Timevalue: {}\n", j,
947 cell->vertex(j)->info());
956 template <
int dimension, detail::ConstForwardRange Container>
967 template <
int dimension, detail::ConstForwardRange Container>
970 for (
auto const& cell : t_cells)
972 spdlog::debug(
"Cell info => {}\n", cell->info());
973 for (
int j = 0; j < dimension + 1; ++j)
975 spdlog::debug(
"Vertex({}) Point: ({}) Timevalue: {}\n", j,
977 cell->vertex(j)->info());
979 spdlog::debug(
"---\n");
986 template <
int dimension>
989 for (
int j = 0; j < dimension + 1; ++j)
991 fmt::print(
"Neighboring cell {}:", j);
1003 template <
int dimension>
1007 "Edge: Vertex({}) Point({}) Timevalue: {} -> Vertex({}) Point({}) "
1011 t_edge.first->vertex(t_edge.second)->info(), t_edge.third,
1013 t_edge.first->vertex(t_edge.third)->info());
1026 template <
int dimension, detail::ConstForwardRange Container>
1028 -> std::vector<std::pair<Int_precision, Facet_t<dimension>>>
1033 using Volume_entry = std::pair<Int_precision, Facet_t<dimension>>;
1034 std::vector<Volume_entry> space_faces;
1035 if constexpr (std::ranges::sized_range<Container>)
1037 space_faces.reserve(std::ranges::size(t_facets));
1039 for (
auto const& face : t_facets)
1042 auto index_of_facet = face.second;
1044 spdlog::trace(
"Facet index is {}\n", index_of_facet);
1046 std::set<Int_precision> facet_timevalues;
1048 for (
int i = 0; i < dimension + 1; ++i)
1050 if (i != index_of_facet)
1053 spdlog::trace(
"Vertex[{}] has timevalue {}\n", i,
1054 cell->vertex(i)->info());
1056 facet_timevalues.insert(cell->vertex(i)->info());
1061 if (facet_timevalues.size() == 1)
1064 spdlog::trace(
"Facet is spacelike on timevalue {}.\n",
1065 *facet_timevalues.begin());
1067 space_faces.emplace_back(*facet_timevalues.begin(), face);
1072 spdlog::trace(
"Facet is timelike.\n");
1076 std::ranges::stable_sort(
1077 space_faces, std::ranges::less{},
1078 [](Volume_entry
const& entry)
noexcept {
return entry.first; });
1089 template <
int dimension, detail::ConstForwardRange Container>
1091 -> std::multimap<Int_precision, Facet_t<dimension>>
1094 return {std::make_move_iterator(space_faces.begin()),
1095 std::make_move_iterator(space_faces.end())};
1114 template <
int dimension>
1117 -> std::vector<Cell_handle_t<dimension>>
1120 std::vector<Cell_handle_t<dimension>> invalid_cells;
1121 std::copy_if(cells.begin(), cells.end(), std::back_inserter(invalid_cells),
1122 [](
auto const& cell) {
1123 auto const classification =
1124 expected_cell_type<dimension>(cell);
1125 return classification == CellType::ACAUSAL ||
1126 classification == CellType::UNCLASSIFIED;
1128 return invalid_cells;
1135 template <
int dimension>
1145 template <
int dimension>
1151 spdlog::debug(
"===Invalid Cell===\n");
1152 std::vector<Cell_handle_t<dimension>> bad_cell{cell};
1155 std::multimap<Int_precision, Vertex_handle_t<dimension>> vertices;
1156 for (
int i = 0; i < dimension + 1; ++i)
1159 std::make_pair(cell->vertex(i)->info(), cell->vertex(i)));
1162 auto const minvalue = vertices.cbegin()->first;
1163 auto const maxvalue = vertices.crbegin()->first;
1164 auto const minvalue_count = vertices.count(minvalue);
1165 auto const maxvalue_count = vertices.count(maxvalue);
1170 return minvalue_count >= maxvalue_count ? vertices.rbegin()->second
1171 : vertices.begin()->second;
1181 template <
int dimension>
1186 auto invalid_cells =
1188 if (!invalid_cells.empty())
1190 std::set<Vertex_handle_t<dimension>> vertices_to_remove;
1194 invalid_cells.begin(), invalid_cells.end(),
1195 std::inserter(vertices_to_remove, vertices_to_remove.begin()),
1199 spdlog::warn(
"There are {} invalid vertices.\n",
1200 vertices_to_remove.size());
1202 t_triangulation.remove(vertices_to_remove.begin(),
1203 vertices_to_remove.end());
1204 assert(t_triangulation.tds().is_valid());
1205 assert(t_triangulation.is_valid());
1230 template <
int dimension, std::uniform_random_bit_generator Generator>
1233 double const initial_radius,
1234 double const foliation_spacing,
1235 Generator& generator)
1237 if (t_simplices < 2 || t_timeslices < 2)
1239 throw std::invalid_argument(
1240 "Simplices and timeslices must each be at least 2.");
1242 if (!std::isfinite(initial_radius) || initial_radius <= 0.0)
1244 throw std::invalid_argument(
1245 "Initial radius must be finite and positive.");
1247 if (!std::isfinite(foliation_spacing) || foliation_spacing <= 0.0)
1249 throw std::invalid_argument(
1250 "Foliation spacing must be finite and positive.");
1254 dimension, t_simplices, t_timeslices, initial_radius,
1256 if (population.points_per_timeslice < 2)
1258 throw std::invalid_argument(
1259 "Simplices and timeslices would create an empty triangulation.");
1261 if (!std::isfinite(population.last_layer_points) ||
1262 population.last_layer_points >
1263 static_cast<long double>(std::numeric_limits<Int_precision>::max()))
1265 throw std::out_of_range(
1266 "Foliation parameters generate too many points per timeslice.");
1270 causal_vertices.reserve(
static_cast<std::size_t
>(t_simplices));
1271 std::uniform_int_distribution<unsigned int> seed_distribution;
1272 CGAL::Random cgal_random{seed_distribution(generator)};
1274 for (gsl::index i = 0; i < t_timeslices; ++i)
1277 initial_radius +
static_cast<double>(i) * foliation_spacing;
1278 auto const generated_points =
1279 static_cast<long double>(population.points_per_timeslice) * radius;
1280 if (!std::isfinite(radius) || generated_points < 2.0L)
1282 throw std::invalid_argument(
1283 "Foliation parameters do not populate every timeslice.");
1285 if (generated_points >
1286 static_cast<long double>(std::numeric_limits<Int_precision>::max()))
1288 throw std::out_of_range(
1289 "Foliation parameters generate too many points per timeslice.");
1293 for (gsl::index j = 0; j < static_cast<Int_precision>(generated_points);
1296 causal_vertices.emplace_back(*gen++, i + 1);
1299 if (causal_vertices.size() <
static_cast<std::size_t
>(dimension + 1))
1301 throw std::invalid_argument(
"Parameters create an empty triangulation.");
1303 return causal_vertices;
1328 template <
int dimension, std::uniform_random_bit_generator Generator>
1331 double const initial_radius,
1332 double const foliation_spacing,
1333 Generator& generator)
1339 fmt::print(
"\nGenerating universe ...\n");
1341 auto causal_vertices =
1343 foliation_spacing, generator);
1344 detail::Delaunay_state<dimension> state{causal_vertices};
1345 auto& triangulation = state.mutable_triangulation_unchecked();
1348 for (
auto passes = 1; passes < detail::MAX_FIX_PASSES + 1; ++passes)
1356 spdlog::warn(
"Deleting incorrect vertices pass #{}\n", passes);
1361 for (
auto passes = 1; passes < detail::MAX_FIX_PASSES + 1; ++passes)
1365 spdlog::warn(
"Fixing timeslices pass #{}\n", passes);
1370 for (
auto i = 1; i < detail::MAX_FIX_PASSES + 1; ++i)
1374 spdlog::warn(
"Fixing incorrect cells pass #{}\n", i);
1380 return std::move(state).into_detached_triangulation();
1385 template <
int dimension>
1411 using Cell_container = std::vector<Cell_handle>;
1412 using Face_container = std::vector<Facet_t<3>>;
1413 using Edge_container = std::vector<Edge_handle_t<3>>;
1415 using Vertex_container = std::vector<Vertex_handle>;
1416 using Volume_entry = std::pair<Int_precision, Facet_t<3>>;
1417 using Volume_by_timeslice = std::vector<Volume_entry>;
1418 using Delaunay_state = detail::Delaunay_state<3>;
1420 static_assert(std::is_nothrow_swappable_v<double>,
1421 "FoliatedTriangulation swap requires non-throwing scalars.");
1423 std::is_nothrow_swappable_v<Delaunay_state> &&
1424 std::is_nothrow_swappable_v<Vertex_container> &&
1425 std::is_nothrow_swappable_v<Cell_container> &&
1426 std::is_nothrow_swappable_v<Face_container> &&
1427 std::is_nothrow_swappable_v<Edge_container> &&
1428 std::is_nothrow_swappable_v<Volume_by_timeslice>,
1429 "FoliatedTriangulation swap requires non-throwing container swaps.");
1430 static_assert(std::is_nothrow_swappable_v<Int_precision>,
1431 "FoliatedTriangulation swap requires non-throwing bounds.");
1433 std::is_nothrow_move_constructible_v<Delaunay_state> &&
1434 std::is_nothrow_move_constructible_v<Vertex_container> &&
1435 std::is_nothrow_move_constructible_v<Cell_container> &&
1436 std::is_nothrow_move_constructible_v<Face_container> &&
1437 std::is_nothrow_move_constructible_v<Edge_container> &&
1438 std::is_nothrow_move_constructible_v<Volume_by_timeslice>,
1439 "FoliatedTriangulation move construction requires non-throwing member "
1442 [[nodiscard]]
static auto cache_spacelike_facets(
1443 Face_container
const& faces) -> Volume_by_timeslice
1446 [[nodiscard]]
static auto require_nonempty(Delaunay_state state)
1449 if (state.triangulation().number_of_vertices() == 0)
1451 throw std::invalid_argument(
1452 "A foliated triangulation must contain at least one vertex.");
1457 [[nodiscard]]
auto triangulation()
noexcept -> Delaunay&
1458 {
return m_delaunay_state.mutable_triangulation_unchecked(); }
1460 [[nodiscard]]
auto triangulation()
const noexcept -> Delaunay
const&
1461 {
return m_delaunay_state.triangulation(); }
1465 Delaunay_state m_delaunay_state;
1468 Vertex_container m_vertices;
1469 Cell_container m_cells;
1470 Cell_container m_three_one;
1471 Cell_container m_two_two;
1472 Cell_container m_one_three;
1473 Face_container m_faces;
1474 Volume_by_timeslice m_spacelike_facets;
1475 Edge_container m_edges;
1476 Edge_container m_timelike_edges;
1477 Edge_container m_spacelike_edges;
1481 [[nodiscard]]
auto has_consistent_structure()
const ->
bool
1483 auto const& delaunay = triangulation();
1484 auto const cells_are_partitioned =
1485 m_three_one.size() + m_two_two.size() + m_one_three.size() ==
1487 auto const edges_are_partitioned =
1488 m_timelike_edges.size() + m_spacelike_edges.size() == m_edges.size();
1489 auto const time_bounds_are_valid =
1490 m_vertices.empty() ? m_max_timevalue == 0 && m_min_timevalue == 0
1491 : m_min_timevalue <= m_max_timevalue;
1492 return m_delaunay_state.has_consistent_lock_binding() &&
1493 m_vertices.size() == delaunay.number_of_vertices() &&
1494 m_spacelike_facets.size() <= m_faces.size() &&
1495 cells_are_partitioned && edges_are_partitioned &&
1496 time_bounds_are_valid;
1499 [[nodiscard]]
auto has_consistent_derived_state()
const ->
bool
1501 if (!has_consistent_structure()) {
return false; }
1503 auto const& delaunay = triangulation();
1504 if (m_cells.size() != delaunay.number_of_finite_cells() ||
1505 m_faces.size() != delaunay.number_of_finite_facets() ||
1506 m_edges.size() != delaunay.number_of_finite_edges())
1511 auto const& tds = delaunay.tds();
1512 auto const valid_vertices = std::ranges::all_of(
1514 [&tds](Vertex_handle
const vertex) {
return tds.is_vertex(vertex); });
1515 auto const valid_cells = std::ranges::all_of(
1517 [&tds](Cell_handle
const cell) {
return tds.is_cell(cell); });
1518 auto const valid_faces =
1519 std::ranges::all_of(m_faces, [&tds](
auto const& face) {
1520 return tds.is_facet(face.first, face.second);
1522 auto const valid_edges =
1523 std::ranges::all_of(m_edges, [&tds](
auto const& edge) {
1524 return tds.is_valid(edge.first, edge.second, edge.third);
1526 if (!valid_vertices || !valid_cells || !valid_faces || !valid_edges)
1534 m_spacelike_facets != cache_spacelike_facets(m_faces) ||
1541 if (m_vertices.empty()) {
return true; }
1558 if (other.triangulation().number_of_vertices() == 0)
1560 m_initial_radius = other.m_initial_radius;
1561 m_foliation_spacing = other.m_foliation_spacing;
1565 other.m_initial_radius,
1566 other.m_foliation_spacing};
1576 if (
this == &other) {
return *
this; }
1594 if (
this != &other) {
swap(other, *
this); }
1613 swap(swap_from.m_delaunay_state, swap_into.m_delaunay_state);
1614 swap(swap_from.m_initial_radius, swap_into.m_initial_radius);
1615 swap(swap_from.m_foliation_spacing, swap_into.m_foliation_spacing);
1616 swap(swap_from.m_vertices, swap_into.m_vertices);
1617 swap(swap_from.m_cells, swap_into.m_cells);
1618 swap(swap_from.m_three_one, swap_into.m_three_one);
1619 swap(swap_from.m_two_two, swap_into.m_two_two);
1620 swap(swap_from.m_one_three, swap_into.m_one_three);
1621 swap(swap_from.m_faces, swap_into.m_faces);
1622 swap(swap_from.m_spacelike_facets, swap_into.m_spacelike_facets);
1623 swap(swap_from.m_edges, swap_into.m_edges);
1624 swap(swap_from.m_timelike_edges, swap_into.m_timelike_edges);
1625 swap(swap_from.m_spacelike_edges, swap_into.m_spacelike_edges);
1626 swap(swap_from.m_max_timevalue, swap_into.m_max_timevalue);
1627 swap(swap_from.m_min_timevalue, swap_into.m_min_timevalue);
1649 double const initial_radius,
1650 double const foliation_spacing)
1651 : m_delaunay_state{require_nonempty(std::move(state))}
1652 , m_initial_radius{initial_radius}
1653 , m_foliation_spacing{foliation_spacing}
1655 , m_cells{classify_cells(
collect_cells<3>(triangulation()))}
1659 , m_faces{collect_faces()}
1660 , m_spacelike_facets{cache_spacelike_facets(m_faces)}
1688 t_foliation_spacing, generator),
1689 t_initial_radius, t_foliation_spacing}
1708 t_initial_radius, t_foliation_spacing}
1725 t_initial_radius, t_foliation_spacing}
1741 {
return triangulation().is_valid(); }
1745 {
return triangulation().tds().is_valid(); }
1776 Delaunay updated{triangulation()};
1778 updated, m_initial_radius, m_foliation_spacing);
1780 auto const fixed_timeslices =
1782 auto const changed = fixed_vertices || fixed_cells || fixed_timeslices;
1786 m_foliation_spacing};
1787 swap(replacement, *
this);
1797 Delaunay snapshot{triangulation()};
1799 snapshot.set_lock_data_structure(
nullptr);
1806 return triangulation().number_of_finite_cells();
1812 return triangulation().number_of_finite_facets();
1818 return triangulation().number_of_finite_edges();
1823 {
return triangulation().number_of_vertices(); }
1826 [[nodiscard]]
auto dimension()
const {
return triangulation().dimension(); }
1831 Int_precision const timevalue)
const noexcept -> std::size_t
1833 auto const matching_facets = std::ranges::equal_range(
1834 m_spacelike_facets, timevalue, std::ranges::less{},
1835 [](Volume_entry
const& entry)
noexcept {
return entry.first; });
1836 return static_cast<std::size_t
>(matching_facets.size());
1841 {
return m_spacelike_facets.size(); }
1845 {
return static_cast<Int_precision>(m_timelike_edges.size()); }
1849 {
return static_cast<Int_precision>(m_spacelike_edges.size()); }
1852 [[nodiscard]]
auto max_time()
const {
return m_max_timevalue; }
1855 [[nodiscard]]
auto min_time()
const {
return m_min_timevalue; }
1872 auto const expected_radius_squared = std::pow(radius, 2);
1873 if (expected_radius_squared == 0.0)
1875 return std::abs(actual_radius_squared) <=
TOLERANCE;
1877 return actual_radius_squared >
1878 expected_radius_squared * (1 -
TOLERANCE) &&
1879 actual_radius_squared < expected_radius_squared * (1 +
TOLERANCE);
1895 auto const timevalue = t_vertex->info();
1896 return m_initial_radius + m_foliation_spacing * (timevalue - 1);
1906 t_vertex, m_initial_radius, m_foliation_spacing);
1913 triangulation(), m_initial_radius, m_foliation_spacing);
1923 Delaunay updated{triangulation()};
1925 updated, m_initial_radius, m_foliation_spacing);
1929 m_foliation_spacing};
1930 swap(replacement, *
this);
1938 for (
auto const& vertex : m_vertices)
1940 fmt::print(
"Vertex Point: ({}) Timevalue: {} Expected Timevalue: {}\n",
1950 for (
auto const& edge : m_edges)
1954 fmt::print(
"==> timelike\n");
1958 fmt::print(
"==> spacelike\n");
1968 fmt::print(
"Timeslice {} has {} spacelike faces.\n", j,
1975 {
return m_three_one.size(); }
1979 {
return m_two_two.size(); }
1983 {
return m_one_three.size(); }
2002 Delaunay updated{triangulation()};
2007 m_foliation_spacing};
2008 swap(replacement, *
this);
2022 "Triangulation has {} vertices and {} edges and {} faces and {} "
2029 [[nodiscard]]
auto classify_vertices(Vertex_container
const& vertices)
const
2032 assert(vertices.size() == number_of_vertices());
2033 for (
auto const& vertex : vertices)
2044 [[nodiscard]]
auto classify_cells(Cell_container
const& cells)
const
2047 assert(cells.size() == number_of_finite_cells());
2048 for (
auto const& cell : cells)
2050 cell->info() =
static_cast<int>(expected_cell_type<3>(cell));
2056 [[nodiscard]]
auto collect_faces() const -> Face_container
2059 assert(is_tds_valid());
2060 Face_container init_faces;
2061 init_faces.reserve(triangulation().number_of_finite_facets());
2062 for (
auto const& facet : triangulation().finite_facets())
2064 assert(triangulation().tds().is_facet(facet.first, facet.second));
2065 init_faces.emplace_back(facet);
2067 assert(init_faces.size() == triangulation().number_of_finite_facets());
Run-owned random-number generation and reproducible stream splitting.
#define CDT_PRETTY_FUNCTION
Cross-platform spelling of the current function signature for diagnostics.
Traits class for particular uses of CGAL.
void print_delaunay(TriangulationType const &t_triangulation)
Print triangulation statistics.
auto point_to_str(Point const &t_point) -> std::string
Covert a CGAL point to a string.
auto generated_population_bounds(Int_precision const dimension, Int_precision const simplices, Int_precision const timeslices, double const initial_radius, double const foliation_spacing) -> Generated_population_bounds
Calculate the generated point count and its upper bound.
A run-owned PCG engine with a recorded seed and stream identifier.
auto delaunay_snapshot() const -> Delaunay
FoliatedTriangulation(Int_precision const t_simplices, Int_precision const t_timeslices, cdt::Random &&generator, double const t_initial_radius=INITIAL_RADIUS, double const t_foliation_spacing=FOLIATION_SPACING)
Construct from an explicit temporary initialization stream.
auto initial_radius() const
~FoliatedTriangulation()=default
Default dtor.
auto number_of_finite_cells() const
auto fix_cells() -> bool
Fix all cells in the triangulation.
auto expected_radius(Vertex_handle_t< 3 > const &t_vertex) const -> double
Calculates the expected radial distance of a vertex.
auto does_vertex_radius_match_timevalue(Vertex_handle_t< 3 > const t_vertex) const -> bool
Check the radius of a vertex from the origin with its timevalue.
auto number_of_vertices() const
auto is_tds_valid() const -> bool
auto spacelike_face_count(Int_precision const timevalue) const noexcept -> std::size_t
auto foliation_spacing() const
void print_volume_per_timeslice() const
Print the number of spacelike faces per timeslice.
auto is_correct() const -> bool
auto check_all_cells() const -> bool
Check that all cells are correctly classified.
auto operator=(FoliatedTriangulation &&other) noexcept -> FoliatedTriangulation &
Move assignment operator.
auto is_delaunay() const -> bool
FoliatedTriangulation(Causal_vertices_t< 3 > const &causal_vertices, double const t_initial_radius=INITIAL_RADIUS, double const t_foliation_spacing=FOLIATION_SPACING)
Constructor from Causal_vertices.
auto is_initialized() const -> bool
auto fix_vertices() -> bool
Fix vertices with wrong timevalues after foliation.
auto number_of_spacelike_faces() const noexcept -> std::size_t
FoliatedTriangulation(Delaunay triangulation, double const initial_radius=INITIAL_RADIUS, double const foliation_spacing=FOLIATION_SPACING)
Constructor using delaunay triangulation Pass-by-value-then-move. Delaunay is the ctor for the Delaun...
auto number_of_finite_edges() const
auto is_structurally_correct() const -> bool
auto number_of_two_two_cells() const noexcept -> std::size_t
auto is_correct_with_diagnostics() const -> bool
auto is_foliated() const -> bool
Verifies the triangulation is properly foliated.
FoliatedTriangulation(Int_precision const t_simplices, Int_precision const t_timeslices, cdt::Random &generator, double const t_initial_radius=INITIAL_RADIUS, double const t_foliation_spacing=FOLIATION_SPACING)
Constructor with a caller-owned initialization stream.
auto expected_timevalue(Vertex_handle_t< 3 > const &t_vertex) const -> int
Calculate the expected timevalue for a vertex.
void print() const
Print triangulation statistics.
void print_edges() const
Print timevalues of each vertex in the edge and classify as timelike or spacelike.
FoliatedTriangulation()=default
Default ctor.
friend void swap(FoliatedTriangulation &swap_from, FoliatedTriangulation &swap_into) noexcept
Non-member swap function for Foliated Triangulations.
auto operator=(FoliatedTriangulation const &other) -> FoliatedTriangulation &
Copy assignment operator.
auto number_of_three_one_cells() const noexcept -> std::size_t
auto check_all_vertices() const -> bool
void print_vertices() const
Print values of a vertex.
auto number_of_finite_facets() const
auto number_of_one_three_cells() const noexcept -> std::size_t
void print_cells() const
Print timevalues of each vertex in the cell and the resulting cell->info().
FoliatedTriangulation(FoliatedTriangulation &&other) noexcept=default
Move constructor.
FoliatedTriangulation(FoliatedTriangulation const &other)
Copy Constructor.
A multi-pass range whose const-qualified value can be traversed by classification and materialization...
Supported construction, inspection, classification, and repair operations for foliated Delaunay trian...
auto collect_spacelike_facets(Container const &t_facets) -> std::vector< std::pair< Int_precision, Facet_t< dimension > > >
Collect spacelike facets into a contiguous container ordered by time value.
auto fix_timevalues(Delaunay_t< dimension > &t_triangulation) -> bool
Fix the vertices of a cell to be consistent with the foliation.
auto find_cell(Delaunay_t< dimension > const &delaunay, Vertex_handle_t< dimension > const &vh1, Vertex_handle_t< dimension > const &vh2, Vertex_handle_t< dimension > const &vh3, Vertex_handle_t< dimension > const &vh4) -> std::optional< Cell_handle_t< dimension > >
Returns the cell containing the vertices.
auto collect_cells(Delaunay_t< dimension > const &t_triangulation) -> std::vector< Cell_handle_t< dimension > >
Obtain all finite cells in the Delaunay triangulation.
auto get_vertices_from_cells(std::vector< Cell_handle_t< dimension > > const &t_cells)
Extracts vertices from cells.
auto find_incorrect_vertices(std::vector< Cell_handle_t< dimension > > const &t_cells, double t_initial_radius, double t_foliation_spacing)
Obtain vertices with incorrect timevalues.
auto check_vertices(Delaunay_t< dimension > const &t_triangulation, double t_initial_radius, double t_foliation_spacing)
Check if vertices have the correct timevalues.
auto find_min_timevalue(Container const &t_vertices) -> Int_precision
void debug_print_cells(Container const &t_cells)
Write to debug log timevalues of each vertex in the cell and the resulting cell->info.
auto expected_timevalue(Vertex_handle_t< dimension > const &t_vertex, double t_initial_radius, double t_foliation_spacing) -> Int_precision
Find the expected timevalue for a vertex.
auto collect_edges(Delaunay_t< dimension > const &delaunay)
Returns a container of all the finite edges in the triangulation.
auto find_bad_vertex(Cell_handle_t< dimension > const &cell) -> Vertex_handle_t< dimension >
Find the vertex that is causing a cell's foliation to be invalid.
auto filter_edges(std::vector< Edge_handle_t< dimension > > const &t_edges, EdgeType const edge_type) -> std::vector< Edge_handle_t< dimension > >
auto has_valid_timevalues(Delaunay_t< dimension > const &triangulation) -> bool
Check whether all cell timevalues form a valid foliation.
auto classify_edge(Edge_handle_t< dimension > const &t_edge) -> EdgeType
Predicate to classify edge as timelike or spacelike.
auto make_causal_vertices(std::span< Point_t< dimension > const > vertices, std::span< size_t const > timevalues) -> Causal_vertices_t< dimension >
Create causal vertices from vertices and timevalues.
auto squared_radius(Vertex_handle_t< dimension > const &t_vertex) -> double
Calculate the squared radius from the origin.
FoliatedTriangulation< 3 > FoliatedTriangulation_3
Three-dimensional foliated Delaunay triangulation.
auto fix_vertices(std::vector< Cell_handle_t< dimension > > const &t_cells, double t_initial_radius, double t_foliation_spacing)
Fix vertices with incorrect timevalues.
void print_neighboring_cells(Cell_handle_t< dimension > cell)
Print neighboring cells.
auto find_invalid_timevalue_cells(Delaunay_t< dimension > const &t_triangulation) -> std::vector< Cell_handle_t< dimension > >
Find cells whose vertex timevalues violate foliation.
void print_cell(Cell_handle_t< dimension > cell)
Print a cell in the triangulation.
auto find_incorrect_cells(Delaunay_t< dimension > const &t_triangulation)
Check all finite cells in the Delaunay triangulation.
auto find_max_timevalue(Container const &t_vertices) -> Int_precision
constexpr auto compare_v_info
auto fix_cells(Delaunay_t< dimension > &t_triangulation) -> bool
Fix simplices with the wrong type.
void print_cells(Container const &t_cells)
Print timevalues of each vertex in the cell and the resulting cell->info().
auto make_foliated_ball(Int_precision const t_simplices, Int_precision const t_timeslices, double const initial_radius, double const foliation_spacing, Generator &generator)
Make foliated ball.
auto is_vertex_timevalue_correct(Vertex_handle_t< dimension > const &t_vertex, double const t_initial_radius, double const t_foliation_spacing) -> bool
Checks if vertex timevalue is correct.
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.
void print_edge(Edge_handle_t< dimension > const &t_edge)
Print edge.
auto check_cells(Delaunay_t< dimension > const &t_triangulation) -> bool
Check all finite cells in the Delaunay triangulation.
auto volume_per_timeslice(Container const &t_facets) -> std::multimap< Int_precision, Facet_t< dimension > >
Collect spacelike facets into a container indexed by time value.
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 make_triangulation(Int_precision const t_simplices, Int_precision t_timeslices, double const initial_radius, double const foliation_spacing, Generator &generator) -> Delaunay_t< dimension >
Make a Delaunay triangulation.
auto collect_vertices(Delaunay_t< dimension > const &t_triangulation)
Obtain all finite vertices in the Delaunay triangulation.
clang-15 does not support std::format
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 >::Facet Facet_t
CGAL facet descriptor for a triangulation dimension.
constexpr double INITIAL_RADIUS
Default initial radius for generated foliated triangulations.
constexpr double FOLIATION_SPACING
Default distance between successive foliated timeslices.
typename detail::TriangulationTraits< dimension >::Delaunay Delaunay_t
Delaunay triangulation type for dimension spatial dimensions.
std::vector< std::pair< Point_t< dimension >, Int_precision > > Causal_vertices_t
Point/time-label pairs used to build a causal triangulation.
std::int32_t Int_precision
typename detail::TriangulationTraits< dimension >::Vertex_handle Vertex_handle_t
Mutable CGAL vertex handle for a triangulation dimension.
constexpr Int_precision GV_BOUNDING_BOX_SIZE
Depends on INITIAL_RADIUS and RADIAL_FACTOR.
typename detail::TriangulationTraits< dimension >::Spherical_points_generator Spherical_points_generator_t
CGAL random point generator on a sphere of matching 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.
EdgeType
Causal classification of an edge by its endpoint timeslices.
@ TIMELIKE
Endpoints lie on adjacent timeslices.
@ SPACELIKE
Both endpoints lie on the same timeslice.
constexpr double TOLERANCE
Sets epsilon values for floating point comparisons.