mathhook_core/calculus/integrals/numerical/
gaussian.rs1use super::{IntegrationConfig, IntegrationResult, NumericalIntegrator};
6use crate::error::MathError;
7pub struct GaussianQuadrature {
9 order: usize,
10}
11impl GaussianQuadrature {
12 pub fn new(order: usize) -> Self {
26 assert!(
27 (2..=5).contains(&order),
28 "Gaussian quadrature order must be between 2 and 5"
29 );
30 Self { order }
31 }
32 fn get_nodes_and_weights(&self) -> (&[f64], &[f64]) {
34 match self.order {
35 2 => (&GAUSS_NODES_2, &GAUSS_WEIGHTS_2),
36 3 => (&GAUSS_NODES_3, &GAUSS_WEIGHTS_3),
37 4 => (&GAUSS_NODES_4, &GAUSS_WEIGHTS_4),
38 5 => (&GAUSS_NODES_5, &GAUSS_WEIGHTS_5),
39 _ => panic!("Invalid Gaussian quadrature order"),
40 }
41 }
42 fn transform_point(&self, x: f64, a: f64, b: f64) -> f64 {
44 0.5 * ((b - a) * x + (b + a))
45 }
46 fn jacobian(&self, a: f64, b: f64) -> f64 {
48 0.5 * (b - a)
49 }
50}
51impl NumericalIntegrator for GaussianQuadrature {
52 fn integrate<F>(
53 &self,
54 f: F,
55 a: f64,
56 b: f64,
57 _config: &IntegrationConfig,
58 ) -> Result<IntegrationResult, MathError>
59 where
60 F: Fn(f64) -> f64,
61 {
62 if a >= b {
63 return Err(MathError::InvalidInterval { lower: a, upper: b });
64 }
65 let (nodes, weights) = self.get_nodes_and_weights();
66 let jac = self.jacobian(a, b);
67 let mut sum = 0.0;
68 for (node, weight) in nodes.iter().zip(weights.iter()) {
69 let x = self.transform_point(*node, a, b);
70 sum += weight * f(x);
71 }
72 let value = jac * sum;
73 let error_estimate = self.estimate_error(&f, a, b, value);
74 Ok(IntegrationResult {
75 value,
76 error_estimate,
77 iterations: 1,
78 subdivisions: 1,
79 })
80 }
81}
82impl GaussianQuadrature {
83 fn estimate_error<F>(&self, f: &F, a: f64, b: f64, full_value: f64) -> f64
85 where
86 F: Fn(f64) -> f64,
87 {
88 let mid = (a + b) / 2.0;
89 let (nodes, weights) = self.get_nodes_and_weights();
90 let jac_left = self.jacobian(a, mid);
91 let mut sum_left = 0.0;
92 for (node, weight) in nodes.iter().zip(weights.iter()) {
93 let x = self.transform_point(*node, a, mid);
94 sum_left += weight * f(x);
95 }
96 let value_left = jac_left * sum_left;
97 let jac_right = self.jacobian(mid, b);
98 let mut sum_right = 0.0;
99 for (node, weight) in nodes.iter().zip(weights.iter()) {
100 let x = self.transform_point(*node, mid, b);
101 sum_right += weight * f(x);
102 }
103 let value_right = jac_right * sum_right;
104 let split_value = value_left + value_right;
105 (split_value - full_value).abs() / 15.0
106 }
107}
108const GAUSS_NODES_2: [f64; 2] = [-0.5773502691896257, 0.5773502691896257];
109const GAUSS_WEIGHTS_2: [f64; 2] = [1.0, 1.0];
110const GAUSS_NODES_3: [f64; 3] = [-0.7745966692414834, 0.0, 0.7745966692414834];
111const GAUSS_WEIGHTS_3: [f64; 3] = [0.5555555555555556, 0.8888888888888888, 0.5555555555555556];
112const GAUSS_NODES_4: [f64; 4] = [
113 -0.8611363115940526,
114 -0.3399810435848563,
115 0.3399810435848563,
116 0.8611363115940526,
117];
118const GAUSS_WEIGHTS_4: [f64; 4] = [
119 0.3478548451374538,
120 0.6521451548625461,
121 0.6521451548625461,
122 0.3478548451374538,
123];
124const GAUSS_NODES_5: [f64; 5] = [
125 -0.906_179_845_938_664,
126 -0.538_469_310_105_683,
127 0.0,
128 0.538_469_310_105_683,
129 0.906_179_845_938_664,
130];
131const GAUSS_WEIGHTS_5: [f64; 5] = [
132 0.2369268850561891,
133 0.4786286704993665,
134 0.5688888888888889,
135 0.4786286704993665,
136 0.2369268850561891,
137];
138#[cfg(test)]
139mod tests {
140 use super::*;
141 #[test]
142 fn test_gaussian_quadrature_polynomial() {
143 let integrator = GaussianQuadrature::new(3);
144 let config = IntegrationConfig::default();
145 let result = integrator.integrate(|x| x * x, 0.0, 1.0, &config).unwrap();
146 assert!((result.value - 1.0 / 3.0).abs() < 1e-10);
147 }
148 #[test]
149 fn test_gaussian_quadrature_sine() {
150 let integrator = GaussianQuadrature::new(5);
151 let config = IntegrationConfig::default();
152 let result = integrator
153 .integrate(|x| x.sin(), 0.0, std::f64::consts::PI, &config)
154 .unwrap();
155 assert!((result.value - 2.0).abs() < 1e-6);
156 }
157 #[test]
158 fn test_gaussian_quadrature_exponential() {
159 let integrator = GaussianQuadrature::new(5);
160 let config = IntegrationConfig::default();
161 let result = integrator
162 .integrate(|x| x.exp(), 0.0, 1.0, &config)
163 .unwrap();
164 let expected = std::f64::consts::E - 1.0;
165 assert!((result.value - expected).abs() < 1e-10);
166 }
167 #[test]
168 fn test_gaussian_error_estimate() {
169 let integrator = GaussianQuadrature::new(3);
170 let config = IntegrationConfig::default();
171 let result = integrator
172 .integrate(|x| x * x * x, 0.0, 1.0, &config)
173 .unwrap();
174 assert!(result.error_estimate < 1e-10);
175 }
176 #[test]
177 fn test_gaussian_invalid_interval() {
178 let integrator = GaussianQuadrature::new(3);
179 let config = IntegrationConfig::default();
180 let result = integrator.integrate(|x| x, 1.0, 0.0, &config);
181 assert!(result.is_err());
182 }
183}