1use crate::error::{IntegrateError, IntegrateResult};
8use crate::quad::trapezoid;
9use crate::IntegrateFloat;
10use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
11use scirs2_core::random::{Distribution, Uniform};
12use std::f64::consts::PI;
13use std::fmt::Debug;
14
15#[derive(Debug, Clone)]
17pub struct RombergOptions<F: IntegrateFloat> {
18 pub max_iters: usize,
20 pub abs_tol: F,
22 pub rel_tol: F,
24 pub max_true_dimension: usize,
27 pub min_monte_carlo_samples: usize,
29}
30
31impl<F: IntegrateFloat> Default for RombergOptions<F> {
32 fn default() -> Self {
33 Self {
34 max_iters: 20,
35 abs_tol: F::from_f64(1.0e-10).unwrap(),
36 rel_tol: F::from_f64(1.0e-10).unwrap(),
37 max_true_dimension: 3,
38 min_monte_carlo_samples: 10000,
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct RombergResult<F: IntegrateFloat> {
46 pub value: F,
48 pub abs_error: F,
50 pub n_iters: usize,
52 pub table: Array2<F>,
54 pub converged: bool,
56}
57
58#[allow(dead_code)]
82pub fn romberg<F, Func>(
83 f: Func,
84 a: F,
85 b: F,
86 options: Option<RombergOptions<F>>,
87) -> IntegrateResult<RombergResult<F>>
88where
89 F: IntegrateFloat,
90 Func: Fn(F) -> F + Copy,
91{
92 let opts = options.unwrap_or_default();
93 let max_iters = opts.max_iters;
94
95 let mut r_table = Array2::zeros((max_iters, max_iters));
97
98 r_table[[0, 0]] = trapezoid(f, a, b, 1);
100
101 let mut converged = false;
102 let mut n_iters = 1;
103
104 for i in 1..max_iters {
105 let n_intervals = 1 << i; r_table[[i, 0]] = trapezoid(f, a, b, n_intervals);
108
109 for j in 1..=i {
111 let coef = F::from_f64(4.0_f64.powi(j as i32) - 1.0).unwrap();
113 r_table[[i, j]] =
114 r_table[[i, j - 1]] + (r_table[[i, j - 1]] - r_table[[i - 1, j - 1]]) / coef;
115 }
116
117 let current = r_table[[i, i]];
119 let previous = r_table[[i - 1, i - 1]];
120 let abs_diff = (current - previous).abs();
121 let abs_criterion = abs_diff <= opts.abs_tol;
122 let rel_criterion = abs_diff <= opts.rel_tol * current.abs();
123
124 if abs_criterion || rel_criterion {
125 converged = true;
126 n_iters = i + 1;
127 break;
128 }
129
130 n_iters = i + 1;
131 }
132
133 let value = r_table[[n_iters - 1, n_iters - 1]];
135
136 let abs_error = if n_iters >= 2 {
138 (value - r_table[[n_iters - 2, n_iters - 2]]).abs()
139 } else {
140 F::from_f64(1.0e-3).unwrap() * value.abs()
142 };
143
144 let table = Array2::from_shape_fn((n_iters, n_iters), |(i, j)| {
146 if j <= i {
147 r_table[[i, j]]
148 } else {
149 F::zero()
150 }
151 });
152
153 Ok(RombergResult {
154 value,
155 abs_error,
156 n_iters,
157 table,
158 converged,
159 })
160}
161
162#[derive(Debug, Clone)]
192pub struct MultiRombergResult<F: IntegrateFloat> {
193 pub value: F,
195 pub abs_error: F,
197 pub method: IntegrationMethod,
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
203pub enum IntegrationMethod {
204 Romberg,
206 AdaptiveNested,
208 GridTrapezoid,
210 MonteCarlo,
212}
213
214#[allow(dead_code)]
215pub fn multi_romberg<F, Func>(
216 f: Func,
217 ranges: &[(F, F)],
218 options: Option<RombergOptions<F>>,
219) -> IntegrateResult<F>
220where
221 F: IntegrateFloat + scirs2_core::random::uniform::SampleUniform,
222 Func: Fn(ArrayView1<F>) -> F + Copy,
223{
224 let result = multi_romberg_with_details(f, ranges, options)?;
225 Ok(result.value)
226}
227
228#[allow(dead_code)]
233pub fn multi_romberg_with_details<F, Func>(
234 f: Func,
235 ranges: &[(F, F)],
236 options: Option<RombergOptions<F>>,
237) -> IntegrateResult<MultiRombergResult<F>>
238where
239 F: IntegrateFloat + scirs2_core::random::uniform::SampleUniform,
240 Func: Fn(ArrayView1<F>) -> F + Copy,
241{
242 if ranges.is_empty() {
243 return Err(IntegrateError::ValueError(
244 "Integration ranges cannot be empty".to_string(),
245 ));
246 }
247
248 let opts = options.unwrap_or_default();
249 let n_dims = ranges.len();
250
251 if n_dims == 1 {
253 let (a, b) = ranges[0];
254 let result = romberg(
256 |x| f(Array1::from_vec(vec![x]).view()),
257 a,
258 b,
259 Some(opts.clone()),
260 )?;
261
262 return Ok(MultiRombergResult {
263 value: result.value,
264 abs_error: result.abs_error,
265 method: IntegrationMethod::Romberg,
266 });
267 }
268
269 if n_dims <= opts.max_true_dimension && n_dims <= 3 {
271 return integrate_adaptive_nested(f, ranges, &opts);
272 }
273
274 monte_carlo_high_dimensions(f, ranges, &opts)
277}
278
279#[allow(dead_code)]
281fn integrate_adaptive_nested<F, Func>(
282 f: Func,
283 ranges: &[(F, F)],
284 opts: &RombergOptions<F>,
285) -> IntegrateResult<MultiRombergResult<F>>
286where
287 F: IntegrateFloat + scirs2_core::random::uniform::SampleUniform,
288 Func: Fn(ArrayView1<F>) -> F + Copy,
289{
290 let n_dims = ranges.len();
291
292 if n_dims == 2 {
293 let (a1, b1) = ranges[0];
295 let (a2, b2) = ranges[1];
296
297 let n_points = (opts.max_iters + 1).min(31); let h1 = (b1 - a1) / F::from_usize(n_points - 1).unwrap();
301 let h2 = (b2 - a2) / F::from_usize(n_points - 1).unwrap();
302
303 let mut sum = F::zero();
304 let mut sum_refined = F::zero(); for i in 0..n_points {
308 let x = a1 + F::from_usize(i).unwrap() * h1;
309 let weight_x = if i == 0 || i == n_points - 1 {
310 F::from_f64(0.5).unwrap()
311 } else {
312 F::one()
313 };
314
315 for j in 0..n_points {
316 let y = a2 + F::from_usize(j).unwrap() * h2;
317 let weight_y = if j == 0 || j == n_points - 1 {
318 F::from_f64(0.5).unwrap()
319 } else {
320 F::one()
321 };
322
323 let point = Array1::from_vec(vec![x, y]);
325 let value = f(point.view());
326
327 sum += weight_x * weight_y * value;
329 }
330 }
331
332 let result = sum * h1 * h2;
334
335 if n_points > 4 {
338 let refined_n = n_points / 2 + (n_points % 2);
339 let refined_h1 = (b1 - a1) / F::from_usize(refined_n - 1).unwrap();
340 let refined_h2 = (b2 - a2) / F::from_usize(refined_n - 1).unwrap();
341
342 for i in 0..refined_n {
343 let x = a1 + F::from_usize(i).unwrap() * refined_h1;
344 let weight_x = if i == 0 || i == refined_n - 1 {
345 F::from_f64(0.5).unwrap()
346 } else {
347 F::one()
348 };
349
350 for j in 0..refined_n {
351 let y = a2 + F::from_usize(j).unwrap() * refined_h2;
352 let weight_y = if j == 0 || j == refined_n - 1 {
353 F::from_f64(0.5).unwrap()
354 } else {
355 F::one()
356 };
357
358 let point = Array1::from_vec(vec![x, y]);
360 let value = f(point.view());
361
362 sum_refined += weight_x * weight_y * value;
364 }
365 }
366
367 let refined_result = sum_refined * refined_h1 * refined_h2;
368
369 let abs_error = (result - refined_result).abs();
371
372 return Ok(MultiRombergResult {
373 value: result,
374 abs_error,
375 method: IntegrationMethod::GridTrapezoid,
376 });
377 }
378
379 let abs_error = result.abs() * F::from_f64(1e-3).unwrap();
381
382 return Ok(MultiRombergResult {
383 value: result,
384 abs_error,
385 method: IntegrationMethod::GridTrapezoid,
386 });
387 } else if n_dims == 3 {
388 let (a1, b1) = ranges[0];
390 let (a2, b2) = ranges[1];
391 let (a3, b3) = ranges[2];
392
393 let n_points = (opts.max_iters / 2 + 1).min(11);
395
396 let h1 = (b1 - a1) / F::from_usize(n_points - 1).unwrap();
397 let h2 = (b2 - a2) / F::from_usize(n_points - 1).unwrap();
398 let h3 = (b3 - a3) / F::from_usize(n_points - 1).unwrap();
399
400 let mut sum = F::zero();
401
402 for i in 0..n_points {
404 let x = a1 + F::from_usize(i).unwrap() * h1;
405 let weight_x = if i == 0 || i == n_points - 1 {
406 F::from_f64(0.5).unwrap()
407 } else {
408 F::one()
409 };
410
411 for j in 0..n_points {
412 let y = a2 + F::from_usize(j).unwrap() * h2;
413 let weight_y = if j == 0 || j == n_points - 1 {
414 F::from_f64(0.5).unwrap()
415 } else {
416 F::one()
417 };
418
419 for k in 0..n_points {
420 let z = a3 + F::from_usize(k).unwrap() * h3;
421 let weight_z = if k == 0 || k == n_points - 1 {
422 F::from_f64(0.5).unwrap()
423 } else {
424 F::one()
425 };
426
427 let point = Array1::from_vec(vec![x, y, z]);
429 let value = f(point.view());
430
431 sum += weight_x * weight_y * weight_z * value;
433 }
434 }
435 }
436
437 let result = sum * h1 * h2 * h3;
439
440 let abs_error = result.abs() * F::from_f64(1e-2).unwrap();
443
444 return Ok(MultiRombergResult {
445 value: result,
446 abs_error,
447 method: IntegrationMethod::AdaptiveNested,
448 });
449 }
450
451 monte_carlo_high_dimensions(f, ranges, opts)
453}
454
455#[allow(dead_code)]
457fn monte_carlo_high_dimensions<F, Func>(
458 f: Func,
459 ranges: &[(F, F)],
460 opts: &RombergOptions<F>,
461) -> IntegrateResult<MultiRombergResult<F>>
462where
463 F: IntegrateFloat + scirs2_core::random::uniform::SampleUniform,
464 Func: Fn(ArrayView1<F>) -> F + Copy,
465{
466 let n_dims = ranges.len();
467
468 let base_samples = opts.min_monte_carlo_samples;
471 let n_samples = base_samples * n_dims.max(4);
472
473 let mut sum = F::zero();
474 let mut sum_squares = F::zero();
475 let mut rng = scirs2_core::random::rng();
476
477 let uniforms: Vec<_> = ranges
479 .iter()
480 .map(|&(a, b)| Uniform::new_inclusive(a, b).unwrap())
481 .collect();
482
483 let mut volume = F::one();
485 for &(a, b) in ranges {
486 volume *= b - a;
487 }
488
489 let strata = 2_usize; let n_samples_per_strata = n_samples / strata.pow(n_dims as u32).max(1);
492 let n_samples_per_strata = n_samples_per_strata.max(100); let mut point = Array1::zeros(n_dims);
496 let mut n_actual_samples = 0;
497
498 for _ in 0..n_samples_per_strata {
500 for (i, dist) in uniforms.iter().enumerate() {
502 point[i] = dist.sample(&mut rng);
503 }
504
505 let value = f(point.view());
508 sum += value;
509 sum_squares += value * value;
510 n_actual_samples += 1;
511
512 for i in 0..n_dims {
514 let (a, b) = ranges[i];
515 point[i] = a + b - point[i]; }
517
518 let antithetic_value = f(point.view());
519 sum += antithetic_value;
520 sum_squares += antithetic_value * antithetic_value;
521 n_actual_samples += 1;
522 }
523
524 let n_samples_f = F::from_usize(n_actual_samples).unwrap();
526 let mean = sum / n_samples_f;
527 let result = mean * volume;
528
529 let variance = (sum_squares - sum * sum / n_samples_f) / (n_samples_f - F::one());
531 let std_error = (variance / n_samples_f).sqrt() * volume;
532
533 Ok(MultiRombergResult {
534 value: result,
535 abs_error: std_error,
536 method: IntegrationMethod::MonteCarlo,
537 })
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 use approx::assert_relative_eq;
544
545 #[test]
546 fn test_romberg_integration() {
547 let result = romberg(|x| x * x, 0.0, 1.0, None).unwrap();
549 assert_relative_eq!(result.value, 1.0 / 3.0, epsilon = 1e-10);
550 assert!(result.converged);
551
552 let result = romberg(|x: f64| x.sin(), 0.0, PI, None).unwrap();
554 assert_relative_eq!(result.value, 2.0, epsilon = 1e-10);
555 assert!(result.converged);
556
557 let result = romberg(|x: f64| (-x * x).exp(), -1.0, 1.0, None).unwrap();
560 let exact = PI.sqrt() * libm::erf(1.0);
561 assert_relative_eq!(result.value, exact, epsilon = 1e-10);
562 assert!(result.converged);
563 }
564
565 #[test]
566 fn test_romberg_with_custom_options() {
567 let options = RombergOptions {
569 max_iters: 10,
570 abs_tol: 1e-12,
571 rel_tol: 1e-12,
572 max_true_dimension: 3,
573 min_monte_carlo_samples: 10000,
574 };
575
576 let result = romberg(|x| x * x, 0.0, 1.0, Some(options)).unwrap();
577 assert_relative_eq!(result.value, 1.0 / 3.0, epsilon = 1e-12);
578 assert!(result.converged);
579 }
580
581 #[test]
582 fn test_multi_dimensional_romberg() {
583 let result = multi_romberg_with_details(
586 |x| x[0] * x[0] + x[1] * x[1],
587 &[(0.0, 1.0), (0.0, 1.0)],
588 None,
589 )
590 .unwrap();
591
592 assert_relative_eq!(result.value, 2.0 / 3.0, epsilon = 1e-3);
594 assert_eq!(result.method, IntegrationMethod::GridTrapezoid);
596
597 let result = multi_romberg_with_details(
600 |x| x[0] * x[0] * x[1] * x[1] * x[2] * x[2],
601 &[(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
602 None,
603 )
604 .unwrap();
605
606 assert_relative_eq!(result.value, 1.0 / 27.0, epsilon = 1e-2);
607 assert_eq!(result.method, IntegrationMethod::AdaptiveNested);
609
610 let result = multi_romberg_with_details(
614 |x| x[0] * x[0] * x[1] * x[1] * x[2] * x[2] * x[3] * x[3],
615 &[(0.0, 1.0), (0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
616 None,
617 )
618 .unwrap();
619
620 assert_relative_eq!(result.value, 1.0 / 81.0, epsilon = 1e-1);
622 assert_eq!(result.method, IntegrationMethod::MonteCarlo);
624
625 let custom_opts = RombergOptions {
627 max_true_dimension: 1, ..Default::default()
629 };
630
631 let result = multi_romberg_with_details(
633 |x| x[0] * x[0] + x[1] * x[1],
634 &[(0.0, 1.0), (0.0, 1.0)],
635 Some(custom_opts),
636 )
637 .unwrap();
638
639 assert_relative_eq!(result.value, 2.0 / 3.0, epsilon = 5e-2);
640 assert_eq!(result.method, IntegrationMethod::MonteCarlo);
641 }
642}