70auto main(
int const argc,
char*
const argv[]) ->
int
73 std::string
const intro{USAGE};
75 long long simplices{};
76 long long timeslices{};
77 long long dimensions{};
78 double initial_radius{};
79 double foliation_spacing{};
84 long long checkpoint{};
87 std::string input_path;
88 std::string resume_path;
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>(×lices),
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),
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");
121 po::variables_map args;
122 po::store(po::parse_command_line(argc, argv, description), args);
124 if (args.count(
"help"))
126 fmt::print(
"{}\n", fmt::streamed(description));
130 if (args.count(
"version"))
132 fmt::print(
"CDT++ version {}\n", cdt::VERSION);
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();
143 if (has_input && has_resume)
145 throw invalid_argument(
"--input and --resume are mutually exclusive.");
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")))
153 throw invalid_argument(fmt::format(
154 "{} cannot be combined with topology or triangulation-construction "
156 has_resume ?
"--resume" :
"--input"));
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")))
163 throw invalid_argument(
164 "--resume restores seed, threads, action parameters, and checkpoint "
165 "cadence from the saved run.");
167 if (!has_resume && (args.count(
"alpha") == 0 || args.count(
"k") == 0 ||
168 args.count(
"lambda") == 0))
170 throw invalid_argument(
"Alpha, K, and Lambda must be specified.");
172 if (!has_input && !has_resume && !args.count(
"simplices"))
174 throw invalid_argument(
"Number of simplices not specified.");
176 if (!has_input && !has_resume && !args.count(
"timeslices"))
178 throw invalid_argument(
"Number of timeslices not specified.");
181 using Initial_artifact =
184 std::optional<Initial_artifact> initial_artifact;
185 std::optional<Resume_artifact> resume_artifact;
188 initial_artifact.emplace(
193 resume_artifact.emplace(
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))
206 throw out_of_range(
"Saved thread count exceeds the supported range.");
208 return static_cast<long long>(saved);
210 auto const triangulation_config = [&] {
211 if (initial_artifact || resume_artifact)
213 auto const& metadata = initial_artifact ? initial_artifact->metadata
214 : resume_artifact->metadata;
218 metadata.desired_timeslices, metadata.dimension,
219 metadata.initial_radius, metadata.foliation_spacing,
220 root_random.seed(), effective_threads);
223 args.count(
"spherical") != 0, args.count(
"toroidal") != 0, simplices,
224 timeslices, dimensions, initial_radius, foliation_spacing,
225 root_random.seed(), effective_threads);
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
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);
240 if (resume_artifact && (!std::in_range<Int_precision>(target_passes) ||
241 target_passes < completed_passes))
243 throw invalid_argument(
244 "Resume target passes must be at least the completed checkpoint pass.");
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)
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()};
262 auto transition_random =
264 resume_artifact->metadata.seed,
265 resume_artifact->metadata.transition_stream,
266 *resume_artifact->metadata.transition_random_state)
270 fmt::print(
"Topology is {}\n",
272 fmt::print(
"Dimensionality: {}+{}\n", config.triangulation().dimensions() - 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)
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);
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);
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());
314 if (initial_artifact || resume_artifact)
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);
323 std::move(triangulation), metadata.initial_radius,
324 metadata.foliation_spacing}
326 if (!manifold.is_correct_with_diagnostics())
328 throw invalid_argument(
329 "Input triangulation does not satisfy the CDT manifold contract.");
333 auto initialization_random =
336 config.triangulation().simplices(), config.triangulation().timeslices(),
337 initialization_random, config.triangulation().initial_radius(),
338 config.triangulation().foliation_spacing()};
341 auto reproducibility = resume_artifact
342 ? resume_artifact->metadata
344 universe, config.triangulation().seed(),
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)
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;
375 universe.print_details();
376 universe.print_volume_per_timeslice();
378 if (resume_artifact && passes_to_execute == 0)
381 "Checkpoint already reached the target pass; no transitions remain.\n");
382 if (config.write_files())
384 reproducibility.transition_random_state.reset();
391 Metropolis_3 run(config.alpha(), config.k(), config.lambda(), config.passes(),
392 config.checkpoint(), config.write_files(),
393 std::move(transition_random), reproducibility,
397 auto const result = run(universe);
400 if (
auto max_timevalue = result.max_time();
401 max_timevalue < config.triangulation().timeslices())
403 fmt::print(
"You wanted {} timeslices, but only got {}.\n",
404 config.triangulation().timeslices(), max_timevalue);
407 if (!result.is_valid()) {
throw runtime_error(
"Result is invalid!\n"); }
411 fmt::print(
"=== Run Results ===\n");
412 fmt::print(
"Running time is {} seconds.\n", timer.time());
414 result.print_details();
415 result.print_volume_per_timeslice();
418 if (config.write_files())
421 result, run.reproducibility_metadata(