CDT++ 1.0.0-rc3
Causal Dynamical Triangulations in C++
Loading...
Searching...
No Matches
C++ API quickstart

This is the canonical end-to-end example for the supported C++23 API. Its source is built as CDT_cpp_api_quickstart in every normal CMake build and run by CTest when ENABLE_TESTING is enabled, so the documentation cannot drift silently away from the public headers.

Workflow

The example validates raw configuration values before constructing a manifold, splits a fixed root seed into named initialization and transition streams, asks the strategy to sample and resolve ten proposals through the public one-transition Metropolis API. Every proposal prints its move kind, whether candidate construction and validation succeeded, and whether the Metropolis-Hastings criterion accepted and committed it. The existing strategy summary then reports the aggregate proposal and candidate counters.

“Successful” and “accepted” describe different stages. A successful proposal produced a valid candidate; it may still be rejected by Metropolis-Hastings. An accepted proposal is necessarily successful and replaces the canonical state. The one-argument attempt_transition() overload returns both facts in a typed transition report, so callers do not need to infer an individual outcome from aggregate counters.

Finally, the example writes the resulting triangulation with a provenance sidecar. Reading the payload back verifies the sidecar checksum, recorded geometry, and topology fingerprints before the example compares the finite simplex counts. Because the one-transition API does not complete a configured move pass, the artifact records zero completed passes and a transition-trace count of ten. Its optional configured_attempts=10 field identifies the explicit one-step execution plan independently of the pass cadence retained by the strategy.

After Pachner evolution, the canonical state is required to preserve the CGAL triangulation data structure and CDT causal manifold invariants. A bistellar flip is not required to preserve CGAL's geometric Delaunay predicate, so the round-trip check deliberately tests tds().is_valid() rather than Delaunay_t<3>::is_valid().

The strategy owns the named transition stream and draws each move kind, acceptance trial, and candidate site in sequence. This preserves single ownership of the stream and makes the complete sequence replayable from the recorded root seed.

/*******************************************************************************
Causal Dynamical Triangulations in C++ using CGAL
Copyright © 2026 Adam Getchell
******************************************************************************/
#include <fmt/base.h>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <string_view>
#include "Manifold.hpp"
#include "Metropolis.hpp"
#include "Move_tracker.hpp"
#include "Random.hpp"
#include "Utilities.hpp"
namespace
{
inline constexpr std::size_t QUICKSTART_TRANSITIONS = 10;
[[nodiscard]] constexpr auto yes_no(bool const value) noexcept
-> std::string_view
{ return value ? "yes" : "no"; }
[[nodiscard]] auto same_finite_counts(
cdt::Delaunay_t<3> const& restored,
cdt::manifolds::Manifold_3 const& expected) -> bool
{
return restored.number_of_vertices() ==
static_cast<std::size_t>(expected.N0()) &&
restored.number_of_finite_edges() ==
static_cast<std::size_t>(expected.N1()) &&
restored.number_of_finite_facets() ==
static_cast<std::size_t>(expected.N2()) &&
restored.number_of_finite_cells() ==
static_cast<std::size_t>(expected.N3());
}
} // namespace
auto main(int argc, char* argv[]) -> int
try
{
if (argc > 2)
{
fmt::print(stderr, "usage: {} [output.off]\n", argv[0]);
return 2;
}
auto const output = argc == 2 ? std::filesystem::path{argv[1]}
: std::filesystem::path{"cdt-quickstart.off"};
auto const triangulation_config = cdt::runtime_config::make_triangulation(
true, false, 64, 3, 3, 1.0, 1.0, cdt::RandomSeed{92}, 1);
auto const simulation_config = cdt::runtime_config::make_simulation(
triangulation_config, 0.6L, 1.1L, 0.1L, 1, 1, false);
cdt::Random root_random{triangulation_config.seed()};
auto initialization_random =
auto const initialization_stream = initialization_random.stream();
triangulation_config.simplices(), triangulation_config.timeslices(),
initialization_random, triangulation_config.initial_radius(),
triangulation_config.foliation_spacing()};
{
fmt::print(stderr, "initial triangulation failed validation\n");
return 1;
}
initial, triangulation_config.seed(),
provenance.initialization_stream = initialization_stream;
provenance.desired_simplices = triangulation_config.simplices();
provenance.desired_timeslices = triangulation_config.timeslices();
provenance.configured_attempts =
static_cast<cdt::Int_precision>(QUICKSTART_TRANSITIONS);
provenance.max_threads =
static_cast<std::uint64_t>(triangulation_config.threads());
simulation_config.alpha(),
simulation_config.k(),
simulation_config.lambda(),
simulation_config.passes(),
simulation_config.checkpoint(),
simulation_config.write_files(),
provenance};
auto evolved = initial;
fmt::print("=== Ten explicit Metropolis proposals ===\n");
for (std::size_t index = 0; index < QUICKSTART_TRANSITIONS; ++index)
{
auto const transition = strategy.attempt_transition(evolved);
if (transition.accepted() && !transition.successful())
{
fmt::print(stderr, "accepted transition lacked a valid candidate\n");
return 1;
}
fmt::print("proposal {}: move {}, accepted={}, successful={}\n", index + 1,
transition.move(), yes_no(transition.accepted()),
yes_no(transition.successful()));
}
strategy.print_results();
if (!initial.is_correct_with_diagnostics() ||
!evolved.is_correct_with_diagnostics())
{
fmt::print(stderr, "Metropolis transitions failed manifold validation\n");
return 1;
}
if (strategy.proposed().total() !=
static_cast<cdt::Int_precision>(QUICKSTART_TRANSITIONS) ||
strategy.accepted().total() + strategy.rejected().total() !=
static_cast<cdt::Int_precision>(QUICKSTART_TRANSITIONS) ||
strategy.succeeded().total() + strategy.failed().total() !=
static_cast<cdt::Int_precision>(QUICKSTART_TRANSITIONS))
{
fmt::print(stderr, "Metropolis summary counters are inconsistent\n");
return 1;
}
auto const final_metadata = strategy.reproducibility_metadata(
cdt::utilities::write_file(output, evolved.delaunay_snapshot(),
final_metadata);
auto const restored = cdt::utilities::read_file<cdt::Delaunay_t<3>>(output);
// Pachner moves preserve the TDS and CDT invariants, not necessarily CGAL's
// geometric Delaunay predicate.
if (!restored.tds().is_valid() || !same_finite_counts(restored, evolved))
{
fmt::print(
stderr,
"persisted triangulation failed round-trip checks: tds_valid={}, "
"counts=({},{},{},{}) expected=({},{},{},{})\n",
restored.tds().is_valid(), restored.number_of_vertices(),
restored.number_of_finite_edges(), restored.number_of_finite_facets(),
restored.number_of_finite_cells(), evolved.N0(), evolved.N1(),
evolved.N2(), evolved.N3());
return 1;
}
fmt::print("wrote and verified {} with {} finite simplices\n",
output.string(), evolved.N3());
return 0;
}
catch (std::exception const& error)
{
fmt::print(stderr, "quickstart failed: {}\n", error.what());
return 1;
}
Data structures for manifolds.
Manifold< 3 > Manifold_3
Three-dimensional spherical CDT manifold.
Definition Manifold.hpp:373
Perform Metropolis-Hastings algorithm on Delaunay Triangulations.
Track ergodic moves.
Run-owned random-number generation and reproducible stream splitting.
constexpr RandomStream initialization
Stream reserved for initial triangulation generation.
Definition Random.hpp:96
constexpr RandomStream transitions
Stream reserved for stochastic state transitions.
Definition Random.hpp:98
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.
Utility functions.
@ FINAL_TRIANGULATION
Final state after the configured move run.
@ INITIAL_TRIANGULATION
Initial state before stochastic transitions.
Definition Utilities.hpp:98
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 read_file(std::filesystem::path const &filename) -> TriangulationType
Read triangulation from file.
A run-owned PCG engine with a recorded seed and stream identifier.
Definition Random.hpp:113
auto split(RandomStream const stream) const -> Random
Create a fresh reproducible stream from the same root seed.
Definition Random.hpp:170
Root entropy value used to reproduce a random run.
Definition Random.hpp:25
auto is_correct_with_diagnostics() const -> bool
Definition Manifold.hpp:228
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.

Build and run

From the repository root:

cmake --preset reference
cmake --build --preset reference --target CDT_cpp_api_quickstart
./out/build/reference/examples/CDT_cpp_api_quickstart /tmp/cdt-quickstart.off
ctest --test-dir out/build/reference -R '^cpp-api-quickstart$' --output-on-failure

The program accepts zero or one argument. Without an argument it writes cdt-quickstart.off and cdt-quickstart.off.meta in the current directory. With an argument, the sidecar is written next to that payload by appending .meta to its name.

Failure and ownership behavior

Configuration, construction, move execution, and persistence failures are reported on standard error and produce a nonzero exit status. A caller-owned random stream may already have advanced when a later operation throws; replay therefore starts from the recorded root seed and named stream, not from an engine object retained after failure.

CGAL handles and facet or edge descriptors borrow from the exact triangulation that produced them. Do not use them with a copied triangulation or after an invalidating topology mutation. delaunay_snapshot() instead returns an owning, detached triangulation suitable for persistence or transfer across an ownership boundary. See the multithreaded CGAL contract for the full lifetime and synchronization policy.