Monte Carlo Integration Library 1.0
High-performance Monte Carlo methods for numerical integration and optimization
MHintegrator.tpp
Go to the documentation of this file.
1/**
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.
6 */
7// MHintegrator.tpp
8//
9// Created by Giacomo Merlo on 15/01/26.
10//
11
12#include <cmath>
13#include <iostream>
14#include <optional>
15#include <stdexcept>
16
17#include <omp.h>
18
19#include "montecarlo/rng/rng_factory.hpp"
20#include "montecarlo/integrators/MCintegrator.hpp"
21#include "montecarlo/proposals/uniformProposal.hpp"
22
23namespace mc::integrators {
24
25/**
26 * @brief Construct an MH-based integrator for a domain.
27 * @tparam dim Dimensionality parameter.
28 * @param d Reference to the integration domain.
29 */
30template <std::size_t dim>
31MHMontecarloIntegrator<dim>::MHMontecarloIntegrator(const mc::domains::IntegrationDomain<dim>& d)
32 : Integrator<dim>(d)
33{}
34
35/**
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).
44 *
45 * @throws std::invalid_argument If parameters are invalid:
46 * - thinning_ == 0
47 * - deviation_ ≤ 0
48 * - n_samples_volume_ == 0
49 * - x0_ ∉ domain
50 *
51 * @details Must be called before integrate(). Validates all parameters.
52 */
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_,
57 double deviation_,
58 Func p_,
59 Point x0_)
60{
61 burn_in = burn_in_;
62 thinning = thinning_;
63 n_samples_volume = n_samples_volume_;
64 deviation = deviation_;
65 p = std::move(p_);
66 x0 = x0_;
67 configured = true;
68 const double p0 = p(x0);
69
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).");
75}
76
77/**
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.
85 *
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.
89 *
90 * @details Two-stage algorithm:
91 * 1. **Volume Estimation**: Hit-or-Miss MC with n_samples_volume samples
92 * estimates V̂_Ω
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
99 */
100template <std::size_t dim>
101double MHMontecarloIntegrator<dim>::integrate(const Func& f,
102 int n_samples,
103 const mc::proposals::Proposal<dim>&,
104 std::uint32_t seed)
105{
106 if (!configured)
107 throw std::runtime_error("MHMontecarloIntegrator not configured. Call setConfig(...) first.");
108 if (n_samples <= 0)
109 throw std::invalid_argument("n_samples must be > 0");
110
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);
115
116 long double sum = 0.0L;
117 std::size_t kept = 0;
118
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);
122
123#pragma omp parallel reduction(+:sum, kept)
124 {
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);
127
128 auto rng = mc::rng::make_engine_with_seed(std::optional<std::uint32_t>{seed},
129 static_cast<std::uint64_t>(tid));
130
131 mc::mcmc::MetropolisHastingsSampler<dim> mh_local(this->domain, p, x0, deviation);
132 Point x_local{};
133
134 // Burn-in
135 for (std::size_t i = 0; i < burn_in; ++i)
136 (void)mh_local.next(rng);
137
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);
142
143 const double px = p(x_local);
144 if (!(px > 0.0) || !std::isfinite(px))
145 continue;
146
147 const double fx = f(x_local);
148 if (std::isfinite(fx)) {
149 sum += static_cast<long double>(fx / px);
150 ++kept;
151 }
152 }
153 }
154
155 if (kept == 0)
156 throw std::runtime_error("All sampled f(x)/p(x) were invalid (p<=0 or non-finite).");
157
158 const double mean_f_over_p = static_cast<double>(sum / static_cast<long double>(kept));
159
160 //Integral estimate
161 return Zp_hat * mean_f_over_p;
162}
163
164} //namespace mc::integrators