limes 3.1.0
Composable Calculus Expressions for C++20
Loading...
Searching...
No Matches
box_integral.hpp
Go to the documentation of this file.
1#pragma once
2
38#include <span>
39#include <string>
40#include <array>
41#include <cstddef>
42#include <sstream>
43#include <utility>
44#include <random>
45#include "../algorithms/core/result.hpp"
46#include "../methods/methods.hpp"
47#include "nodes/binary.hpp"
48
54namespace limes::expr {
55
56// Forward declarations
57template<typename E, std::size_t Dims, typename Constraint>
58struct ConstrainedBoxIntegral;
59
60namespace detail {
61
64template<typename T, std::size_t Dims>
65struct mc_box_sampler {
66 std::mt19937_64 rng;
67 std::array<std::uniform_real_distribution<T>, Dims> dists;
68 T volume;
69
70 explicit mc_box_sampler(
71 std::array<std::pair<T, T>, Dims> const& bounds,
72 std::optional<std::size_t> seed)
73 : volume{T(1)}
74 {
75 if (seed) {
76 rng.seed(*seed);
77 } else {
78 std::random_device rd;
79 rng.seed(rd());
80 }
81 for (std::size_t d = 0; d < Dims; ++d) {
82 dists[d] = std::uniform_real_distribution<T>(bounds[d].first, bounds[d].second);
83 volume *= (bounds[d].second - bounds[d].first);
84 }
85 }
86
87 void sample(std::array<T, Dims>& point) {
88 for (std::size_t d = 0; d < Dims; ++d) {
89 point[d] = dists[d](rng);
90 }
91 }
92};
93
94} // namespace detail
95
96// =============================================================================
97// BoxIntegral: Integration over rectangular N-dimensional regions
98// =============================================================================
99
131template<typename E, std::size_t Dims>
133 using value_type = typename E::value_type;
134 using integrand_type = E;
135 using bounds_type = std::array<std::pair<value_type, value_type>, Dims>;
136
137 static constexpr std::size_t dims_v = Dims;
138 static constexpr std::size_t arity_v = (E::arity_v > Dims) ? (E::arity_v - Dims) : 0;
139
142
143 constexpr BoxIntegral(E e, bounds_type b) noexcept
144 : integrand{e}, bounds{b} {}
145
146 // =========================================================================
147 // Evaluation using Monte Carlo (default for N-D integration)
148 // =========================================================================
149
151 [[nodiscard]] algorithms::integration_result<value_type>
153 detail::mc_box_sampler<value_type, Dims> sampler(bounds, method.seed);
154
156 value_type sum_sq = value_type(0);
157 std::array<value_type, Dims> point;
158
159 for (std::size_t i = 0; i < method.samples; ++i) {
160 sampler.sample(point);
161 value_type y = integrand.eval(std::span<value_type const>{point.data(), Dims});
162 sum += y;
163 sum_sq += y * y;
164 }
165
166 value_type n = static_cast<value_type>(method.samples);
167 value_type mean = sum / n;
168 value_type variance = (sum_sq / n - mean * mean) / n;
169 value_type value = mean * sampler.volume;
170 value_type error = std::sqrt(variance) * sampler.volume;
171
172 algorithms::integration_result<value_type> result{value, error, method.samples, method.samples};
173 result.variance_ = variance;
174 return result;
175 }
176
179 eval() const {
181 }
182
185 eval(std::size_t samples) const {
186 return eval(methods::monte_carlo<value_type>{samples});
187 }
188
189 // Deprecated: use eval() instead
190 [[nodiscard]] [[deprecated("use eval() instead")]]
194
196 [[nodiscard]] std::string to_string() const {
197 std::ostringstream oss;
198 oss << "(box-integral " << integrand.to_string() << " [";
199 for (std::size_t d = 0; d < Dims; ++d) {
200 if (d > 0) oss << ", ";
201 oss << "[" << bounds[d].first << ", " << bounds[d].second << "]";
202 }
203 oss << "])";
204 return oss.str();
205 }
206
208 template<typename Constraint>
209 [[nodiscard]] constexpr auto where(Constraint c) const {
211 }
212};
213
214// =============================================================================
215// ConstrainedBoxIntegral: Box integral with a constraint function
216// =============================================================================
217
252template<typename E, std::size_t Dims, typename Constraint>
254 using value_type = typename E::value_type;
255 using integrand_type = E;
256 using bounds_type = std::array<std::pair<value_type, value_type>, Dims>;
257
258 static constexpr std::size_t dims_v = Dims;
259 static constexpr std::size_t arity_v = (E::arity_v > Dims) ? (E::arity_v - Dims) : 0;
260
263 Constraint constraint;
264
265 constexpr ConstrainedBoxIntegral(E e, bounds_type b, Constraint c) noexcept
266 : integrand{e}, bounds{b}, constraint{c} {}
267
269 [[nodiscard]] algorithms::integration_result<value_type>
271 detail::mc_box_sampler<value_type, Dims> sampler(bounds, method.seed);
272
274 value_type sum_sq = value_type(0);
275 std::size_t accepted = 0;
276 std::array<value_type, Dims> point;
277
278 for (std::size_t i = 0; i < method.samples; ++i) {
279 sampler.sample(point);
280
281 if (evaluate_constraint(point)) {
282 value_type y = integrand.eval(std::span<value_type const>{point.data(), Dims});
283 sum += y;
284 sum_sq += y * y;
285 ++accepted;
286 }
287 }
288
289 if (accepted == 0) {
291 value_type(0), value_type(0), method.samples, method.samples};
292 }
293
294 value_type acceptance_rate = static_cast<value_type>(accepted)
295 / static_cast<value_type>(method.samples);
296 value_type region_volume = sampler.volume * acceptance_rate;
297
298 value_type n = static_cast<value_type>(accepted);
299 value_type mean = sum / n;
300 value_type variance = (sum_sq / n - mean * mean) / n;
301 value_type value = mean * region_volume;
302 value_type error = std::sqrt(variance) * region_volume;
303
304 algorithms::integration_result<value_type> result{value, error, method.samples, method.samples};
305 result.variance_ = variance;
306 return result;
307 }
308
311 eval() const {
313 }
314
317 eval(std::size_t samples) const {
318 return eval(methods::monte_carlo<value_type>{samples});
319 }
320
322 [[nodiscard]] std::string to_string() const {
323 std::ostringstream oss;
324 oss << "(constrained-box-integral " << integrand.to_string() << " [";
325 for (std::size_t d = 0; d < Dims; ++d) {
326 if (d > 0) oss << ", ";
327 oss << "[" << bounds[d].first << ", " << bounds[d].second << "]";
328 }
329 oss << "] <constraint>)";
330 return oss.str();
331 }
332
333private:
334 // Helper to call constraint with array unpacked to arguments
335 template<std::size_t... Is>
336 [[nodiscard]] bool evaluate_constraint_impl(
337 std::array<value_type, Dims> const& point,
338 std::index_sequence<Is...>) const {
339 return constraint(point[Is]...);
340 }
341
342 [[nodiscard]] bool evaluate_constraint(std::array<value_type, Dims> const& point) const {
343 return evaluate_constraint_impl(point, std::make_index_sequence<Dims>{});
344 }
345};
346
347// =============================================================================
348// IntegralBuilder extension for over_box
349// =============================================================================
350
351// Note: This is added to the IntegralBuilder via a free function pattern
352// to avoid modifying the existing IntegralBuilder class
353
355template<typename E, std::size_t Dims>
356[[nodiscard]] constexpr auto over_box(
357 E expr,
358 std::array<std::pair<typename E::value_type, typename E::value_type>, Dims> bounds
359) {
360 return BoxIntegral<E, Dims>{expr, bounds};
361}
362
363
364// =============================================================================
365// Convenience functions
366// =============================================================================
367
369template<typename E>
370[[nodiscard]] auto box2d(E expr, typename E::value_type x0, typename E::value_type x1,
371 typename E::value_type y0, typename E::value_type y1) {
372 return BoxIntegral<E, 2>{expr, {{{x0, x1}, {y0, y1}}}};
373}
374
376template<typename E>
377[[nodiscard]] auto box3d(E expr,
378 typename E::value_type x0, typename E::value_type x1,
379 typename E::value_type y0, typename E::value_type y1,
380 typename E::value_type z0, typename E::value_type z1) {
381 return BoxIntegral<E, 3>{expr, {{{x0, x1}, {y0, y1}, {z0, z1}}}};
382}
383
384// =============================================================================
385// Type traits
386// =============================================================================
387
388template<typename T>
389struct is_box_integral : std::false_type {};
390
391template<typename E, std::size_t D>
392struct is_box_integral<BoxIntegral<E, D>> : std::true_type {};
393
394template<typename T>
396
397template<typename T>
398struct is_constrained_box_integral : std::false_type {};
399
400template<typename E, std::size_t D, typename C>
401struct is_constrained_box_integral<ConstrainedBoxIntegral<E, D, C>> : std::true_type {};
402
403template<typename T>
405
// end of expr_ops group
407
408} // namespace limes::expr
constexpr bool is_constrained_box_integral_v
constexpr bool is_box_integral_v
auto box3d(E expr, typename E::value_type x0, typename E::value_type x1, typename E::value_type y0, typename E::value_type y1, typename E::value_type z0, typename E::value_type z1)
Create a 3D box integral (specialization for common case)
auto box2d(E expr, typename E::value_type x0, typename E::value_type x1, typename E::value_type y0, typename E::value_type y1)
Create a 2D box integral (specialization for common case)
constexpr auto over_box(E expr, std::array< std::pair< typename E::value_type, typename E::value_type >, Dims > bounds)
Create a box integral from an expression.
Expression layer for composable calculus.
Definition analysis.hpp:7
constexpr auto sum(E body, int lo, int hi)
constexpr Var< 1, T > y
Definition var.hpp:49
N-dimensional integral over a rectangular box.
static constexpr std::size_t dims_v
algorithms::integration_result< value_type > eval(methods::monte_carlo< value_type > const &method) const
Evaluate using Monte Carlo integration.
algorithms::integration_result< value_type > evaluate() const
static constexpr std::size_t arity_v
typename E::value_type value_type
algorithms::integration_result< value_type > eval(std::size_t samples) const
Evaluate with sample count.
algorithms::integration_result< value_type > eval() const
Evaluate with default Monte Carlo (10000 samples)
constexpr auto where(Constraint c) const
Add a constraint to the box integral (for rejection sampling)
std::string to_string() const
String representation.
constexpr BoxIntegral(E e, bounds_type b) noexcept
std::array< std::pair< value_type, value_type >, Dims > bounds_type
Box integral with constraint for irregular regions.
std::array< std::pair< value_type, value_type >, Dims > bounds_type
constexpr ConstrainedBoxIntegral(E e, bounds_type b, Constraint c) noexcept
algorithms::integration_result< value_type > eval(std::size_t samples) const
Evaluate with sample count.
static constexpr std::size_t dims_v
static constexpr std::size_t arity_v
algorithms::integration_result< value_type > eval(methods::monte_carlo< value_type > const &method) const
Evaluate using Monte Carlo with rejection sampling.
std::string to_string() const
String representation.
algorithms::integration_result< value_type > eval() const
Evaluate with default Monte Carlo.
Monte Carlo integration using random sampling. Error decreases as O(1/sqrt(n)).
Definition methods.hpp:107
std::optional< std::size_t > seed
Definition methods.hpp:111