CDT++ 1.0.0
Causal Dynamical Triangulations in C++
Loading...
Searching...
No Matches
Foliated_triangulation.hpp
Go to the documentation of this file.
1/*******************************************************************************
2 Causal Dynamical Triangulations in C++ using CGAL
3
4 Copyright © 2018 Adam Getchell
5 ******************************************************************************/
6
20
21#ifndef CDT_PLUSPLUS_FOLIATEDTRIANGULATION_HPP
22#define CDT_PLUSPLUS_FOLIATEDTRIANGULATION_HPP
23
24#include <CGAL/Bbox_3.h>
25#include <CGAL/Random.h>
26
27#include <algorithm>
28#include <array>
29#include <cassert>
30#include <cmath>
31#include <concepts>
32#include <functional>
33#include <iterator>
34#include <limits>
35#include <map>
36#include <memory>
37#include <numeric>
38#include <optional>
39#include <ranges>
40#include <set>
41#include <span>
42#include <stdexcept>
43#include <type_traits>
44#include <unordered_set>
45#include <utility>
46#include <vector>
47
48#include "Random.hpp"
50#include "Utilities.hpp"
51
52namespace cdt
53{
56 template <int dimension>
57 using Delaunay_t = typename detail::TriangulationTraits<dimension>::Delaunay;
58
61 template <int dimension>
62 using Point_t = typename detail::TriangulationTraits<dimension>::Point;
63
66 template <int dimension>
68 std::vector<std::pair<Point_t<dimension>, Int_precision>>;
69
74 template <int dimension>
76 typename detail::TriangulationTraits<dimension>::Cell_handle;
77
82 template <int dimension>
83 using Facet_t = typename detail::TriangulationTraits<dimension>::Facet;
84
89 template <int dimension>
91 typename detail::TriangulationTraits<dimension>::Edge_handle;
92
97 template <int dimension>
99 typename detail::TriangulationTraits<dimension>::Vertex_handle;
100
103 template <int dimension>
104 using Spherical_points_generator_t = typename detail::TriangulationTraits<
105 dimension>::Spherical_points_generator;
106
110 namespace detail
111 {
112 template <typename C>
113 concept ConstForwardRange = std::ranges::forward_range<
114 std::add_const_t<std::remove_reference_t<C>>>;
115
116 inline constexpr int MAX_FIX_PASSES = 50;
117
118#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
119 CDT_ENABLE_PARALLEL_TRIANGULATION
120 inline constexpr int LOCK_GRID_RESOLUTION = 50;
121
122 [[nodiscard]] inline auto pad_locking_box(CGAL::Bbox_3 const& box)
123 -> CGAL::Bbox_3
124 {
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};
130 }
131
132 [[nodiscard]] inline auto default_locking_box() -> CGAL::Bbox_3
133 {
134 auto const extent = static_cast<double>(GV_BOUNDING_BOX_SIZE);
135 return {-extent, -extent, -extent, extent, extent, extent};
136 }
137
138 template <typename Iterator, typename Point_projection>
139 [[nodiscard]] auto locking_box(Iterator first, Iterator last,
140 Point_projection point_for) -> CGAL::Bbox_3
141 {
142 if (first == last) { return default_locking_box(); }
143
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();
148 });
149 return pad_locking_box(box);
150 }
151
152 template <int dimension>
153 [[nodiscard]] auto locking_box(
154 Causal_vertices_t<dimension> const& causal_vertices) -> CGAL::Bbox_3
155 {
156 return locking_box(causal_vertices.begin(), causal_vertices.end(),
157 [](auto const& causal_vertex) -> auto const& {
158 return causal_vertex.first;
159 });
160 }
161
162 template <int dimension>
163 [[nodiscard]] auto locking_box(Delaunay_t<dimension> const& triangulation)
164 -> CGAL::Bbox_3
165 {
166 auto const vertices = triangulation.finite_vertex_handles();
167 return locking_box(
168 vertices.begin(), vertices.end(),
169 [](auto const vertex) -> auto const& { return vertex->point(); });
170 }
171#endif
172
177 template <int dimension>
178 class Delaunay_state
179 {
180 public:
181 using Delaunay = Delaunay_t<dimension>;
182 using Kernel = typename TriangulationTraits<dimension>::Kernel;
183
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>;
188#else
189 struct Lock_owner
190 {};
191#endif
192
193 static_assert(
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 "
201 "members.");
202
203 private:
204 Lock_owner m_lock_data_structure;
205 Delaunay m_triangulation;
206
207 struct Pending_state
208 {
209 Lock_owner lock_data_structure;
210 Delaunay triangulation;
211 };
212
213 [[nodiscard]] static auto make_empty_state() -> Pending_state
214 {
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)};
221#else
222 return {Lock_owner{}, Delaunay{}};
223#endif
224 }
225
226 [[nodiscard]] static auto make_insertion_state(
227 Causal_vertices_t<dimension> const& causal_vertices) -> Pending_state
228 {
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)};
235#else
236 static_cast<void>(causal_vertices);
237 return {Lock_owner{}, Delaunay{}};
238#endif
239 }
240
241 [[nodiscard]] static auto make_adopted_state(Delaunay source)
242 -> Pending_state
243 {
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)};
250#else
251 source.set_lock_data_structure(nullptr);
252 return {Lock_owner{}, std::move(source)};
253#endif
254 }
255
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); }
260
261 public:
262 Delaunay_state() : Delaunay_state{make_empty_state()} {}
263
264 explicit Delaunay_state(
265 Causal_vertices_t<dimension> const& causal_vertices)
266 : Delaunay_state{make_insertion_state(causal_vertices)}
267 {
268 auto const inserted = m_triangulation.insert(causal_vertices.begin(),
269 causal_vertices.end());
270 if (inserted != std::ssize(causal_vertices))
271 {
272 throw std::invalid_argument(
273 "Causal vertices must contain unique geometric points.");
274 }
275 }
276
277 explicit Delaunay_state(Delaunay source)
278 : Delaunay_state{make_adopted_state(std::move(source))}
279 {}
280
281 Delaunay_state(Delaunay_state const& other)
282 : Delaunay_state{Delaunay{other.m_triangulation}}
283 {}
284
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); }
289
290 friend void swap(Delaunay_state& lhs, Delaunay_state& rhs) noexcept
291 {
292 lhs.m_triangulation.swap(rhs.m_triangulation);
293 using std::swap;
294 swap(lhs.m_lock_data_structure, rhs.m_lock_data_structure);
295 }
296
297 auto operator=(Delaunay_state const& other) -> Delaunay_state&
298 {
299 if (this != &other)
300 {
301 Delaunay_state copy{other};
302 swap(*this, copy);
303 }
304 return *this;
305 }
306
307 auto operator=(Delaunay_state&& other) noexcept -> Delaunay_state&
308 {
309 if (this != &other) { swap(*this, other); }
310 return *this;
311 }
312
313 ~Delaunay_state() = default;
314
315 [[nodiscard]] auto triangulation() const noexcept -> Delaunay const&
316 { return m_triangulation; }
317
320 [[nodiscard]] auto mutable_triangulation_unchecked() noexcept -> Delaunay&
321 { return m_triangulation; }
322
323 [[nodiscard]] auto lock_data_structure() const noexcept
324 {
325#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
326 CDT_ENABLE_PARALLEL_TRIANGULATION
327 return m_lock_data_structure.get();
328#else
329 return nullptr;
330#endif
331 }
332
333 [[nodiscard]] auto has_consistent_lock_binding() const noexcept -> bool
334 {
335 return m_triangulation.get_lock_data_structure() ==
336 lock_data_structure();
337 }
338
339 [[nodiscard]] auto into_detached_triangulation() && -> Delaunay
340 {
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();
345#endif
346 return std::move(m_triangulation);
347 }
348 };
349 } // namespace detail
350
352 enum class CellType
353 {
354 // 3D simplices
356 TWO_TWO = 22,
358 ACAUSAL = 99,
360 };
361
363 enum class EdgeType
364 {
367 };
368} // namespace cdt
369
371{
382
391 template <int dimension>
392 [[nodiscard]] auto make_causal_vertices(
393 std::span<Point_t<dimension> const> vertices,
394 std::span<size_t const> timevalues) -> Causal_vertices_t<dimension>
395 {
396 if (vertices.size() != timevalues.size())
397 {
398 throw std::length_error("Vertices and timevalues must be the same size.");
399 }
400 Causal_vertices_t<dimension> causal_vertices;
401 causal_vertices.reserve(vertices.size());
402 std::ranges::transform(
403 vertices, timevalues, std::back_inserter(causal_vertices),
404 [](Point_t<dimension> point, size_t time) {
405 if (!std::in_range<Int_precision>(time))
406 {
407 throw std::out_of_range("Timevalue does not fit Int_precision.");
408 }
409 return std::pair{point, static_cast<Int_precision>(time)};
410 });
411 return causal_vertices;
412 }
413
421 template <int dimension>
422 [[nodiscard]] auto collect_edges(Delaunay_t<dimension> const& delaunay)
423 {
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())
428 {
429 assert(delaunay.tds().is_valid(edge.first, edge.second, edge.third));
430 init_edges.emplace_back(edge);
431 }
432 assert(init_edges.size() == delaunay.number_of_finite_edges());
433 return init_edges;
434 } // collect_edges
435
445 template <int dimension>
446 [[nodiscard]] auto find_vertex(Delaunay_t<dimension> const& delaunay,
447 Point_t<dimension> const& point)
448 -> std::optional<Vertex_handle_t<dimension>>
449 {
450 if (Vertex_handle_t<dimension> vertex{nullptr};
451 delaunay.is_vertex(point, vertex))
452 {
453 return vertex;
454 }
455 return std::nullopt;
456 } // find_vertex
457
471 template <int dimension>
472 [[nodiscard]] auto find_cell(Delaunay_t<dimension> const& delaunay,
477 -> std::optional<Cell_handle_t<dimension>>
478 {
479 if (Cell_handle_t<dimension> cell{nullptr};
480 delaunay.is_cell(vh1, vh2, vh3, vh4, cell))
481 {
482 return cell;
483 }
484 return std::nullopt;
485 } // find_cell
486
489 template <int dimension>
490 constexpr auto compare_v_info = [](Vertex_handle_t<dimension> const& lhs,
491 Vertex_handle_t<dimension> const& rhs) {
492 return lhs->info() < rhs->info();
493 };
494
499 template <int dimension, detail::ConstForwardRange Container>
500 [[nodiscard]] auto find_max_timevalue(Container const& t_vertices)
502 {
503 if (std::ranges::empty(t_vertices))
504 {
505 throw std::invalid_argument("Cannot classify an empty triangulation.");
506 }
507 auto const max_element =
508 std::ranges::max_element(t_vertices, compare_v_info<dimension>);
509 return (*max_element)->info();
510 } // find_max_timevalue
511
516 template <int dimension, detail::ConstForwardRange Container>
517 [[nodiscard]] auto find_min_timevalue(Container const& t_vertices)
519 {
520 if (std::ranges::empty(t_vertices))
521 {
522 throw std::invalid_argument("Cannot classify an empty triangulation.");
523 }
524 auto const min_element =
525 std::ranges::min_element(t_vertices, compare_v_info<dimension>);
526 return (*min_element)->info();
527 } // find_min_timevalue
528
533 template <int dimension>
534 [[nodiscard]] auto classify_edge(Edge_handle_t<dimension> const& t_edge)
535 -> EdgeType
536 {
537#ifndef NDEBUG
538 spdlog::debug("{} called.\n", CDT_PRETTY_FUNCTION);
539#endif
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();
543
544#ifndef NDEBUG
545 spdlog::trace("Edge: Vertex(1) timevalue: {} Vertex(2) timevalue: {}\n",
546 time1, time2);
547#endif
548
549 return time1 != time2 ? EdgeType::TIMELIKE : EdgeType::SPACELIKE;
550 } // classify_edge
551
556 template <int dimension>
557 [[nodiscard]] auto filter_edges(
558 std::vector<Edge_handle_t<dimension>> const& t_edges,
559 EdgeType const edge_type) -> std::vector<Edge_handle_t<dimension>>
560 {
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) {
565 return edge_type == classify_edge<dimension>(edge);
566 });
567 return filtered_edges;
568 } // filter_edges
569
574 template <int dimension>
575 [[nodiscard]] auto filter_cells(
576 std::vector<Cell_handle_t<dimension>> const& t_cells,
577 CellType const& t_cell_type) -> std::vector<Cell_handle_t<dimension>>
578 {
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);
584 });
585 return filtered_cells;
586 } // filter_cells
587
592 template <int dimension>
593 [[nodiscard]] auto squared_radius(Vertex_handle_t<dimension> const& t_vertex)
594 -> double
595 {
596 typename detail::TriangulationTraits<dimension>::squared_distance const r_2;
597 return r_2(t_vertex->point(),
598 detail::TriangulationTraits<dimension>::ORIGIN_POINT);
599 } // squared_radius
600
616 template <int dimension>
617 [[nodiscard]] auto expected_timevalue(
618 Vertex_handle_t<dimension> const& t_vertex, double t_initial_radius,
619 double t_foliation_spacing) -> Int_precision
620 {
621 auto const radius = std::sqrt(squared_radius<dimension>(t_vertex));
622 return static_cast<Int_precision>(
623 std::lround((radius - t_initial_radius + t_foliation_spacing) /
624 t_foliation_spacing));
625 } // expected_timevalue
626
634 template <int dimension>
635 [[nodiscard]] auto is_vertex_timevalue_correct(
636 Vertex_handle_t<dimension> const& t_vertex, double const t_initial_radius,
637 double const t_foliation_spacing) -> bool
638 {
639 auto const timevalue = expected_timevalue<dimension>(
640 t_vertex, t_initial_radius, t_foliation_spacing);
641#ifndef NDEBUG
642 spdlog::trace("Vertex({}) timevalue {} has expected timevalue == {}\n",
643 utilities::point_to_str(t_vertex->point()), t_vertex->info(),
644 timevalue);
645#endif
646 return timevalue == t_vertex->info();
647 } // is_vertex_timevalue_correct
648
654 template <int dimension>
655 [[nodiscard]] auto collect_vertices(
656 Delaunay_t<dimension> const& t_triangulation)
657 {
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())
661 {
662 assert(t_triangulation.tds().is_vertex(vertex));
663 vertices.emplace_back(vertex);
664 }
665 return vertices;
666 } // collect_vertices
667
675 template <int dimension>
676 [[nodiscard]] auto check_vertices(
677 Delaunay_t<dimension> const& t_triangulation, double t_initial_radius,
678 double t_foliation_spacing)
679 {
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);
684 });
685 } // check_vertices
686
692 template <int dimension>
693 [[nodiscard]] auto collect_cells(Delaunay_t<dimension> const& t_triangulation)
694 -> std::vector<Cell_handle_t<dimension>>
695 {
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())
699 {
700 assert(t_triangulation.tds().is_cell(cell));
701 cells.emplace_back(cell);
702 }
703 return cells;
704 } // collect_cells
705
710 template <int dimension>
711 [[nodiscard]] auto get_vertices_from_cells(
712 std::vector<Cell_handle_t<dimension>> const& t_cells)
713 {
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)
717 {
718 cell_vertices.emplace(t_cell->vertex(i));
719 }
720 };
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());
724 return result;
725 } // get_vertices_from_cells
726
734 template <int dimension>
735 [[nodiscard]] auto find_incorrect_vertices(
736 std::vector<Cell_handle_t<dimension>> const& t_cells,
737 double t_initial_radius, double t_foliation_spacing)
738 {
739 auto checked_vertices = get_vertices_from_cells<dimension>(t_cells);
740 std::vector<Vertex_handle_t<dimension>> incorrect_vertices;
741
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);
747 });
748 return incorrect_vertices;
749 } // find_incorrect_vertices
750
759 template <int dimension>
760 [[nodiscard]] auto find_incorrect_vertices(
761 Delaunay_t<dimension> const& t_triangulation, double t_initial_radius,
762 double t_foliation_spacing)
763 {
764 auto cells_to_check = collect_cells<dimension>(t_triangulation);
765 return find_incorrect_vertices<dimension>(cells_to_check, t_initial_radius,
766 t_foliation_spacing);
767 } // find_incorrect_vertices
768
778 template <int dimension>
779 [[nodiscard]] auto fix_vertices(
780 std::vector<Cell_handle_t<dimension>> const& t_cells,
781 double t_initial_radius, double t_foliation_spacing)
782 {
783 auto incorrect_vertices = find_incorrect_vertices<dimension>(
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);
789 });
790 return !incorrect_vertices.empty();
791 } // fix_vertices
792
801 template <int dimension>
802 [[nodiscard]] auto fix_vertices(Delaunay_t<dimension>& t_triangulation,
803 double const t_initial_radius,
804 double const t_foliation_spacing) -> bool
805 {
807 t_initial_radius, t_foliation_spacing);
808 } // fix_vertices
809
814 template <int dimension>
815 [[nodiscard]] auto expected_cell_type(Cell_handle_t<dimension> const& t_cell)
816 {
817#ifndef NDEBUG
818 spdlog::debug("{} called.\n", CDT_PRETTY_FUNCTION);
819#endif
820 std::array<int, static_cast<std::size_t>(dimension) + 1>
821 vertex_timevalues{};
822 // There are d+1 vertices in a d-dimensional simplex
823 for (auto i = 0; i < dimension + 1; ++i)
824 {
825 // Obtain timevalue of vertex
826 vertex_timevalues.at(static_cast<std::size_t>(i)) =
827 t_cell->vertex(i)->info();
828 }
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;
835 // A properly foliated simplex should have a timevalue difference of 1
836 if (maxtime - mintime != 1 || maxtime == mintime)
837 {
838#ifndef NDEBUG
839 spdlog::trace("This simplex is acausal:\n");
840 spdlog::trace("Max timevalue is {} and min timevalue is {}.\n", maxtime,
841 mintime);
842 spdlog::trace("--\n");
843#endif
844 return CellType::ACAUSAL;
845 }
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);
850
851 // 3D simplices
852 if (max_vertices == 3 && min_vertices == 1) { return CellType::ONE_THREE; }
853 if (max_vertices == 2 && min_vertices == 2) { return CellType::TWO_TWO; }
854 if (max_vertices == 1 && min_vertices == 3) { return CellType::THREE_ONE; }
855
856 // If we got here, there's some kind of error
857#ifndef NDEBUG
858 spdlog::trace("This simplex has an error:\n");
859 spdlog::trace("Max timevalue is {} and min timevalue is {}.\n", maxtime,
860 mintime);
861 spdlog::trace(
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");
866#endif
868 } // expected_cell_type
869
874 template <int dimension>
875 [[nodiscard]] auto is_cell_type_correct(
876 Cell_handle_t<dimension> const& t_cell) -> bool
877 {
878 auto cell_type = expected_cell_type<dimension>(t_cell);
879 return cell_type != CellType::ACAUSAL &&
880 cell_type != CellType::UNCLASSIFIED &&
881 cell_type == static_cast<CellType>(t_cell->info());
882 } // is_cell_type_correct
883
889 template <int dimension>
890 [[nodiscard]] auto check_cells(Delaunay_t<dimension> const& t_triangulation)
891 -> bool
892 {
893 return std::ranges::all_of(
894 t_triangulation.finite_cell_handles(),
895 [](auto const cell) { return is_cell_type_correct<dimension>(cell); });
896 } // check_cells
897
903 template <int dimension>
904 [[nodiscard]] auto find_incorrect_cells(
905 Delaunay_t<dimension> const& t_triangulation)
906 {
907 auto checked_cells = collect_cells<dimension>(t_triangulation);
908
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);
913 });
914 return incorrect_cells;
915 } // find_incorrect_cells
916
923 template <int dimension>
924 [[nodiscard]] auto fix_cells(Delaunay_t<dimension>& t_triangulation) -> bool
925 {
926 auto incorrect_cells = find_incorrect_cells<dimension>(t_triangulation);
927 std::for_each(
928 incorrect_cells.begin(), incorrect_cells.end(), [&](auto const& cell) {
929 cell->info() =
930 static_cast<Int_precision>(expected_cell_type<dimension>(cell));
931 });
932 return !incorrect_cells.empty();
933 } // fix_cells
934
938 template <int dimension>
940 {
941 fmt::print("Cell info => {}\n", cell->info());
942 // There are d+1 vertices in a d-dimensional simplex
943 for (int j = 0; j < dimension + 1; ++j)
944 {
945 fmt::print("Vertex({}) Point: ({}) Timevalue: {}\n", j,
946 utilities::point_to_str(cell->vertex(j)->point()),
947 cell->vertex(j)->info());
948 }
949 fmt::print("---\n");
950 } // print_cell
951
956 template <int dimension, detail::ConstForwardRange Container>
957 void print_cells(Container const& t_cells)
958 {
959 for (auto const& cell : t_cells) { print_cell<dimension>(cell); }
960 } // print_cells
961
967 template <int dimension, detail::ConstForwardRange Container>
968 void debug_print_cells(Container const& t_cells)
969 {
970 for (auto const& cell : t_cells)
971 {
972 spdlog::debug("Cell info => {}\n", cell->info());
973 for (int j = 0; j < dimension + 1; ++j)
974 {
975 spdlog::debug("Vertex({}) Point: ({}) Timevalue: {}\n", j,
976 utilities::point_to_str(cell->vertex(j)->point()),
977 cell->vertex(j)->info());
978 }
979 spdlog::debug("---\n");
980 }
981 } // debug_print_cells
982
986 template <int dimension>
988 {
989 for (int j = 0; j < dimension + 1; ++j)
990 {
991 fmt::print("Neighboring cell {}:", j);
992 print_cell<dimension>(cell->neighbor(j));
993 }
994 } // print_neighboring_cells
995
1003 template <int dimension>
1005 {
1006 fmt::print(
1007 "Edge: Vertex({}) Point({}) Timevalue: {} -> Vertex({}) Point({}) "
1008 "Timevalue: {}\n",
1009 t_edge.second,
1010 utilities::point_to_str(t_edge.first->vertex(t_edge.second)->point()),
1011 t_edge.first->vertex(t_edge.second)->info(), t_edge.third,
1012 utilities::point_to_str(t_edge.first->vertex(t_edge.third)->point()),
1013 t_edge.first->vertex(t_edge.third)->info());
1014 } // print_edge
1015
1026 template <int dimension, detail::ConstForwardRange Container>
1027 [[nodiscard]] auto collect_spacelike_facets(Container const& t_facets)
1028 -> std::vector<std::pair<Int_precision, Facet_t<dimension>>>
1029 {
1030#ifndef NDEBUG
1031 spdlog::debug("{} called.\n", CDT_PRETTY_FUNCTION);
1032#endif
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>)
1036 {
1037 space_faces.reserve(std::ranges::size(t_facets));
1038 }
1039 for (auto const& face : t_facets)
1040 {
1041 Cell_handle_t<dimension> const cell = face.first;
1042 auto index_of_facet = face.second;
1043#ifndef NDEBUG
1044 spdlog::trace("Facet index is {}\n", index_of_facet);
1045#endif
1046 std::set<Int_precision> facet_timevalues;
1047 // There are d+1 vertices in a d-dimensional simplex
1048 for (int i = 0; i < dimension + 1; ++i)
1049 {
1050 if (i != index_of_facet)
1051 {
1052#ifndef NDEBUG
1053 spdlog::trace("Vertex[{}] has timevalue {}\n", i,
1054 cell->vertex(i)->info());
1055#endif
1056 facet_timevalues.insert(cell->vertex(i)->info());
1057 }
1058 }
1059 // If we have a 1-element set then all timevalues on that facet are
1060 // equal
1061 if (facet_timevalues.size() == 1)
1062 {
1063#ifndef NDEBUG
1064 spdlog::trace("Facet is spacelike on timevalue {}.\n",
1065 *facet_timevalues.begin());
1066#endif
1067 space_faces.emplace_back(*facet_timevalues.begin(), face);
1068 }
1069 else
1070 {
1071#ifndef NDEBUG
1072 spdlog::trace("Facet is timelike.\n");
1073#endif
1074 }
1075 }
1076 std::ranges::stable_sort(
1077 space_faces, std::ranges::less{},
1078 [](Volume_entry const& entry) noexcept { return entry.first; });
1079 return space_faces;
1080 } // collect_spacelike_facets
1081
1089 template <int dimension, detail::ConstForwardRange Container>
1090 [[nodiscard]] auto volume_per_timeslice(Container const& t_facets)
1091 -> std::multimap<Int_precision, Facet_t<dimension>>
1092 {
1093 auto space_faces = collect_spacelike_facets<dimension>(t_facets);
1094 return {std::make_move_iterator(space_faces.begin()),
1095 std::make_move_iterator(space_faces.end())};
1096 } // volume_per_timeslice
1097
1114 template <int dimension>
1116 Delaunay_t<dimension> const& t_triangulation)
1117 -> std::vector<Cell_handle_t<dimension>>
1118 {
1119 auto const& cells = collect_cells<dimension>(t_triangulation);
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;
1127 });
1128 return invalid_cells;
1129 } // find_invalid_timevalue_cells
1130
1135 template <int dimension>
1136 [[nodiscard]] auto has_valid_timevalues(
1137 Delaunay_t<dimension> const& triangulation) -> bool
1138 { return find_invalid_timevalue_cells<dimension>(triangulation).empty(); }
1139
1145 template <int dimension>
1146 [[nodiscard]] auto find_bad_vertex(Cell_handle_t<dimension> const& cell)
1148 {
1149#ifndef NDEBUG
1150 spdlog::debug("{} called.\n", CDT_PRETTY_FUNCTION);
1151 spdlog::debug("===Invalid Cell===\n");
1152 std::vector<Cell_handle_t<dimension>> bad_cell{cell};
1153 debug_print_cells<dimension>(std::span{bad_cell});
1154#endif
1155 std::multimap<Int_precision, Vertex_handle_t<dimension>> vertices;
1156 for (int i = 0; i < dimension + 1; ++i)
1157 {
1158 vertices.emplace(
1159 std::make_pair(cell->vertex(i)->info(), cell->vertex(i)));
1160 }
1161 // Now it's sorted in the multimap
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);
1166 // Return the vertex with the highest value if there are equal or more
1167 // vertices with lower values. Note that we preferentially return higher
1168 // timeslice vertices because there are typically more cells at higher
1169 // timeslices (see expected_points_per_timeslice())
1170 return minvalue_count >= maxvalue_count ? vertices.rbegin()->second
1171 : vertices.begin()->second;
1172 } // find_bad_vertex
1173
1181 template <int dimension>
1182 [[nodiscard]] auto fix_timevalues(Delaunay_t<dimension>& t_triangulation)
1183 -> bool
1184 {
1185 // Obtain a container of cells that are incorrectly foliated
1186 auto invalid_cells =
1188 if (!invalid_cells.empty())
1189 {
1190 std::set<Vertex_handle_t<dimension>> vertices_to_remove;
1191 // Transform the invalid cells into a set of vertices to remove
1192 // Reduction to unique vertices happens via the set container
1193 std::transform(
1194 invalid_cells.begin(), invalid_cells.end(),
1195 std::inserter(vertices_to_remove, vertices_to_remove.begin()),
1197 // Remove the vertices
1198#ifndef NDEBUG
1199 spdlog::warn("There are {} invalid vertices.\n",
1200 vertices_to_remove.size());
1201#endif
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());
1206 return true;
1207 }
1208 return false;
1209 } // fix_timevalues
1210
1230 template <int dimension, std::uniform_random_bit_generator Generator>
1231 [[nodiscard]] auto make_foliated_ball(Int_precision const t_simplices,
1232 Int_precision const t_timeslices,
1233 double const initial_radius,
1234 double const foliation_spacing,
1235 Generator& generator)
1236 {
1237 if (t_simplices < 2 || t_timeslices < 2)
1238 {
1239 throw std::invalid_argument(
1240 "Simplices and timeslices must each be at least 2.");
1241 }
1242 if (!std::isfinite(initial_radius) || initial_radius <= 0.0)
1243 {
1244 throw std::invalid_argument(
1245 "Initial radius must be finite and positive.");
1246 }
1247 if (!std::isfinite(foliation_spacing) || foliation_spacing <= 0.0)
1248 {
1249 throw std::invalid_argument(
1250 "Foliation spacing must be finite and positive.");
1251 }
1252
1253 auto const population = utilities::generated_population_bounds(
1254 dimension, t_simplices, t_timeslices, initial_radius,
1255 foliation_spacing);
1256 if (population.points_per_timeslice < 2)
1257 {
1258 throw std::invalid_argument(
1259 "Simplices and timeslices would create an empty triangulation.");
1260 }
1261 if (!std::isfinite(population.last_layer_points) ||
1262 population.last_layer_points >
1263 static_cast<long double>(std::numeric_limits<Int_precision>::max()))
1264 {
1265 throw std::out_of_range(
1266 "Foliation parameters generate too many points per timeslice.");
1267 }
1268
1269 Causal_vertices_t<dimension> causal_vertices;
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)};
1273
1274 for (gsl::index i = 0; i < t_timeslices; ++i)
1275 {
1276 auto const radius =
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)
1281 {
1282 throw std::invalid_argument(
1283 "Foliation parameters do not populate every timeslice.");
1284 }
1285 if (generated_points >
1286 static_cast<long double>(std::numeric_limits<Int_precision>::max()))
1287 {
1288 throw std::out_of_range(
1289 "Foliation parameters generate too many points per timeslice.");
1290 }
1291 Spherical_points_generator_t<dimension> gen{radius, cgal_random};
1292 // Generate random points at the radius
1293 for (gsl::index j = 0; j < static_cast<Int_precision>(generated_points);
1294 ++j)
1295 {
1296 causal_vertices.emplace_back(*gen++, i + 1);
1297 } // j
1298 } // i
1299 if (causal_vertices.size() < static_cast<std::size_t>(dimension + 1))
1300 {
1301 throw std::invalid_argument("Parameters create an empty triangulation.");
1302 }
1303 return causal_vertices;
1304 } // make_foliated_ball
1305
1328 template <int dimension, std::uniform_random_bit_generator Generator>
1329 [[nodiscard]] auto make_triangulation(Int_precision const t_simplices,
1330 Int_precision t_timeslices,
1331 double const initial_radius,
1332 double const foliation_spacing,
1333 Generator& generator)
1335 {
1336#ifndef NDEBUG
1337 spdlog::debug("{} called.\n", CDT_PRETTY_FUNCTION);
1338#endif
1339 fmt::print("\nGenerating universe ...\n");
1340 // Make initial triangulation
1341 auto causal_vertices =
1342 make_foliated_ball<dimension>(t_simplices, t_timeslices, initial_radius,
1343 foliation_spacing, generator);
1344 detail::Delaunay_state<dimension> state{causal_vertices};
1345 auto& triangulation = state.mutable_triangulation_unchecked();
1346
1347 // Fix vertices
1348 for (auto passes = 1; passes < detail::MAX_FIX_PASSES + 1; ++passes)
1349 {
1350 if (!fix_vertices<dimension>(triangulation, initial_radius,
1351 foliation_spacing))
1352 {
1353 break;
1354 }
1355#ifndef NDEBUG
1356 spdlog::warn("Deleting incorrect vertices pass #{}\n", passes);
1357#endif
1358 }
1359
1360 // Fix timeslices
1361 for (auto passes = 1; passes < detail::MAX_FIX_PASSES + 1; ++passes)
1362 {
1363 if (!fix_timevalues<dimension>(triangulation)) { break; }
1364#ifndef NDEBUG
1365 spdlog::warn("Fixing timeslices pass #{}\n", passes);
1366#endif
1367 }
1368
1369 // Fix cells
1370 for (auto i = 1; i < detail::MAX_FIX_PASSES + 1; ++i)
1371 {
1372 if (!fix_cells<dimension>(triangulation)) { break; }
1373#ifndef NDEBUG
1374 spdlog::warn("Fixing incorrect cells pass #{}\n", i);
1375#endif
1376 }
1377
1378 utilities::print_delaunay(triangulation);
1379 assert(has_valid_timevalues<dimension>(triangulation));
1380 return std::move(state).into_detached_triangulation();
1381 } // make_triangulation
1382
1385 template <int dimension>
1387
1406 template <>
1407 class [[nodiscard("This contains data!")]] FoliatedTriangulation<3> // NOLINT
1408 {
1409 using Delaunay = Delaunay_t<3>;
1410 using Cell_handle = Cell_handle_t<3>;
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>>;
1414 using Vertex_handle = Vertex_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>;
1419
1420 static_assert(std::is_nothrow_swappable_v<double>,
1421 "FoliatedTriangulation swap requires non-throwing scalars.");
1422 static_assert(
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.");
1432 static_assert(
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 "
1440 "moves.");
1441
1442 [[nodiscard]] static auto cache_spacelike_facets(
1443 Face_container const& faces) -> Volume_by_timeslice
1444 { return collect_spacelike_facets<3>(std::span{faces}); }
1445
1446 [[nodiscard]] static auto require_nonempty(Delaunay_state state)
1447 -> Delaunay_state
1448 {
1449 if (state.triangulation().number_of_vertices() == 0)
1450 {
1451 throw std::invalid_argument(
1452 "A foliated triangulation must contain at least one vertex.");
1453 }
1454 return state;
1455 }
1456
1457 [[nodiscard]] auto triangulation() noexcept -> Delaunay&
1458 { return m_delaunay_state.mutable_triangulation_unchecked(); }
1459
1460 [[nodiscard]] auto triangulation() const noexcept -> Delaunay const&
1461 { return m_delaunay_state.triangulation(); }
1462
1465 Delaunay_state m_delaunay_state;
1466 double m_initial_radius{INITIAL_RADIUS};
1467 double m_foliation_spacing{FOLIATION_SPACING};
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;
1478 Int_precision m_max_timevalue{0};
1479 Int_precision m_min_timevalue{0};
1480
1481 [[nodiscard]] auto has_consistent_structure() const -> bool
1482 {
1483 auto const& delaunay = triangulation();
1484 auto const cells_are_partitioned =
1485 m_three_one.size() + m_two_two.size() + m_one_three.size() ==
1486 m_cells.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;
1497 }
1498
1499 [[nodiscard]] auto has_consistent_derived_state() const -> bool
1500 {
1501 if (!has_consistent_structure()) { return false; }
1502
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())
1507 {
1508 return false;
1509 }
1510
1511 auto const& tds = delaunay.tds();
1512 auto const valid_vertices = std::ranges::all_of(
1513 m_vertices,
1514 [&tds](Vertex_handle const vertex) { return tds.is_vertex(vertex); });
1515 auto const valid_cells = std::ranges::all_of(
1516 m_cells,
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);
1521 });
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);
1525 });
1526 if (!valid_vertices || !valid_cells || !valid_faces || !valid_edges)
1527 {
1528 return false;
1529 }
1530
1531 if (m_three_one != filter_cells<3>(m_cells, CellType::THREE_ONE) ||
1532 m_two_two != filter_cells<3>(m_cells, CellType::TWO_TWO) ||
1533 m_one_three != filter_cells<3>(m_cells, CellType::ONE_THREE) ||
1534 m_spacelike_facets != cache_spacelike_facets(m_faces) ||
1535 m_timelike_edges != filter_edges<3>(m_edges, EdgeType::TIMELIKE) ||
1536 m_spacelike_edges != filter_edges<3>(m_edges, EdgeType::SPACELIKE))
1537 {
1538 return false;
1539 }
1540
1541 if (m_vertices.empty()) { return true; }
1542 return m_max_timevalue == find_max_timevalue<3>(std::span{m_vertices}) &&
1543 m_min_timevalue == find_min_timevalue<3>(std::span{m_vertices});
1544 }
1545
1546 public:
1549
1552
1557 {
1558 if (other.triangulation().number_of_vertices() == 0)
1559 {
1560 m_initial_radius = other.m_initial_radius;
1561 m_foliation_spacing = other.m_foliation_spacing;
1562 return;
1563 }
1564 FoliatedTriangulation copy{Delaunay_state{other.m_delaunay_state},
1565 other.m_initial_radius,
1566 other.m_foliation_spacing};
1567 swap(copy, *this);
1568 }
1569
1575 {
1576 if (this == &other) { return *this; }
1577 FoliatedTriangulation copy{other};
1578 swap(copy, *this);
1579 return *this;
1580 }
1581
1587
1591 auto operator=(FoliatedTriangulation&& other) noexcept
1593 {
1594 if (this != &other) { swap(other, *this); }
1595 return *this;
1596 }
1597
1605 friend void swap(FoliatedTriangulation& swap_from,
1606 FoliatedTriangulation& swap_into) noexcept
1607 {
1608 // Delaunay_state swaps the triangulations before their lock owners so
1609 // each triangulation remains paired with the grid it observes.
1610 // See
1611 // https://doc.cgal.org/latest/Triangulation_3/classCGAL_1_1Triangulation__3.html#a767066a964b4d7b14376e5f5d1a04b34
1612 using std::swap;
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);
1628
1629 } // swap
1630
1641 Delaunay triangulation, double const initial_radius = INITIAL_RADIUS,
1642 double const foliation_spacing = FOLIATION_SPACING)
1643 : FoliatedTriangulation{Delaunay_state{std::move(triangulation)},
1645 {}
1646
1647 private:
1648 explicit FoliatedTriangulation(Delaunay_state state,
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}
1654 , m_vertices{classify_vertices(collect_vertices<3>(triangulation()))}
1655 , m_cells{classify_cells(collect_cells<3>(triangulation()))}
1656 , m_three_one{filter_cells<3>(m_cells, CellType::THREE_ONE)}
1657 , m_two_two{filter_cells<3>(m_cells, CellType::TWO_TWO)}
1658 , m_one_three{filter_cells<3>(m_cells, CellType::ONE_THREE)}
1659 , m_faces{collect_faces()}
1660 , m_spacelike_facets{cache_spacelike_facets(m_faces)}
1661 , m_edges{foliated_triangulations::collect_edges<3>(triangulation())}
1662 , m_timelike_edges{filter_edges<3>(m_edges, EdgeType::TIMELIKE)}
1663 , m_spacelike_edges{filter_edges<3>(m_edges, EdgeType::SPACELIKE)}
1664 , m_max_timevalue{find_max_timevalue<3>(std::span{m_vertices})}
1665 , m_min_timevalue{find_min_timevalue<3>(std::span{m_vertices})}
1666 {}
1667
1668 public:
1682 Int_precision const t_timeslices,
1683 cdt::Random& generator,
1684 double const t_initial_radius = INITIAL_RADIUS,
1685 double const t_foliation_spacing = FOLIATION_SPACING)
1687 make_triangulation<3>(t_simplices, t_timeslices, t_initial_radius,
1688 t_foliation_spacing, generator),
1689 t_initial_radius, t_foliation_spacing}
1690 {}
1691
1703 Int_precision const t_timeslices,
1704 cdt::Random&& generator,
1705 double const t_initial_radius = INITIAL_RADIUS,
1706 double const t_foliation_spacing = FOLIATION_SPACING)
1707 : FoliatedTriangulation{t_simplices, t_timeslices, generator,
1708 t_initial_radius, t_foliation_spacing}
1709 {}
1710
1721 Causal_vertices_t<3> const& causal_vertices,
1722 double const t_initial_radius = INITIAL_RADIUS,
1723 double const t_foliation_spacing = FOLIATION_SPACING)
1724 : FoliatedTriangulation{Delaunay_state{causal_vertices},
1725 t_initial_radius, t_foliation_spacing}
1726 {}
1727
1734 [[nodiscard]] auto is_foliated() const -> bool
1735 { return has_valid_timevalues<3>(triangulation()); } // is_foliated
1736
1740 [[nodiscard]] auto is_delaunay() const -> bool
1741 { return triangulation().is_valid(); } // is_delaunay
1742
1744 [[nodiscard]] auto is_tds_valid() const -> bool
1745 { return triangulation().tds().is_valid(); } // is_tds_valid
1746
1750 [[nodiscard]] auto is_structurally_correct() const -> bool
1751 {
1752 return has_consistent_structure() && is_tds_valid() && check_all_cells();
1753 }
1754
1759 [[nodiscard]] auto is_correct() const -> bool
1760 { return is_structurally_correct(); } // is_correct
1761
1765 [[nodiscard]] auto is_correct_with_diagnostics() const -> bool
1766 { return is_structurally_correct() && has_consistent_derived_state(); }
1767
1770 [[nodiscard]] auto is_initialized() const -> bool
1771 { return is_correct() && is_delaunay(); } // is_initialized
1772
1774 [[nodiscard]] auto is_fixed() -> bool
1775 {
1776 Delaunay updated{triangulation()};
1777 auto const fixed_vertices = foliated_triangulations::fix_vertices<3>(
1778 updated, m_initial_radius, m_foliation_spacing);
1779 auto const fixed_cells = foliated_triangulations::fix_cells<3>(updated);
1780 auto const fixed_timeslices =
1782 auto const changed = fixed_vertices || fixed_cells || fixed_timeslices;
1783 if (changed)
1784 {
1785 FoliatedTriangulation replacement{std::move(updated), m_initial_radius,
1786 m_foliation_spacing};
1787 swap(replacement, *this);
1788 }
1789 return changed;
1790 } // is_fixed
1791
1795 [[nodiscard]] auto delaunay_snapshot() const -> Delaunay
1796 {
1797 Delaunay snapshot{triangulation()};
1798 // A snapshot does not own this object's lock grid and can outlive it.
1799 snapshot.set_lock_data_structure(nullptr);
1800 return snapshot;
1801 } // delaunay_snapshot
1802
1804 [[nodiscard]] auto number_of_finite_cells() const
1805 {
1806 return triangulation().number_of_finite_cells();
1807 } // number_of_finite_cells
1808
1810 [[nodiscard]] auto number_of_finite_facets() const
1811 {
1812 return triangulation().number_of_finite_facets();
1813 } // number_of_finite_facets
1814
1816 [[nodiscard]] auto number_of_finite_edges() const
1817 {
1818 return triangulation().number_of_finite_edges();
1819 } // number_of_finite_edges
1820
1822 [[nodiscard]] auto number_of_vertices() const
1823 { return triangulation().number_of_vertices(); } // number_of_vertices
1824
1826 [[nodiscard]] auto dimension() const { return triangulation().dimension(); }
1827
1830 [[nodiscard]] auto spacelike_face_count(
1831 Int_precision const timevalue) const noexcept -> std::size_t
1832 {
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());
1837 }
1838
1840 [[nodiscard]] auto number_of_spacelike_faces() const noexcept -> std::size_t
1841 { return m_spacelike_facets.size(); }
1842
1844 [[nodiscard]] auto N1_TL() const
1845 { return static_cast<Int_precision>(m_timelike_edges.size()); } // N1_TL
1846
1848 [[nodiscard]] auto N1_SL() const
1849 { return static_cast<Int_precision>(m_spacelike_edges.size()); } // N1_SL
1850
1852 [[nodiscard]] auto max_time() const { return m_max_timevalue; }
1853
1855 [[nodiscard]] auto min_time() const { return m_min_timevalue; }
1856
1858 [[nodiscard]] auto initial_radius() const { return m_initial_radius; }
1859
1861 [[nodiscard]] auto foliation_spacing() const { return m_foliation_spacing; }
1862
1868 Vertex_handle_t<3> const t_vertex) const -> bool
1869 {
1870 auto const actual_radius_squared = squared_radius<3>(t_vertex);
1871 auto const radius = expected_radius(t_vertex);
1872 auto const expected_radius_squared = std::pow(radius, 2);
1873 if (expected_radius_squared == 0.0)
1874 {
1875 return std::abs(actual_radius_squared) <= TOLERANCE;
1876 }
1877 return actual_radius_squared >
1878 expected_radius_squared * (1 - TOLERANCE) &&
1879 actual_radius_squared < expected_radius_squared * (1 + TOLERANCE);
1880 } // does_vertex_radius_match_timevalue
1881
1892 [[nodiscard]] auto expected_radius(Vertex_handle_t<3> const& t_vertex) const
1893 -> double
1894 {
1895 auto const timevalue = t_vertex->info();
1896 return m_initial_radius + m_foliation_spacing * (timevalue - 1);
1897 } // expected_radial_distance
1898
1902 [[nodiscard]] auto expected_timevalue(
1903 Vertex_handle_t<3> const& t_vertex) const -> int
1904 {
1906 t_vertex, m_initial_radius, m_foliation_spacing);
1907 } // expected_timevalue
1908
1910 [[nodiscard]] auto check_all_vertices() const -> bool
1911 {
1913 triangulation(), m_initial_radius, m_foliation_spacing);
1914 } // check_all_vertices
1915
1921 [[nodiscard]] auto fix_vertices() -> bool
1922 {
1923 Delaunay updated{triangulation()};
1924 auto const changed = foliated_triangulations::fix_vertices<3>(
1925 updated, m_initial_radius, m_foliation_spacing);
1926 if (changed)
1927 {
1928 FoliatedTriangulation replacement{std::move(updated), m_initial_radius,
1929 m_foliation_spacing};
1930 swap(replacement, *this);
1931 }
1932 return changed;
1933 } // fix_vertices
1934
1936 void print_vertices() const
1937 {
1938 for (auto const& vertex : m_vertices)
1939 {
1940 fmt::print("Vertex Point: ({}) Timevalue: {} Expected Timevalue: {}\n",
1941 utilities::point_to_str(vertex->point()), vertex->info(),
1942 expected_timevalue(vertex));
1943 }
1944 } // print_vertices
1945
1948 void print_edges() const
1949 {
1950 for (auto const& edge : m_edges)
1951 {
1953 {
1954 fmt::print("==> timelike\n");
1955 }
1956 else
1957 {
1958 fmt::print("==> spacelike\n");
1959 }
1960 }
1961 } // print_edges
1962
1965 {
1966 for (auto j = min_time(); j <= max_time(); ++j)
1967 {
1968 fmt::print("Timeslice {} has {} spacelike faces.\n", j,
1970 }
1971 } // print_volume_per_timeslice
1972
1974 [[nodiscard]] auto number_of_three_one_cells() const noexcept -> std::size_t
1975 { return m_three_one.size(); }
1976
1978 [[nodiscard]] auto number_of_two_two_cells() const noexcept -> std::size_t
1979 { return m_two_two.size(); }
1980
1982 [[nodiscard]] auto number_of_one_three_cells() const noexcept -> std::size_t
1983 { return m_one_three.size(); }
1984
1990 [[nodiscard]] auto check_all_cells() const -> bool
1991 {
1992 return foliated_triangulations::check_cells<3>(triangulation());
1993 } // check_all_cells
1994
2000 [[nodiscard]] auto fix_cells() -> bool
2001 {
2002 Delaunay updated{triangulation()};
2003 auto const changed = foliated_triangulations::fix_cells<3>(updated);
2004 if (changed)
2005 {
2006 FoliatedTriangulation replacement{std::move(updated), m_initial_radius,
2007 m_foliation_spacing};
2008 swap(replacement, *this);
2009 }
2010 return changed;
2011 } // fix_cells
2012
2017
2019 void print() const
2020 {
2021 fmt::print(
2022 "Triangulation has {} vertices and {} edges and {} faces and {} "
2023 "simplices.\n",
2026 }
2027
2028 private:
2029 [[nodiscard]] auto classify_vertices(Vertex_container const& vertices) const
2030 -> Vertex_container
2031 {
2032 assert(vertices.size() == number_of_vertices());
2033 for (auto const& vertex : vertices)
2034 {
2035 vertex->info() = expected_timevalue(vertex);
2036 }
2037 return vertices;
2038 } // classify_vertices
2039
2044 [[nodiscard]] auto classify_cells(Cell_container const& cells) const
2045 -> Cell_container
2046 {
2047 assert(cells.size() == number_of_finite_cells());
2048 for (auto const& cell : cells)
2049 {
2050 cell->info() = static_cast<int>(expected_cell_type<3>(cell));
2051 }
2052 return cells;
2053 } // classify_cells
2054
2056 [[nodiscard]] auto collect_faces() const -> Face_container
2057 {
2058 // Somewhere in bistellar_flip_really a vertex is rendered invalid
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())
2063 {
2064 assert(triangulation().tds().is_facet(facet.first, facet.second));
2065 init_faces.emplace_back(facet);
2066 }
2067 assert(init_faces.size() == triangulation().number_of_finite_facets());
2068 return init_faces;
2069 } // collect_faces
2070 };
2071
2074
2075} // namespace cdt::foliated_triangulations
2076
2077#endif // CDT_PLUSPLUS_FOLIATEDTRIANGULATION_HPP
Run-owned random-number generation and reproducible stream splitting.
#define CDT_PRETTY_FUNCTION
Cross-platform spelling of the current function signature for diagnostics.
Definition Settings.hpp:37
Traits class for particular uses of CGAL.
Utility functions.
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.
Definition Random.hpp:137
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 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 spacelike_face_count(Int_precision const timevalue) const noexcept -> std::size_t
void print_volume_per_timeslice() const
Print the number of spacelike faces per timeslice.
auto check_all_cells() const -> bool
Check that all cells are correctly classified.
auto operator=(FoliatedTriangulation &&other) noexcept -> FoliatedTriangulation &
Move assignment operator.
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 fix_vertices() -> bool
Fix vertices with wrong timevalues after foliation.
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 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_edges() const
Print timevalues of each vertex in the edge and classify as timelike or spacelike.
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.
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
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.
Definition Settings.hpp:47
constexpr double FOLIATION_SPACING
Default distance between successive foliated timeslices.
Definition Settings.hpp:49
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
Definition Settings.hpp:30
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.
Definition Settings.hpp:55
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.
Definition Settings.hpp:52