17#ifndef INCLUDE_METROPOLIS_HPP_
18#define INCLUDE_METROPOLIS_HPP_
50 template <
typename ManifoldType>
51 requires(ManifoldType::dimension == 3)
52 class MoveStrategy<MoveStrategyKind::METROPOLIS, ManifoldType>
55 using CommandResults = detail::MoveCommandResults<ManifoldType>;
78 using PassResult = detail::MovePassResult<ManifoldType, RunStatistics>;
87 bool m_write_files{
true};
97 CommandResults m_command_results;
100 RunStatistics m_run_statistics;
109 bool m_resume_pending{};
111 [[nodiscard]]
static auto to_counts(Counter
const& counter)
115 for (std::size_t index = 0; index < counts.size(); ++index)
117 counts[index] = counter[
static_cast<gsl::index
>(index)];
122 [[nodiscard]]
static auto from_counts(
126 for (std::size_t index = 0; index < counts.size(); ++index)
128 counter[
static_cast<gsl::index
>(index)] = counts[index];
138 throw std::invalid_argument(
139 "Checkpoint resume requires cumulative transition statistics.");
142 m_command_results.attempted = from_counts(saved.attempted);
143 m_command_results.succeeded = from_counts(saved.succeeded);
144 m_command_results.failed = from_counts(saved.failed);
145 m_run_statistics.proposed = from_counts(saved.proposed);
146 m_run_statistics.accepted = from_counts(saved.accepted);
147 m_run_statistics.rejected = from_counts(saved.rejected);
152 static void record_transition(
156 auto const append = [&statistics](std::uint8_t
const value) {
157 statistics.transition_trace ^= value;
158 statistics.transition_trace *= 1099511628211ULL;
160 append(
static_cast<std::uint8_t
>(move));
161 append(
static_cast<std::uint8_t
>(outcome));
162 ++statistics.transition_count;
166 MoveStrategy() =
delete;
184 bool const write_files =
true)
185 : MoveStrategy{
alpha,
214 long double const alpha,
long double const k,
long double const lambda,
217 std::optional<utilities::Reproducibility_metadata> reproducibility =
220 : m_parameters{s3_action::make_physical_parameters(
alpha,
k,
lambda)}
223 , m_write_files{write_files}
224 , m_generator{std::move(random)}
225 , m_reproducibility{reproducibility.value_or(
226 utilities::Reproducibility_metadata{
231 .configured_passes =
passes,
233 , m_completed_passes{completed_passes}
235 if (m_completed_passes < 0)
237 throw std::invalid_argument(
"Completed passes cannot be negative.");
239 m_reproducibility.alpha = m_parameters.alpha();
240 m_reproducibility.k = m_parameters.k();
241 m_reproducibility.lambda = m_parameters.lambda();
242 auto const total_passes =
static_cast<std::int64_t
>(m_completed_passes) +
243 static_cast<std::int64_t
>(m_cadence.passes());
244 if (!std::in_range<Int_precision>(total_passes))
246 throw std::out_of_range(
247 "Total pass count exceeds the supported range.");
249 if (m_completed_passes > 0 || m_reproducibility.transition_random_state)
251 if (!m_reproducibility.configured_passes ||
252 *m_reproducibility.configured_passes != total_passes ||
253 !m_reproducibility.transition_random_state)
255 throw std::invalid_argument(
256 "Checkpoint resume state does not match its pass range.");
258 if (m_reproducibility.seed != m_generator.seed() ||
259 m_reproducibility.transition_stream != m_generator.stream() ||
260 *m_reproducibility.transition_random_state !=
261 m_generator.serialized_state())
263 throw std::invalid_argument(
264 "Checkpoint resume generator does not match its recorded random state.");
266 restore_statistics(m_reproducibility);
267 m_resume_pending =
true;
269 m_reproducibility.
seed = m_generator.
seed();
307 [[nodiscard]]
auto alpha() const noexcept {
return m_parameters.alpha(); }
310 [[nodiscard]]
auto k() const noexcept {
return m_parameters.k(); }
313 [[nodiscard]]
auto lambda() const noexcept {
return m_parameters.lambda(); }
316 [[nodiscard]]
auto passes() const noexcept {
return m_cadence.passes(); }
320 {
return m_cadence.checkpoint(); }
324 {
return m_checkpoint_events; }
327 [[nodiscard]]
auto writes_files() const noexcept {
return m_write_files; }
330 [[nodiscard]]
auto seed() const noexcept {
return m_generator.seed(); }
333 [[nodiscard]]
auto stream() const noexcept {
return m_generator.stream(); }
338 {
return m_run_statistics.transition_trace; }
342 {
return m_run_statistics.transition_count; }
355 return make_reproducibility_metadata(manifold, artifact, completed_passes,
356 m_command_results, m_run_statistics);
360 [[nodiscard]]
auto proposed() const noexcept -> Counter const&
361 {
return m_run_statistics.proposed; }
364 [[nodiscard]]
auto accepted() const noexcept -> Counter const&
365 {
return m_run_statistics.accepted; }
368 [[nodiscard]]
auto rejected() const noexcept -> Counter const&
369 {
return m_run_statistics.rejected; }
372 [[nodiscard]]
auto attempted() const noexcept -> Counter const&
373 {
return m_command_results.attempted; }
376 [[nodiscard]]
auto succeeded() const noexcept -> Counter const&
377 {
return m_command_results.succeeded; }
380 [[nodiscard]]
auto failed() const noexcept -> Counter const&
381 {
return m_command_results.failed; }
385 ->
Geometry<ManifoldType::dimension> const&
386 {
return m_run_statistics.geometry; }
392 -> std::optional<move_tracker::MoveType>
397 case TWO_THREE:
return THREE_TWO;
398 case THREE_TWO:
return TWO_THREE;
399 case TWO_SIX:
return SIX_TWO;
400 case SIX_TWO:
return TWO_SIX;
401 case FOUR_FOUR:
return FOUR_FOUR;
416 case TWO_THREE:
return geometry.N3_22;
417 case THREE_TWO:
return geometry.N1_TL;
418 case TWO_SIX:
return geometry.N3_13;
420 case FOUR_FOUR:
return geometry.N1_SL;
439 auto const move_count =
465 throw std::invalid_argument{
"Cannot reverse an unknown move type."};
468 if (mpfr_zero_p(forward.fr()) != 0 || mpfr_zero_p(reverse.fr()) != 0)
470 throw std::logic_error(
471 "A successful reversible proposal must have nonzero forward and reverse probabilities.");
488 current.N1_TL, current.N3_31_13, current.N3_22, m_parameters);
510 return mpfr_cmp(ratio.fr(), one.fr()) < 0 ? ratio : one;
514 [[nodiscard]]
auto propose_candidate(ManifoldType
const& current,
532 return std::unexpected{
533 ergodic_moves::MoveError{
534 .category = ergodic_moves::MoveFailure::UNKNOWN_MOVE,
535 .requested_move = move}
542 CommandResults
const& command_results,
543 RunStatistics
const& statistics)
const
546 auto metadata = m_reproducibility;
552 .proposed = to_counts(statistics.proposed),
553 .accepted = to_counts(statistics.accepted),
554 .rejected = to_counts(statistics.rejected),
555 .attempted = to_counts(command_results.attempted),
556 .succeeded = to_counts(command_results.succeeded),
557 .failed = to_counts(command_results.failed)};
580 auto resolve_transition(ManifoldType& current,
581 CommandResults& command_results,
582 RunStatistics& statistics,
584 long double const trial_value)
587 if (!std::isfinite(trial_value) || trial_value < 0.0L ||
590 throw std::invalid_argument(
"MH trial value must lie in [0, 1].");
593 if (!reverse_move(move))
597 .requested_move = move});
600 statistics.geometry = current.geometry();
601 ++statistics.proposed[move];
602 ++command_results.attempted[move];
604 auto candidate = propose_candidate(current, move);
607 ++command_results.failed[move];
608 ++statistics.rejected[move];
610 record_transition(statistics, move, outcome);
615 ++command_results.failed[move];
616 ++statistics.rejected[move];
617 record_transition(statistics, move,
622 ++command_results.succeeded[move];
623 auto const probability = acceptance_probability(
624 statistics.geometry, candidate->geometry(), move);
625 if (mpfr_cmp_ld(probability.fr(), trial_value) >= 0)
627 swap(*candidate, current);
628 statistics.geometry = current.geometry();
629 ++statistics.accepted[move];
630 record_transition(statistics, move,
635 ++statistics.rejected[move];
636 record_transition(statistics, move,
641 [[nodiscard]]
auto sample_transition(ManifoldType& current,
642 CommandResults& command_results,
643 RunStatistics& statistics)
648 return {move, resolve_transition(current, command_results, statistics,
652 [[nodiscard]]
auto execute_pass(ManifoldType current,
653 RunStatistics statistics,
656 auto command_results = CommandResults{};
657 for (
auto move_attempt =
Int_precision{0}; move_attempt < attempts;
661 sample_transition(current, command_results, statistics));
663 return {.manifold = std::move(current),
664 .command_results = std::move(command_results),
665 .strategy_state = std::move(statistics)};
668 static void print_results(CommandResults
const& command_results,
669 RunStatistics
const& statistics)
671 fmt::print(
"=== Move Results ===\n");
673 "There were {} proposed moves with {} accepted moves and {} rejected "
675 statistics.proposed.total(), statistics.accepted.total(),
676 statistics.rejected.total());
678 "There were {} candidate construction attempts with {} successful "
679 "candidates and {} failed candidates.\n",
680 command_results.attempted.total(), command_results.succeeded.total(),
681 command_results.failed.total());
683 "(2,3) moves: {} proposed ({} accepted and {} rejected); candidate "
684 "construction: {} attempted ({} succeeded and {} failed).\n",
685 statistics.proposed.two_three_moves(),
686 statistics.accepted.two_three_moves(),
687 statistics.rejected.two_three_moves(),
688 command_results.attempted.two_three_moves(),
689 command_results.succeeded.two_three_moves(),
690 command_results.failed.two_three_moves());
693 "(3,2) moves: {} proposed ({} accepted and {} rejected); candidate "
694 "construction: {} attempted ({} succeeded and {} failed).\n",
695 statistics.proposed.three_two_moves(),
696 statistics.accepted.three_two_moves(),
697 statistics.rejected.three_two_moves(),
698 command_results.attempted.three_two_moves(),
699 command_results.succeeded.three_two_moves(),
700 command_results.failed.three_two_moves());
703 "(2,6) moves: {} proposed ({} accepted and {} rejected); candidate "
704 "construction: {} attempted ({} succeeded and {} failed).\n",
705 statistics.proposed.two_six_moves(),
706 statistics.accepted.two_six_moves(),
707 statistics.rejected.two_six_moves(),
708 command_results.attempted.two_six_moves(),
709 command_results.succeeded.two_six_moves(),
710 command_results.failed.two_six_moves());
713 "(6,2) moves: {} proposed ({} accepted and {} rejected); candidate "
714 "construction: {} attempted ({} succeeded and {} failed).\n",
715 statistics.proposed.six_two_moves(),
716 statistics.accepted.six_two_moves(),
717 statistics.rejected.six_two_moves(),
718 command_results.attempted.six_two_moves(),
719 command_results.succeeded.six_two_moves(),
720 command_results.failed.six_two_moves());
723 "(4,4) moves: {} proposed ({} accepted and {} rejected); candidate "
724 "construction: {} attempted ({} succeeded and {} failed).\n",
725 statistics.proposed.four_four_moves(),
726 statistics.accepted.four_four_moves(),
727 statistics.rejected.four_four_moves(),
728 command_results.attempted.four_four_moves(),
729 command_results.succeeded.four_four_moves(),
730 command_results.failed.four_four_moves());
747 long double const trial_value)
750 return {move, resolve_transition(current, m_command_results,
751 m_run_statistics, move, trial_value)};
763 {
return sample_transition(current, m_command_results, m_run_statistics); }
768 { m_run_statistics.geometry = manifold.geometry(); }
780 [[nodiscard]]
auto operator()(ManifoldType
const& t_manifold)
787 auto initial_statistics =
788 m_resume_pending ? m_run_statistics : RunStatistics{};
789 initial_statistics.geometry = t_manifold.geometry();
790 auto initial_command_results =
791 m_resume_pending ? m_command_results : CommandResults{};
792 auto const initial_completed_passes =
795 t_manifold, std::move(initial_command_results),
796 std::move(initial_statistics), initial_completed_passes, m_cadence,
797 detail::MoveRunIdentity{.algorithm =
"Metropolis-Hastings",
801 [
this](ManifoldType current, RunStatistics statistics,
803 return execute_pass(std::move(current), std::move(statistics),
806 [](ManifoldType
const&, CommandResults
const& command_results,
807 RunStatistics
const& statistics) {
808 print_results(command_results, statistics);
810 [
this](ManifoldType
const& current,
811 CommandResults
const& command_results,
812 RunStatistics
const& statistics,
815 current, make_reproducibility_metadata(
817 pass_number, command_results, statistics));
820 m_command_results = std::move(result.command_results);
821 m_run_statistics = std::move(result.strategy_state);
822 m_checkpoint_events = result.checkpoint_events;
823 m_completed_passes = initial_completed_passes + m_cadence.passes();
824 m_resume_pending =
false;
825 return std::move(result.manifold);
830 { print_results(m_command_results, m_run_statistics); }
Pachner moves on 2+1 dimensional foliated Delaunay triangulations.
auto propose_44_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one spacelike edge as a (4,4) site.
auto propose_62_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one vertex as a (6,2) site for Metropolis-Hastings.
auto propose_23_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one (2,3) site for Metropolis-Hastings.
auto propose_32_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose one (3,2) site for Metropolis-Hastings.
auto check_move(Manifold const &before, Manifold const &after, move_tracker::MoveType const &move) -> bool
Check tracked move deltas and essential CDT manifold invariants.
auto propose_26_move(Manifold const &t_manifold, Generator &generator) -> Expected
Propose a uniformly selected (2,6) site.
constexpr auto outcome_from(MoveError const error) noexcept -> MoveOutcome
Classify a structured move error for counter accounting.
std::expected< ManifoldType, MoveError > MoveResult
Value returned by a fallible Pachner-move transformation.
MoveOutcome
Typed state used to route proposal and execution accounting.
@ METROPOLIS_ACCEPTED
Metropolis-Hastings accepted the proposal.
@ EXECUTION_FAILED
Mutation failed after proposal preparation.
@ METROPOLIS_REJECTED
Metropolis-Hastings rejected the proposal.
@ UNKNOWN_MOVE
The requested move kind is unsupported.
Shared value-oriented orchestration for ergodic-move strategies.
auto execute_move_run(ManifoldType initial, MoveCommandResults< ManifoldType > initial_command_results, StrategyState initial_strategy_state, Int_precision const completed_passes, MoveRunCadence const cadence, MoveRunIdentity const identity, bool const writes_files, ExecutePass execute_pass, Report report, Checkpoint checkpoint) -> MoveRunResult< ManifoldType, StrategyState >
Execute shared pass, accounting, checkpoint, and report cadence.
Template class for move algorithms (strategies) on manifolds.
MoveType
The types of 3D ergodic moves.
auto generate_random_move_3(Generator &generator) -> MoveType
Generate a uniformly distributed 3D move from caller-owned RNG.
constexpr std::size_t NUMBER_OF_3D_MOVES
Number of supported Pachner move kinds in three dimensions.
auto multiply(Value const &left, Value const &right) -> Value
auto from_integer(long const value) -> Value
auto subtract(Value const &left, Value const &right) -> Value
auto exponential(Value const &value) -> Value
auto divide(Value const &numerator, Value const &denominator) -> Value
CGAL::Gmpfr Value
Owning arbitrary-precision floating-point value used by CDT++.
Run-owned random-number generation and reproducible stream splitting.
constexpr RandomStream transitions
Stream reserved for stochastic state transitions.
Calculate S3 bulk actions on 3D Delaunay Triangulations.
auto s3_bulk_action(Int_precision const n1_tl_count, Int_precision const n3_31_13_count, Int_precision const n3_22_count, PhysicalParameters const ¶meters) -> mpfr_values::Value
Calculates the generalized S3 bulk action in terms of , , , , , and .
#define CDT_PRETTY_FUNCTION
Cross-platform spelling of the current function signature for diagnostics.
void update_reproducibility_state(Reproducibility_metadata &metadata, ManifoldType const &manifold)
Refresh state-dependent provenance after a transition sequence.
ArtifactKind
Persistence artifact represented by a triangulation payload.
@ CHECKPOINT
Intermediate snapshot during a move run.
auto generate_probability(Generator &generator)
Generate a probability.
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.
Positive pass count and checkpoint interval for a move run.
constexpr auto checkpoint() const noexcept
MoveStrategy(long double const alpha, long double const k, long double const lambda, Int_precision const passes, Int_precision const checkpoint, bool const write_files, cdt::Random random, std::optional< utilities::Reproducibility_metadata > reproducibility=std::nullopt, Int_precision const completed_passes=0)
Construct a run from an already selected PCG stream.
auto reproducibility_metadata(ManifoldType const &manifold, utilities::ArtifactKind const artifact, Int_precision const completed_passes) const -> utilities::Reproducibility_metadata
Materialize output provenance for the supplied canonical state.
static constexpr auto proposal_site_count(Geometry< ManifoldType::dimension > const &geometry, move_tracker::MoveType const move) noexcept -> Int_precision
auto failed() const noexcept -> Counter const &
auto rejected() const noexcept -> Counter const &
auto passes() const noexcept
void initialize(ManifoldType const &manifold)
Initialize the cached action geometry from the canonical manifold.
static auto proposal_probability(Geometry< ManifoldType::dimension > const &geometry, move_tracker::MoveType const move) -> mpfr_values::Value
MoveStrategy(long double const alpha, long double const k, long double const lambda, Int_precision const passes, Int_precision const checkpoint, bool const write_files=true)
Metropolis function object constructor.
auto succeeded() const noexcept -> Counter const &
auto checkpoint_events() const noexcept
auto transition_trace() const noexcept
auto geometry() const noexcept -> Geometry< ManifoldType::dimension > const &
auto action_ratio(Geometry< ManifoldType::dimension > const ¤t, Geometry< ManifoldType::dimension > const &proposed) const -> mpfr_values::Value
Calculate the action factor .
auto transition_count() const noexcept
static auto hastings_ratio(Geometry< ManifoldType::dimension > const ¤t, Geometry< ManifoldType::dimension > const &proposed, move_tracker::MoveType const move) -> mpfr_values::Value
Calculate the Hastings reverse-to-forward proposal ratio.
static constexpr auto reverse_move(move_tracker::MoveType const move) noexcept -> std::optional< move_tracker::MoveType >
auto proposed() const noexcept -> Counter const &
auto seed() const noexcept
auto acceptance_probability(Geometry< ManifoldType::dimension > const ¤t, Geometry< ManifoldType::dimension > const &proposed, move_tracker::MoveType const move) const -> mpfr_values::Value
auto attempt_transition(ManifoldType ¤t) -> ergodic_moves::MetropolisTransition
Sample and immediately resolve one Markov transition.
auto stream() const noexcept
auto accepted() const noexcept -> Counter const &
auto lambda() const noexcept
MoveStrategy(long double const alpha, long double const k, long double const lambda, Int_precision const passes, Int_precision const checkpoint, bool const write_files, cdt::RandomSeed const seed)
Construct a replayable run with an explicit RNG seed.
auto operator()(ManifoldType const &t_manifold) -> ManifoldType
Execute a fresh run while continuing the owned random stream.
void print_results() const
Display results of the latest completed invocation.
auto alpha() const noexcept
auto attempt_transition(ManifoldType ¤t, move_tracker::MoveType const move, long double const trial_value) -> ergodic_moves::MetropolisTransition
Resolve one caller-selected Markov transition.
auto writes_files() const noexcept
auto attempted() const noexcept -> Counter const &
auto checkpoint() const noexcept
A run-owned PCG engine with a recorded seed and stream identifier.
auto split(RandomStream const stream) const -> Random
Create a fresh reproducible stream from the same root seed.
auto serialized_state() const -> std::string
Serialize the complete mutable PCG state for exact continuation.
auto seed() const noexcept -> RandomSeed
auto stream() const noexcept -> RandomStream
Root entropy value used to reproduce a random run.
Result of one fully sampled Metropolis-Hastings transition.
The data and methods to track ergodic moves.
Finite physical couplings used to evaluate the Euclidean action.
clang-15 does not support std::format
std::int32_t Int_precision
MoveStrategy< MoveStrategyKind::METROPOLIS, manifolds::Manifold_3 > Metropolis_3
Metropolis-Hastings move strategy for the supported 3D manifold.
Typed error returned by move preparation or private execution.
Cumulative counters needed to preserve observable run state.
std::array< Int_precision, move_tracker::NUMBER_OF_3D_MOVES > Counts
Per-move counts in stable MoveType index order.