1pub mod basic_mc;
17pub mod importance_sampling;
18pub mod multilevel_mc;
19pub mod path_sampling;
20pub mod quasi_monte_carlo;
21
22pub use importance_sampling::{
26 effective_sample_size, importance_sampling_integral, self_normalized_is, sequential_is,
27 stratified_sampling, ImportanceSamplingResult, StratifiedResult,
28};
29pub use multilevel_mc::{
30 geometric_brownian_motion_levels, mlmc_adaptive, mlmc_complexity, mlmc_integrate, MLMCLevel,
31 MLMCOptions, MLMCResult,
32};
33pub use path_sampling::{
34 annealed_importance_sampling, diffusion_bridge, metropolis_walk, AISOptions, AISResult,
35 DiffusionBridgeResult, FeynmanKacEstimator, FeynmanKacOptions, MetropolisWalkOptions,
36 MetropolisWalkResult,
37};
38pub use quasi_monte_carlo::{
39 qmc_integrate, scrambled_net, HaltonSequence, LatticeRule, QmcIntegrateOptions,
40 QmcIntegrateResult, SobolSequence,
41};
42
43use crate::error::{IntegrateError, IntegrateResult};
44use crate::IntegrateFloat;
45use scirs2_core::ndarray::{Array1, ArrayView1};
46use scirs2_core::random::prelude::*;
47use scirs2_core::random::uniform::SampleUniform;
48use scirs2_core::random::{Distribution, Uniform};
49use std::f64::consts::PI;
50use std::fmt::Debug;
51use std::marker::PhantomData;
52
53#[derive(Debug, Clone)]
55pub struct MonteCarloOptions<F: IntegrateFloat> {
56 pub n_samples: usize,
58 pub seed: Option<u64>,
60 pub error_method: ErrorEstimationMethod,
62 pub use_antithetic: bool,
64 pub phantom: PhantomData<F>,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
70pub enum ErrorEstimationMethod {
71 StandardError,
73 BatchMeans,
75}
76
77impl<F: IntegrateFloat> Default for MonteCarloOptions<F> {
78 fn default() -> Self {
79 Self {
80 n_samples: 10000,
81 seed: None,
82 error_method: ErrorEstimationMethod::StandardError,
83 use_antithetic: false,
84 phantom: PhantomData,
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct MonteCarloResult<F: IntegrateFloat> {
92 pub value: F,
94 pub std_error: F,
96 pub n_evals: usize,
98}
99
100#[allow(dead_code)]
136pub fn monte_carlo<F, Func>(
137 f: Func,
138 ranges: &[(F, F)],
139 options: Option<MonteCarloOptions<F>>,
140) -> IntegrateResult<MonteCarloResult<F>>
141where
142 F: IntegrateFloat + Send + Sync + SampleUniform,
143 Func: Fn(ArrayView1<F>) -> F + Sync,
144 scirs2_core::random::StandardNormal: Distribution<F>,
145{
146 let opts = options.unwrap_or_default();
147 let n_dims = ranges.len();
148
149 if n_dims == 0 {
150 return Err(IntegrateError::ValueError(
151 "Integration ranges cannot be empty".to_string(),
152 ));
153 }
154
155 if opts.n_samples == 0 {
156 return Err(IntegrateError::ValueError(
157 "Number of samples must be positive".to_string(),
158 ));
159 }
160
161 let mut volume = F::one();
163 for &(a, b) in ranges {
164 volume *= b - a;
165 }
166
167 let mut rng = if let Some(seed) = opts.seed {
169 StdRng::seed_from_u64(seed)
170 } else {
171 let mut os_rng = scirs2_core::random::rng();
172 StdRng::from_rng(&mut os_rng)
173 };
174
175 let distributions: Vec<_> = ranges
177 .iter()
178 .map(|&(a, b)| Uniform::new_inclusive(a, b).expect("valid range for Uniform distribution"))
179 .collect();
180
181 let n_actual_samples = if opts.use_antithetic {
183 opts.n_samples / 2 * 2 } else {
185 opts.n_samples
186 };
187
188 let mut point = Array1::zeros(n_dims);
190
191 let mut sum = F::zero();
193 let mut sum_sq = F::zero();
194 let mut n_evals = 0;
195
196 if opts.use_antithetic {
197 for _ in 0..(n_actual_samples / 2) {
199 for (i, dist) in distributions.iter().enumerate() {
200 point[i] = dist.sample(&mut rng);
201 }
202 let value = f(point.view());
203 sum += value;
204 sum_sq += value * value;
205 n_evals += 1;
206
207 for (i, &(a, b)) in ranges.iter().enumerate() {
209 point[i] = a + b - point[i];
210 }
211 let antithetic_value = f(point.view());
212 sum += antithetic_value;
213 sum_sq += antithetic_value * antithetic_value;
214 n_evals += 1;
215 }
216 } else {
217 for _ in 0..n_actual_samples {
219 for (i, dist) in distributions.iter().enumerate() {
220 point[i] = dist.sample(&mut rng);
221 }
222 let value = f(point.view());
223 sum += value;
224 sum_sq += value * value;
225 n_evals += 1;
226 }
227 }
228
229 let mean = sum / F::from_usize(n_actual_samples).expect("n_actual_samples fits in F");
230 let integral_value = mean * volume;
231
232 let std_error = match opts.error_method {
233 ErrorEstimationMethod::StandardError | ErrorEstimationMethod::BatchMeans => {
234 let variance = (sum_sq
235 - sum * sum / F::from_usize(n_actual_samples).expect("n_actual_samples fits in F"))
236 / F::from_usize(n_actual_samples - 1).expect("n_actual_samples-1 fits in F");
237 (variance / F::from_usize(n_actual_samples).expect("n_actual_samples fits in F")).sqrt()
238 * volume
239 }
240 };
241
242 Ok(MonteCarloResult {
243 value: integral_value,
244 std_error,
245 n_evals,
246 })
247}
248
249#[allow(dead_code)]
295pub fn importance_sampling<F, Func, Pdf, Sampler>(
296 f: Func,
297 g: Pdf,
298 sampler: Sampler,
299 ranges: &[(F, F)],
300 options: Option<MonteCarloOptions<F>>,
301) -> IntegrateResult<MonteCarloResult<F>>
302where
303 F: IntegrateFloat + Send + Sync + SampleUniform,
304 Func: Fn(ArrayView1<F>) -> F + Sync,
305 Pdf: Fn(ArrayView1<F>) -> F + Sync,
306 Sampler: Fn(&mut StdRng, usize) -> Array1<F> + Sync,
307 scirs2_core::random::StandardNormal: Distribution<F>,
308{
309 let opts = options.unwrap_or_default();
310 let n_dims = ranges.len();
311
312 if n_dims == 0 {
313 return Err(IntegrateError::ValueError(
314 "Integration ranges cannot be empty".to_string(),
315 ));
316 }
317
318 if opts.n_samples == 0 {
319 return Err(IntegrateError::ValueError(
320 "Number of samples must be positive".to_string(),
321 ));
322 }
323
324 let mut rng = if let Some(seed) = opts.seed {
325 StdRng::seed_from_u64(seed)
326 } else {
327 let mut os_rng = scirs2_core::random::rng();
328 StdRng::from_rng(&mut os_rng)
329 };
330
331 let mut sum = F::zero();
332 let mut sum_sq = F::zero();
333 let n_samples = opts.n_samples;
334 let mut valid_samples = 0;
335
336 for _ in 0..n_samples {
337 let point = sampler(&mut rng, n_dims);
338
339 let fx = f(point.view());
340 let gx = g(point.view());
341
342 if gx <= F::from_f64(1e-10).expect("1e-10 fits in F") {
343 continue;
344 }
345
346 if fx.is_nan() || fx.is_infinite() || gx.is_nan() || gx.is_infinite() {
347 continue;
348 }
349
350 let ratio = fx / gx;
351
352 if ratio.is_nan() || ratio.is_infinite() {
353 continue;
354 }
355
356 sum += ratio;
357 sum_sq += ratio * ratio;
358 valid_samples += 1;
359 }
360
361 if valid_samples < 10 {
362 return Err(IntegrateError::ConvergenceError(
363 "Too few valid samples in importance sampling".to_string(),
364 ));
365 }
366
367 let valid_samples_f = F::from_usize(valid_samples).expect("valid_samples fits in F");
368 let mean = sum / valid_samples_f;
369
370 let variance = if valid_samples > 1 {
371 (sum_sq - sum * sum / valid_samples_f)
372 / F::from_usize(valid_samples - 1).expect("valid_samples-1 fits in F")
373 } else {
374 F::zero()
375 };
376
377 let std_error = (variance / valid_samples_f).sqrt();
378
379 Ok(MonteCarloResult {
380 value: mean,
381 std_error,
382 n_evals: valid_samples,
383 })
384}
385
386#[allow(dead_code)]
408pub fn monte_carlo_parallel<F, Func>(
409 f: Func,
410 ranges: &[(F, F)],
411 options: Option<MonteCarloOptions<F>>,
412 workers: Option<usize>,
413) -> IntegrateResult<MonteCarloResult<F>>
414where
415 F: IntegrateFloat + Send + Sync + SampleUniform,
416 Func: Fn(ArrayView1<F>) -> F + Sync + Send,
417 scirs2_core::random::StandardNormal: Distribution<F>,
418{
419 #[cfg(feature = "parallel")]
420 {
421 if workers.is_some() {
422 use crate::monte_carlo_parallel::{parallel_monte_carlo, ParallelMonteCarloOptions};
423
424 let opts = options.unwrap_or_default();
425 let parallel_opts = ParallelMonteCarloOptions {
426 n_samples: opts.n_samples,
427 seed: opts.seed,
428 error_method: opts.error_method,
429 use_antithetic: opts.use_antithetic,
430 n_threads: workers,
431 batch_size: 1000,
432 use_chunking: true,
433 phantom: PhantomData,
434 };
435
436 return parallel_monte_carlo(f, ranges, Some(parallel_opts));
437 }
438 }
439
440 let _ = workers;
441 monte_carlo(f, ranges, options)
442}
443
444#[cfg(test)]
445mod tests {
446 use crate::{importance_sampling, monte_carlo, monte_carlo_parallel, MonteCarloOptions};
447 use scirs2_core::ndarray::{Array1, ArrayView1};
448 use scirs2_core::random::prelude::StdRng;
449 use scirs2_core::random::Distribution;
450 use std::f64::consts::PI;
451 use std::marker::PhantomData;
452
453 fn is_close_enough(result: f64, expected: f64, epsilon: f64) -> bool {
454 (result - expected).abs() < epsilon
455 }
456
457 #[test]
458 fn test_monte_carlo_1d() {
459 let options = MonteCarloOptions {
460 n_samples: 100000,
461 seed: Some(12345),
462 phantom: PhantomData,
463 ..Default::default()
464 };
465
466 let result =
467 monte_carlo(|x| x[0] * x[0], &[(0.0, 1.0)], Some(options)).expect("Operation failed");
468
469 assert!(is_close_enough(result.value, 1.0 / 3.0, 0.01));
470 }
471
472 #[test]
473 fn test_monte_carlo_2d() {
474 let options = MonteCarloOptions {
475 n_samples: 100000,
476 seed: Some(12345),
477 phantom: PhantomData,
478 ..Default::default()
479 };
480
481 let result = monte_carlo(
482 |x| x[0] * x[0] + x[1] * x[1],
483 &[(0.0, 1.0), (0.0, 1.0)],
484 Some(options),
485 )
486 .expect("Failed to integrate");
487
488 assert!(is_close_enough(result.value, 2.0 / 3.0, 0.01));
489 }
490
491 #[test]
492 fn test_monte_carlo_with_antithetic() {
493 let options = MonteCarloOptions {
494 n_samples: 100000,
495 seed: Some(12345),
496 use_antithetic: true,
497 phantom: PhantomData,
498 ..Default::default()
499 };
500
501 let result =
502 monte_carlo(|x| x[0] * x[0], &[(0.0, 1.0)], Some(options)).expect("Operation failed");
503
504 assert!(is_close_enough(result.value, 1.0 / 3.0, 0.01));
505 }
506
507 #[test]
508 fn test_importance_sampling() {
509 let sampler = |rng: &mut StdRng, dims: usize| {
510 let mut point = Array1::zeros(dims);
511 let normal = scirs2_core::random::Normal::new(0.0, 1.0).expect("valid normal params");
512
513 for i in 0..dims {
514 let mut x: f64 = normal.sample(rng);
515 x = x.abs();
516 if x > 3.0 {
517 x = 6.0 - x;
518 if x < 0.0 {
519 x = 0.0;
520 }
521 }
522 point[i] = x;
523 }
524 point
525 };
526
527 let normal_pdf = |x: ArrayView1<f64>| {
528 let mut pdf_val = 1.0;
529 for &xi in x.iter() {
530 let z = xi;
531 let density = (-0.5 * z * z).exp() / (2.0f64 * PI).sqrt();
532 let folded_density = if xi < 3.0 { 2.0 * density } else { 0.0 };
533 pdf_val *= folded_density.max(1e-10);
534 }
535 pdf_val
536 };
537
538 let options = MonteCarloOptions {
539 n_samples: 100000,
540 seed: Some(12345),
541 phantom: PhantomData,
542 ..Default::default()
543 };
544
545 let exact_value = (PI).sqrt() / 2.0 * libm::erf(3.0);
546
547 let result = importance_sampling(
548 |x| (-x[0] * x[0]).exp(),
549 normal_pdf,
550 sampler,
551 &[(0.0, 3.0)],
552 Some(options),
553 )
554 .expect("Failed to integrate");
555
556 assert!(is_close_enough(result.value, exact_value, 0.1));
557 println!(
558 "Importance sampling test: got {}, expected {}",
559 result.value, exact_value
560 );
561 }
562
563 #[test]
564 fn test_monte_carlo_parallel_workers() {
565 let f = |x: ArrayView1<f64>| x[0].powi(2);
566 let ranges = vec![(0.0, 1.0)];
567 let options = MonteCarloOptions {
568 n_samples: 10000,
569 ..Default::default()
570 };
571
572 let result =
573 monte_carlo_parallel(f, &ranges, Some(options), Some(2)).expect("Operation failed");
574
575 assert!(is_close_enough(result.value, 1.0 / 3.0, 0.1));
576 assert!(result.std_error >= 0.0);
577 }
578}