mathhook_core/calculus/integrals/numerical/
simpson.rs1use super::{IntegrationConfig, IntegrationResult, NumericalIntegrator};
7use crate::error::MathError;
8
9pub struct AdaptiveSimpson;
11
12struct SimpsonRecursionParams<'a> {
14 tolerance: f64,
15 s_whole: f64,
16 fa: f64,
17 fb: f64,
18 depth: usize,
19 max_depth: usize,
20 subdivisions: &'a mut usize,
21}
22
23impl AdaptiveSimpson {
24 pub fn new() -> Self {
34 Self
35 }
36
37 fn simpson_step<F>(&self, f: &F, a: f64, b: f64, fa: f64, fb: f64) -> f64
39 where
40 F: Fn(f64) -> f64,
41 {
42 let mid = (a + b) / 2.0;
43 let fmid = f(mid);
44 let h = (b - a) / 6.0;
45 h * (fa + 4.0 * fmid + fb)
46 }
47
48 fn adaptive_simpson_recursive<F>(
50 &self,
51 f: &F,
52 a: f64,
53 b: f64,
54 params: &mut SimpsonRecursionParams,
55 ) -> Result<f64, MathError>
56 where
57 F: Fn(f64) -> f64,
58 {
59 if params.depth > params.max_depth {
60 return Err(MathError::MaxIterationsReached {
61 max_iterations: params.max_depth,
62 });
63 }
64
65 let mid = (a + b) / 2.0;
66 let fmid = f(mid);
67
68 let s_left = self.simpson_step(f, a, mid, params.fa, fmid);
69 let s_right = self.simpson_step(f, mid, b, fmid, params.fb);
70 let s_split = s_left + s_right;
71
72 let error = (s_split - params.s_whole).abs();
73
74 if error < 15.0 * params.tolerance {
75 *params.subdivisions += 1;
76 Ok(s_split + error / 15.0)
77 } else {
78 *params.subdivisions += 1;
79
80 let mut left_params = SimpsonRecursionParams {
81 tolerance: params.tolerance / 2.0,
82 s_whole: s_left,
83 fa: params.fa,
84 fb: fmid,
85 depth: params.depth + 1,
86 max_depth: params.max_depth,
87 subdivisions: params.subdivisions,
88 };
89
90 let left_result = self.adaptive_simpson_recursive(f, a, mid, &mut left_params)?;
91
92 let mut right_params = SimpsonRecursionParams {
93 tolerance: params.tolerance / 2.0,
94 s_whole: s_right,
95 fa: fmid,
96 fb: params.fb,
97 depth: params.depth + 1,
98 max_depth: params.max_depth,
99 subdivisions: params.subdivisions,
100 };
101
102 let right_result = self.adaptive_simpson_recursive(f, mid, b, &mut right_params)?;
103
104 Ok(left_result + right_result)
105 }
106 }
107}
108
109impl Default for AdaptiveSimpson {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl NumericalIntegrator for AdaptiveSimpson {
116 fn integrate<F>(
117 &self,
118 f: F,
119 a: f64,
120 b: f64,
121 config: &IntegrationConfig,
122 ) -> Result<IntegrationResult, MathError>
123 where
124 F: Fn(f64) -> f64,
125 {
126 if a >= b {
127 return Err(MathError::InvalidInterval { lower: a, upper: b });
128 }
129
130 let fa = f(a);
131 let fb = f(b);
132 let s_whole = self.simpson_step(&f, a, b, fa, fb);
133
134 let max_depth = (config.max_iterations as f64).log2().ceil() as usize;
135
136 let mut subdivisions = 0;
137
138 let mut params = SimpsonRecursionParams {
139 tolerance: config.tolerance,
140 s_whole,
141 fa,
142 fb,
143 depth: 0,
144 max_depth,
145 subdivisions: &mut subdivisions,
146 };
147
148 let value = self.adaptive_simpson_recursive(&f, a, b, &mut params)?;
149
150 let error_estimate = config.tolerance;
151
152 Ok(IntegrationResult {
153 value,
154 error_estimate,
155 iterations: subdivisions,
156 subdivisions,
157 })
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_adaptive_simpson_polynomial() {
167 let integrator = AdaptiveSimpson::new();
168 let config = IntegrationConfig {
169 tolerance: 1e-10,
170 ..Default::default()
171 };
172
173 let result = integrator.integrate(|x| x * x, 0.0, 1.0, &config).unwrap();
174
175 assert!((result.value - 1.0 / 3.0).abs() < 1e-9);
176 }
177
178 #[test]
179 fn test_adaptive_simpson_sine() {
180 let integrator = AdaptiveSimpson::new();
181 let config = IntegrationConfig {
182 tolerance: 1e-8,
183 ..Default::default()
184 };
185
186 let result = integrator
187 .integrate(|x| x.sin(), 0.0, std::f64::consts::PI, &config)
188 .unwrap();
189
190 assert!((result.value - 2.0).abs() < 1e-7);
191 }
192
193 #[test]
194 fn test_adaptive_simpson_exponential() {
195 let integrator = AdaptiveSimpson::new();
196 let config = IntegrationConfig {
197 tolerance: 1e-10,
198 ..Default::default()
199 };
200
201 let result = integrator
202 .integrate(|x| x.exp(), 0.0, 1.0, &config)
203 .unwrap();
204
205 let expected = std::f64::consts::E - 1.0;
206 assert!((result.value - expected).abs() < 1e-9);
207 }
208
209 #[test]
210 fn test_adaptive_simpson_oscillatory() {
211 let integrator = AdaptiveSimpson::new();
212 let config = IntegrationConfig {
213 tolerance: 1e-6,
214 ..Default::default()
215 };
216
217 let result = integrator
218 .integrate(|x| (10.0 * x).sin(), 0.0, 1.0, &config)
219 .unwrap();
220
221 let expected = (1.0 - (10.0_f64).cos()) / 10.0;
222 assert!((result.value - expected).abs() < 1e-5);
223 }
224
225 #[test]
226 fn test_adaptive_simpson_cubic() {
227 let integrator = AdaptiveSimpson::new();
228 let config = IntegrationConfig {
229 tolerance: 1e-10,
230 ..Default::default()
231 };
232
233 let result = integrator
234 .integrate(|x| x * x * x, 0.0, 2.0, &config)
235 .unwrap();
236
237 assert!((result.value - 4.0).abs() < 1e-9);
238 }
239
240 #[test]
241 fn test_adaptive_simpson_invalid_interval() {
242 let integrator = AdaptiveSimpson::new();
243 let config = IntegrationConfig::default();
244
245 let result = integrator.integrate(|x| x, 1.0, 0.0, &config);
246 assert!(result.is_err());
247 }
248}