CDT++ 1.0.0
Causal Dynamical Triangulations in C++
Loading...
Searching...
No Matches
cdt.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Causal Dynamical Triangulations in C++ using CGAL
3
4 Copyright © 2013–2026 Adam Getchell
5 ******************************************************************************/
6
12
13#include <CGAL/Real_timer.h>
14#include <fmt/ostream.h>
15
16#include <boost/program_options.hpp>
17#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
18 CDT_ENABLE_PARALLEL_TRIANGULATION
19#include <oneapi/tbb/global_control.h>
20#endif
21
22#include <cstdint>
23#include <Metropolis.hpp>
24#include <optional>
25#include <string>
26#include <utility>
27
28#include "Runtime_config.hpp"
29#include "Version.hpp"
30
31using Timer = CGAL::Real_timer;
32
33using namespace cdt;
34using namespace std;
35namespace po = boost::program_options;
36
38static constexpr string_view USAGE{
39 R"(Causal Dynamical Triangulations in C++ using CGAL.
40
41Copyright (c) 2013-2026 Adam Getchell
42
43A program that generates d-dimensional triangulated spacetimes
44with a defined causal structure and evolves them according
45to the Metropolis algorithm. Specify the number of passes to control
46how much evolution is desired. Each pass attempts a number of ergodic
47moves equal to the number of simplices in the simulation.
48
49Usage: ./cdt ((--spherical | --toroidal) -n SIMPLICES -t TIMESLICES
50 [-d DIM] [--init INITIAL RADIUS] [--foliate FOLIATION SPACING]
51 | --input INITIAL.off)
52 -k K --alpha ALPHA --lambda LAMBDA [--no-output] [--seed SEED]
53 [--threads THREADS] [-p PASSES] [-c CHECKPOINT]
54 ./cdt --resume CHECKPOINT.off [-p TOTAL_PASSES] [--no-output]
55
56Optional arguments are in square brackets.
57
58Examples:
59./cdt --spherical -n 32000 -t 11 --alpha 0.6 -k 1.1 --lambda 0.1 --passes 1000
60./cdt -s -n32000 -t11 -a.6 -k1.1 -l.1 -p1000 --seed 92
61./cdt --input <initialize-output>.off -a.6 -k1.1 -l.1 -p1000 --seed 93
62./cdt --resume <checkpoint>.off
63
64Options)"};
65
70auto main(int const argc, char* const argv[]) -> int
71try
72{
73 std::string const intro{USAGE};
74 // Parsed arguments
75 long long simplices{};
76 long long timeslices{};
77 long long dimensions{};
78 double initial_radius{};
79 double foliation_spacing{};
80 long double alpha{};
81 long double k{};
82 long double lambda{};
83 long long passes{};
84 long long checkpoint{};
85 std::uint64_t seed{};
86 long long threads{};
87 std::string input_path;
88 std::string resume_path;
89
90 po::options_description description(intro);
91 description.add_options()("help,h", "Show this message")(
92 "version,v", "Show program version")("spherical,s", "Spherical topology")(
93 "toroidal,e", "Toroidal topology")("simplices,n",
94 po::value<long long>(&simplices),
95 "Approximate number of simplices")(
96 "timeslices,t", po::value<long long>(&timeslices),
97 "Number of timeslices")(
98 "dimensions,d", po::value<long long>(&dimensions)->default_value(3),
99 "Dimensionality")("init,i",
100 po::value<double>(&initial_radius)->default_value(1.0),
101 "Initial radius")(
102 "foliate,f", po::value<double>(&foliation_spacing)->default_value(1.0),
103 "Foliation spacing")("input", po::value<std::string>(&input_path),
104 "Initial-triangulation payload (requires .meta)")(
105 "resume", po::value<std::string>(&resume_path),
106 "Resume the identical Markov chain from a checkpoint")(
107 "no-output", "Do not write checkpoint or final triangulation files")(
108 "seed", po::value<std::uint64_t>(&seed),
109 "Root random seed (default: operating-system entropy)")(
110 "threads", po::value<long long>(&threads)->default_value(1),
111 "Maximum worker threads for supported Delaunay operations")(
112 "alpha,a", po::value<long double>(&alpha),
113 "Negative squared geodesic length of 1-d timelike edges")(
114 "k,k", po::value<long double>(&k), "K = 1/(8*pi*G_newton)")(
115 "lambda,l", po::value<long double>(&lambda), "K * Cosmological constant")(
116 "passes,p", po::value<long long>(&passes)->default_value(100),
117 "Total pass target (resume default: saved target)")(
118 "checkpoint,c", po::value<long long>(&checkpoint)->default_value(10),
119 "Checkpoint every n global passes");
120
121 po::variables_map args;
122 po::store(po::parse_command_line(argc, argv, description), args);
123
124 if (args.count("help"))
125 {
126 fmt::print("{}\n", fmt::streamed(description));
127 return EXIT_SUCCESS;
128 }
129
130 if (args.count("version"))
131 {
132 fmt::print("CDT++ version {}\n", cdt::VERSION);
133 return EXIT_SUCCESS;
134 }
135
136 po::notify(args);
137 auto const has_input = args.count("input") != 0;
138 auto const has_resume = args.count("resume") != 0;
139 auto const explicitly_supplied = [&args](char const* option) {
140 auto const value = args.find(option);
141 return value != args.end() && !value->second.defaulted();
142 };
143 if (has_input && has_resume)
144 {
145 throw invalid_argument("--input and --resume are mutually exclusive.");
146 }
147 if ((has_input || has_resume) &&
148 (args.count("spherical") != 0 || args.count("toroidal") != 0 ||
149 args.count("simplices") != 0 || args.count("timeslices") != 0 ||
150 explicitly_supplied("dimensions") || explicitly_supplied("init") ||
151 explicitly_supplied("foliate")))
152 {
153 throw invalid_argument(fmt::format(
154 "{} cannot be combined with topology or triangulation-construction "
155 "options.",
156 has_resume ? "--resume" : "--input"));
157 }
158 if (has_resume &&
159 (args.count("seed") != 0 || explicitly_supplied("threads") ||
160 args.count("alpha") != 0 || args.count("k") != 0 ||
161 args.count("lambda") != 0 || explicitly_supplied("checkpoint")))
162 {
163 throw invalid_argument(
164 "--resume restores seed, threads, action parameters, and checkpoint "
165 "cadence from the saved run.");
166 }
167 if (!has_resume && (args.count("alpha") == 0 || args.count("k") == 0 ||
168 args.count("lambda") == 0))
169 {
170 throw invalid_argument("Alpha, K, and Lambda must be specified.");
171 }
172 if (!has_input && !has_resume && !args.count("simplices"))
173 {
174 throw invalid_argument("Number of simplices not specified.");
175 }
176 if (!has_input && !has_resume && !args.count("timeslices"))
177 {
178 throw invalid_argument("Number of timeslices not specified.");
179 }
180
181 using Initial_artifact =
183 using Resume_artifact = utilities::Checkpoint_artifact<Delaunay_t<3>>;
184 std::optional<Initial_artifact> initial_artifact;
185 std::optional<Resume_artifact> resume_artifact;
186 if (has_input)
187 {
188 initial_artifact.emplace(
190 }
191 if (has_resume)
192 {
193 resume_artifact.emplace(
195 }
196
197 auto root_random =
198 resume_artifact
199 ? cdt::Random{resume_artifact->metadata.seed}
200 : (args.count("seed") != 0 ? cdt::Random{seed} : cdt::Random{});
201 auto const effective_threads = [&] {
202 if (!resume_artifact) { return threads; }
203 auto const saved = *resume_artifact->metadata.max_threads;
204 if (!std::in_range<long long>(saved))
205 {
206 throw out_of_range("Saved thread count exceeds the supported range.");
207 }
208 return static_cast<long long>(saved);
209 }();
210 auto const triangulation_config = [&] {
211 if (initial_artifact || resume_artifact)
212 {
213 auto const& metadata = initial_artifact ? initial_artifact->metadata
214 : resume_artifact->metadata;
216 metadata.topology == Topology::SPHERICAL,
217 metadata.topology == Topology::TOROIDAL, metadata.desired_simplices,
218 metadata.desired_timeslices, metadata.dimension,
219 metadata.initial_radius, metadata.foliation_spacing,
220 root_random.seed(), effective_threads);
221 }
223 args.count("spherical") != 0, args.count("toroidal") != 0, simplices,
224 timeslices, dimensions, initial_radius, foliation_spacing,
225 root_random.seed(), effective_threads);
226 }();
227 auto const effective_alpha =
228 resume_artifact ? *resume_artifact->metadata.alpha : alpha;
229 auto const effective_k = resume_artifact ? *resume_artifact->metadata.k : k;
230 auto const effective_lambda =
231 resume_artifact ? *resume_artifact->metadata.lambda : lambda;
232 auto const completed_passes = resume_artifact
233 ? *resume_artifact->metadata.completed_passes
234 : Int_precision{};
235 auto const target_passes = [&] {
236 if (!resume_artifact) { return passes; }
237 if (explicitly_supplied("passes")) { return passes; }
238 return static_cast<long long>(*resume_artifact->metadata.configured_passes);
239 }();
240 if (resume_artifact && (!std::in_range<Int_precision>(target_passes) ||
241 target_passes < completed_passes))
242 {
243 throw invalid_argument(
244 "Resume target passes must be at least the completed checkpoint pass.");
245 }
246 auto const passes_to_execute =
247 target_passes - static_cast<long long>(completed_passes);
248 auto const effective_checkpoint =
249 resume_artifact ? static_cast<long long>(
250 *resume_artifact->metadata.checkpoint_interval)
251 : checkpoint;
252 auto const config = runtime_config::make_simulation(
253 triangulation_config, effective_alpha, effective_k, effective_lambda,
254 resume_artifact && passes_to_execute == 0 ? 1 : passes_to_execute,
255 effective_checkpoint, !args.count("no-output"));
256#if defined(CDT_ENABLE_PARALLEL_TRIANGULATION) && \
257 CDT_ENABLE_PARALLEL_TRIANGULATION
258 [[maybe_unused]] oneapi::tbb::global_control thread_limit{
259 oneapi::tbb::global_control::max_allowed_parallelism,
260 config.triangulation().threads()};
261#endif
262 auto transition_random =
263 resume_artifact ? cdt::Random::from_serialized_state(
264 resume_artifact->metadata.seed,
265 resume_artifact->metadata.transition_stream,
266 *resume_artifact->metadata.transition_random_state)
267 : root_random.split(cdt::random_streams::transitions);
268
269 // Display job parameters
270 fmt::print("Topology is {}\n",
271 utilities::topology_to_str(config.triangulation().topology()));
272 fmt::print("Dimensionality: {}+{}\n", config.triangulation().dimensions() - 1,
273 1);
274 fmt::print("Initial radius: {}\n", config.triangulation().initial_radius());
275 fmt::print("Foliation spacing: {}\n",
276 config.triangulation().foliation_spacing());
277 fmt::print("Number of desired simplices: {}\n",
278 config.triangulation().simplices());
279 fmt::print("Number of desired timeslices: {}\n",
280 config.triangulation().timeslices());
281 fmt::print("Number of passes to execute: {}\n", passes_to_execute);
282 fmt::print("Checkpoint every {} passes.\n", config.checkpoint());
283 fmt::print("Effective random seed: {}\n", config.triangulation().seed());
284 fmt::print("Maximum Delaunay threads: {}\n",
285 config.triangulation().threads());
286 if (initial_artifact)
287 {
288 fmt::print("Input initial triangulation: {}\n", input_path);
289 fmt::print("Input initialization seed: {}\n",
290 initial_artifact->metadata.seed);
291 fmt::print("Input topology fingerprint: {:016x}\n",
292 *initial_artifact->metadata.topology_fingerprint);
293 }
294 if (resume_artifact)
295 {
296 fmt::print("Resuming checkpoint: {}\n", resume_path);
297 fmt::print("Completed checkpoint passes: {}\n", completed_passes);
298 fmt::print("Target total passes: {}\n", target_passes);
299 fmt::print("Checkpoint transition count: {}\n",
300 *resume_artifact->metadata.transition_count);
301 }
302 fmt::print("=== Parameters ===\n");
303 fmt::print("Alpha: {}\n", config.alpha());
304 fmt::print("K: {}\n", config.k());
305 fmt::print("Lambda: {}\n", config.lambda());
306
307 // Start running time
308 Timer timer;
309 timer.start();
310 fmt::print("cdt started at {}\n", utilities::current_date_time());
311
312 // Load an exact checkpoint, load an initial state, or generate a fresh state.
313 auto universe = [&]() -> manifolds::Manifold_3 {
314 if (initial_artifact || resume_artifact)
315 {
316 auto const& metadata = initial_artifact ? initial_artifact->metadata
317 : resume_artifact->metadata;
318 auto triangulation = initial_artifact
319 ? std::move(initial_artifact->triangulation)
320 : std::move(resume_artifact->triangulation);
321 auto manifold = manifolds::Manifold_3{
323 std::move(triangulation), metadata.initial_radius,
324 metadata.foliation_spacing}
325 };
326 if (!manifold.is_correct_with_diagnostics())
327 {
328 throw invalid_argument(
329 "Input triangulation does not satisfy the CDT manifold contract.");
330 }
331 return manifold;
332 }
333 auto initialization_random =
334 root_random.split(cdt::random_streams::initialization);
336 config.triangulation().simplices(), config.triangulation().timeslices(),
337 initialization_random, config.triangulation().initial_radius(),
338 config.triangulation().foliation_spacing()};
339 }();
340
341 auto reproducibility = resume_artifact
342 ? resume_artifact->metadata
344 universe, config.triangulation().seed(),
346 reproducibility.artifact = utilities::ArtifactKind::FINAL_TRIANGULATION;
347 reproducibility.desired_simplices = config.triangulation().simplices();
348 reproducibility.desired_timeslices = config.triangulation().timeslices();
349 reproducibility.alpha = config.alpha();
350 reproducibility.k = config.k();
351 reproducibility.lambda = config.lambda();
352 reproducibility.configured_passes = static_cast<Int_precision>(target_passes);
353 reproducibility.checkpoint_interval = config.checkpoint();
354 reproducibility.max_threads = config.triangulation().threads();
355 if (!resume_artifact)
356 {
357 reproducibility.input_artifact =
359 reproducibility.input_seed = initial_artifact
360 ? initial_artifact->metadata.seed
361 : config.triangulation().seed();
362 reproducibility.input_initialization_stream =
363 initial_artifact ? initial_artifact->metadata.initialization_stream
365 reproducibility.input_placement_fingerprint =
366 initial_artifact ? initial_artifact->metadata.placement_fingerprint
367 : reproducibility.placement_fingerprint;
368 reproducibility.input_topology_fingerprint =
369 initial_artifact ? initial_artifact->metadata.topology_fingerprint
370 : reproducibility.topology_fingerprint;
371 }
372
373 // Look at triangulation
374 universe.print();
375 universe.print_details();
376 universe.print_volume_per_timeslice();
377
378 if (resume_artifact && passes_to_execute == 0)
379 {
380 fmt::print(
381 "Checkpoint already reached the target pass; no transitions remain.\n");
382 if (config.write_files())
383 {
384 reproducibility.transition_random_state.reset();
385 utilities::write_file(universe, reproducibility);
386 }
387 return EXIT_SUCCESS;
388 }
389
390 // Initialize the Metropolis algorithm with complete run provenance.
391 Metropolis_3 run(config.alpha(), config.k(), config.lambda(), config.passes(),
392 config.checkpoint(), config.write_files(),
393 std::move(transition_random), reproducibility,
394 completed_passes);
395
396 // The main work of the program
397 auto const result = run(universe);
398
399 // Do we have enough timeslices?
400 if (auto max_timevalue = result.max_time();
401 max_timevalue < config.triangulation().timeslices())
402 {
403 fmt::print("You wanted {} timeslices, but only got {}.\n",
404 config.triangulation().timeslices(), max_timevalue);
405 }
406
407 if (!result.is_valid()) { throw runtime_error("Result is invalid!\n"); }
408
409 // Print results
410 timer.stop(); // End running time counter
411 fmt::print("=== Run Results ===\n");
412 fmt::print("Running time is {} seconds.\n", timer.time());
413 result.print();
414 result.print_details();
415 result.print_volume_per_timeslice();
416
417 // Write results to file
418 if (config.write_files())
419 {
421 result, run.reproducibility_metadata(
423 static_cast<Int_precision>(target_passes)));
424 }
425
426 return EXIT_SUCCESS;
427}
428catch (domain_error const& DomainError)
429{
430 spdlog::critical("{}\n", DomainError.what());
431 spdlog::critical("Triangle inequalities violated ... Exiting.\n");
432 return EXIT_FAILURE;
433}
434catch (invalid_argument const& InvalidArgument)
435{
436 spdlog::critical("{}\n", InvalidArgument.what());
437 spdlog::critical("Invalid parameter ... Exiting.\n");
438 return EXIT_FAILURE;
439}
440catch (logic_error const& LogicError)
441{
442 spdlog::critical("{}\n", LogicError.what());
443 spdlog::critical("Simulation startup failed ... Exiting.\n");
444 return EXIT_FAILURE;
445}
446catch (runtime_error const& RuntimeError)
447{
448 spdlog::critical("{}\n", RuntimeError.what());
449 return EXIT_FAILURE;
450}
451catch (...)
452{
453 spdlog::critical("Something went wrong ... Exiting.\n");
454 return EXIT_FAILURE;
455}
Manifold< 3 > Manifold_3
Three-dimensional spherical CDT manifold.
Definition Manifold.hpp:373
Perform Metropolis-Hastings algorithm on Delaunay Triangulations.
constexpr RandomStream initialization
Stream reserved for initial triangulation generation.
Definition Random.hpp:120
constexpr RandomStream transitions
Stream reserved for stochastic state transitions.
Definition Random.hpp:122
Validated runtime configuration for CDT++ command-line programs.
auto make_simulation(Triangulation const &triangulation, long double const alpha, long double const k, long double const lambda, long long const passes, long long const checkpoint, bool const write_files) -> Simulation
Validate the complete simulation configuration.
auto make_triangulation(bool const spherical, bool const toroidal, long long const simplices, long long const timeslices, long long const dimensions, double const initial_radius, double const foliation_spacing, cdt::RandomSeed const seed=cdt::RandomSeed{}, long long const threads=1) -> Triangulation
Validate raw triangulation options and narrow them into project types.
auto current_date_time(std::chrono::system_clock::time_point const timestamp=std::chrono::system_clock::now())
Return current date and time.
auto read_checkpoint(std::filesystem::path const &filename) -> Triangulation_artifact< TriangulationType >
Read a checkpoint that can continue the identical Markov chain.
@ FINAL_TRIANGULATION
Final state after the configured move run.
@ INITIAL_TRIANGULATION
Initial state before stochastic transitions.
Triangulation_artifact< TriangulationType > Checkpoint_artifact
A validated resumable checkpoint and its complete run state.
auto read_initial_triangulation(std::filesystem::path const &filename) -> Initial_triangulation_artifact< TriangulationType >
Read a manifested initial triangulation for a new CDT run.
void write_file(std::filesystem::path const &filename, TriangulationType const &triangulation)
Write triangulation to file.
auto make_reproducibility_metadata(ManifoldType const &manifold, cdt::RandomSeed const seed, ArtifactKind const artifact) -> Reproducibility_metadata
Build provenance from a canonical manifold state.
auto topology_to_str(Topology const &t_topology) -> std::string
Convert a topology to a string using it's << operator.
Triangulation_artifact< TriangulationType > Initial_triangulation_artifact
A validated initial triangulation and its initialization provenance.
auto main(int const argc, char *const argv[]) -> int try
The main path of the CDT++ program.
Definition cdt.cpp:70
A run-owned PCG engine with a recorded seed and stream identifier.
Definition Random.hpp:137
static auto from_serialized_state(RandomSeed const seed, RandomStream const stream, std::string_view const state) -> Random
Restore an exact PCG continuation point.
Definition Random.hpp:218
FoliatedTriangulation< 3 > FoliatedTriangulation_3
Three-dimensional foliated Delaunay triangulation.
clang-15 does not support std::format
@ TOROIDAL
Reserved for toroidal slices; construction is unsupported.
Definition Utilities.hpp:75
@ SPHERICAL
Supported spherical spatial slices.
Definition Utilities.hpp:76
typename detail::TriangulationTraits< dimension >::Delaunay Delaunay_t
Delaunay triangulation type for dimension spatial dimensions.
std::int32_t Int_precision
Definition Settings.hpp:30
MoveStrategy< MoveStrategyKind::METROPOLIS, manifolds::Manifold_3 > Metropolis_3
Metropolis-Hastings move strategy for the supported 3D manifold.