2 * @file mixtureProposal.tpp
3 * @brief MixtureProposal template implementation.
4 * @details Implements mixture sampling and PDF evaluation.
5 * Stores non-owning pointers to proposal components.
9#ifndef MONTECARLO_1_MIXTURE_PROPOSAL_TPP
10#define MONTECARLO_1_MIXTURE_PROPOSAL_TPP
12#include <numeric> // std::accumulate
13#include <cmath> // std::isfinite
19 * @brief Validate input components and weights.
20 * @tparam dim Dimensionality parameter.
21 * @param components Vector of proposal pointers (must be non-empty, all non-null).
22 * @param weights Vector of component weights (must match size, all ≥ 0, sum > 0).
24 * @throws std::invalid_argument If validation fails.
27void MixtureProposal<dim>::validateInputs(const std::vector<const Proposal<dim>*>& components,
28 const std::vector<double>& weights)
30 if (components.empty()) {
31 throw std::invalid_argument("MixtureProposal: components must be non-empty.");
33 if (components.size() != weights.size()) {
34 throw std::invalid_argument("MixtureProposal: components and weights must have the same size.");
37 for (std::size_t i = 0; i < components.size(); ++i) {
38 if (components[i] == nullptr) {
39 throw std::invalid_argument("MixtureProposal: component pointer cannot be null.");
41 if (!std::isfinite(weights[i]) || weights[i] < 0.0) {
42 throw std::invalid_argument("MixtureProposal: weights must be finite and >= 0.");
46 const double sumw = std::accumulate(weights.begin(), weights.end(), 0.0);
47 if (!std::isfinite(sumw) || sumw <= 0.0) {
48 throw std::invalid_argument("MixtureProposal: sum of weights must be > 0 and finite.");
53 * @brief Normalize weight vector to sum to 1.0.
54 * @tparam dim Dimensionality parameter.
55 * @param weights Unnormalized weights (must sum to > 0).
56 * @return Vector where weights[i] / sum(weights).
59std::vector<double> MixtureProposal<dim>::normalizeWeights(const std::vector<double>& weights)
61 const double sumw = std::accumulate(weights.begin(), weights.end(), 0.0);
63 std::vector<double> out;
64 out.reserve(weights.size());
66 for (double wi : weights) {
67 out.push_back(wi / sumw);
73 * @brief Construct a mixture from proposal components and weights.
74 * @tparam dim Dimensionality parameter.
75 * @param components Vector of non-owning pointers to Proposal<dim> (q_k).
76 * @param weights Vector of component weights (w_k), will be normalized.
78 * @throws std::invalid_argument If validation fails.
80 * @details Creates a mixture distribution:
83 * where components q_k are sampled from via categorical selection.
84 * IMPORTANT: Components must outlive this MixtureProposal instance.
87MixtureProposal<dim>::MixtureProposal(std::vector<const Proposal<dim>*> components,
88 std::vector<double> weights)
89 : comps(std::move(components))
91 validateInputs(comps, weights);
92 w = normalizeWeights(weights);
94 // Build categorical distribution for sampling component indices.
95 cat = std::discrete_distribution<std::size_t>(w.begin(), w.end());
99 * @brief Sample from the mixture distribution.
100 * @tparam dim Dimensionality parameter.
101 * @param rng Mersenne Twister random generator.
102 * @return Point sampled by: (1) selecting component k ~ Cat(w), (2) sampling from q_k.
104 * @details Two-stage sampling:
105 * 1. Draw component index k from categorical distribution over weights
106 * 2. Draw sample x ~ q_k(x)
109mc::geom::Point<dim> MixtureProposal<dim>::sample(std::mt19937& rng) const
111 const std::size_t k = cat(rng);
112 return comps[k]->sample(rng);
116 * @brief Evaluate the mixture probability density.
117 * @tparam dim Dimensionality parameter.
118 * @param x Query point.
119 * @return q(x) = ∑ₖ wₖ q_k(x)
121 * @details Weighted sum of component PDFs. Time complexity: O(num_components * dim).
124double MixtureProposal<dim>::pdf(const mc::geom::Point<dim>& x) const
127 for (std::size_t k = 0; k < comps.size(); ++k) {
128 acc += w[k] * comps[k]->pdf(x);
133} // namespace proposals
136#endif // MONTECARLO_1_MIXTURE_PROPOSAL_TPP