2 * @file MHintegrator.tpp
3 * @brief MHMontecarloIntegrator template implementation.
4 * @details Contains inline implementations for Metropolis-Hastings MCMC integration,
5 * including parallel volume estimation, burn-in, thinning, and mean computation.
9// Created by Giacomo Merlo on 15/01/26.
19#include "montecarlo/rng/rng_factory.hpp"
20#include "montecarlo/integrators/MCintegrator.hpp"
21#include "montecarlo/proposals/uniformProposal.hpp"
23namespace mc::integrators {
26 * @brief Construct an MH-based integrator for a domain.
27 * @tparam dim Dimensionality parameter.
28 * @param d Reference to the integration domain.
30template <std::size_t dim>
31MHMontecarloIntegrator<dim>::MHMontecarloIntegrator(const mc::domains::IntegrationDomain<dim>& d)
36 * @brief Configure the MCMC sampler and volume estimation parameters.
37 * @tparam dim Dimensionality parameter.
38 * @param burn_in_ Number of initial MH samples to discard (warmup iterations).
39 * @param thinning_ Thinning factor: keep every k-th sample (k = thinning_).
40 * @param n_samples_volume_ Number of samples for Hit-or-Miss volume estimation.
41 * @param deviation_ Proposal standard deviation (Gaussian random walk step size).
42 * @param p_ Target density proportional to integrand: π(x) ∝ p(x).
43 * @param x0_ Initial MH state (must be inside domain).
45 * @throws std::invalid_argument If parameters are invalid:
48 * - n_samples_volume_ == 0
51 * @details Must be called before integrate(). Validates all parameters.
53template <std::size_t dim>
54void MHMontecarloIntegrator<dim>::setConfig(std::size_t burn_in_,
55 std::size_t thinning_,
56 std::size_t n_samples_volume_,
63 n_samples_volume = n_samples_volume_;
64 deviation = deviation_;
68 const double p0 = p(x0);
70 if (thinning == 0) throw std::invalid_argument("thinning must be > 0");
71 if (deviation <= 0.0) throw std::invalid_argument("deviation must be > 0");
72 if (n_samples_volume == 0) throw std::invalid_argument("n_samples_volume must be > 0");
73 if (!this->domain.isInside(x0)) throw std::invalid_argument("x0 must be inside the domain");
74 if (!(p0 > 0.0) || !std::isfinite(p0)) throw std::invalid_argument("p(x0) must be finite and > 0 (choose x0 in support of p).");
78 * @brief Compute the integral using MH sampling combined with volume estimation.
79 * @tparam dim Dimensionality parameter.
80 * @param f Integrand function: ℝⁿ → ℝ.
81 * @param n_samples Number of (post-burn-in, post-thinning) samples to keep.
82 * @param proposal Unused (MH uses internal sampler with configured p_).
83 * @param seed Random seed for reproducibility (used for both volume and MH sampling).
84 * @return Estimated integral ∫_Ω f(x) dx.
86 * @throws std::runtime_error If setConfig() was not called first.
87 * @throws std::invalid_argument If n_samples ≤ 0.
88 * @throws std::runtime_error If all sampled f(x) are non-finite.
90 * @details Two-stage algorithm:
91 * 1. **Volume Estimation**: Hit-or-Miss MC with n_samples_volume samples
93 * 2. **Mean Estimation**: MH sampling with:
94 * - Burn-in: discard first burn_in iterations (warmup)
95 * - Thinning: keep every thinning-th sample (autocorrelation reduction)
96 * - Parallel: each thread runs independent MH chain
97 * 3. **Integration**: ∫_Ω f dx ≈ V̂_Ω · (1/M) ∑ f(xᵢ)
98 * where M = number of kept samples
100template <std::size_t dim>
101double MHMontecarloIntegrator<dim>::integrate(const Func& f,
103 const mc::proposals::Proposal<dim>&,
107 throw std::runtime_error("MHMontecarloIntegrator not configured. Call setConfig(...) first.");
109 throw std::invalid_argument("n_samples must be > 0");
111 //Estimate Zp = ∫_Ω p(x) dx with a plain MC integrator (uniform samplI)
112 mc::proposals::UniformProposal<dim> proposal(this->domain);
113 mc::integrators::MontecarloIntegrator<dim> uni(this->domain);
114 const double Zp_hat = uni.integrate(p, n_samples_volume, proposal, seed);
116 long double sum = 0.0L;
117 std::size_t kept = 0;
119 const int T = omp_get_max_threads();
120 const std::size_t base = static_cast<std::size_t>(n_samples) / static_cast<std::size_t>(T);
121 const std::size_t rem = static_cast<std::size_t>(n_samples) % static_cast<std::size_t>(T);
123#pragma omp parallel reduction(+:sum, kept)
125 const int tid = omp_get_thread_num();
126 const std::size_t n_local = base + (static_cast<std::size_t>(tid) < rem ? 1u : 0u);
128 auto rng = mc::rng::make_engine_with_seed(std::optional<std::uint32_t>{seed},
129 static_cast<std::uint64_t>(tid));
131 mc::mcmc::MetropolisHastingsSampler<dim> mh_local(this->domain, p, x0, deviation);
135 for (std::size_t i = 0; i < burn_in; ++i)
136 (void)mh_local.next(rng);
138 // Sampling + thinning
139 for (std::size_t i = 0; i < n_local; ++i) {
140 for (std::size_t t = 0; t < thinning; ++t)
141 x_local = mh_local.next(rng);
143 const double px = p(x_local);
144 if (!(px > 0.0) || !std::isfinite(px))
147 const double fx = f(x_local);
148 if (std::isfinite(fx)) {
149 sum += static_cast<long double>(fx / px);
156 throw std::runtime_error("All sampled f(x)/p(x) were invalid (p<=0 or non-finite).");
158 const double mean_f_over_p = static_cast<double>(sum / static_cast<long double>(kept));
161 return Zp_hat * mean_f_over_p;
164} //namespace mc::integrators