gauss_quad/simpson/
mod.rs1#[cfg(feature = "rayon")]
38use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
39
40use crate::{Node, __impl_node_rule};
41
42use std::backtrace::Backtrace;
43
44#[derive(Debug, Clone, PartialEq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct Simpson {
58 nodes: Vec<Node>,
60}
61
62impl Simpson {
63 pub fn new(degree: usize) -> Result<Self, SimpsonError> {
69 if degree >= 1 {
70 Ok(Self {
71 nodes: (0..degree).map(|d| d as f64).collect(),
72 })
73 } else {
74 Err(SimpsonError::new())
75 }
76 }
77
78 pub fn integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
80 where
81 F: Fn(f64) -> f64,
82 {
83 let n = self.nodes.len() as f64;
84
85 let h = (b - a) / n;
86
87 let sum_over_interval_edges: f64 = self
89 .nodes
90 .iter()
91 .skip(1)
92 .map(|&node| integrand(a + node * h))
93 .sum();
94
95 let sum_over_midpoints: f64 = self
98 .nodes
99 .iter()
100 .skip(1)
101 .map(|&node| integrand(a + (2.0 * node - 1.0) * h / 2.0))
102 .sum();
103
104 h / 6.0
105 * (2.0 * sum_over_interval_edges
106 + 4.0 * sum_over_midpoints
107 + 4.0 * integrand(a + (2.0 * n - 1.0) * h / 2.0)
108 + integrand(a)
109 + integrand(b))
110 }
111
112 #[cfg(feature = "rayon")]
113 pub fn par_integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
115 where
116 F: Fn(f64) -> f64 + Sync,
117 {
118 let n = self.nodes.len() as f64;
119
120 let h = (b - a) / n;
121
122 let (sum_over_interval_edges, sum_over_midpoints): (f64, f64) = rayon::join(
123 || {
124 self.nodes
125 .par_iter()
126 .skip(1)
127 .map(|&node| integrand(a + node * h))
128 .sum::<f64>()
129 },
130 || {
131 self.nodes
132 .par_iter()
133 .skip(1)
134 .map(|&node| integrand(a + (2.0 * node - 1.0) * h / 2.0))
135 .sum::<f64>()
136 },
137 );
138
139 h / 6.0
140 * (2.0 * sum_over_interval_edges
141 + 4.0 * sum_over_midpoints
142 + 4.0 * integrand(a + (2.0 * n - 1.0) * h / 2.0)
143 + integrand(a)
144 + integrand(b))
145 }
146}
147
148__impl_node_rule! {Simpson, SimpsonIter, SimpsonIntoIter}
149
150#[derive(Debug)]
152pub struct SimpsonError(Backtrace);
153
154use core::fmt;
155impl fmt::Display for SimpsonError {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(f, "the degree of the Simpson rule must be at least 1.")
158 }
159}
160
161impl SimpsonError {
162 fn new() -> Self {
164 Self(Backtrace::capture())
165 }
166
167 #[inline]
171 pub fn backtrace(&self) -> &Backtrace {
172 &self.0
173 }
174}
175
176impl std::error::Error for SimpsonError {}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn check_simpson_integration() {
184 let quad = Simpson::new(2).unwrap();
185 let integral = quad.integrate(0.0, 1.0, |x| x * x);
186 approx::assert_abs_diff_eq!(integral, 1.0 / 3.0, epsilon = 0.0001);
187 }
188
189 #[cfg(feature = "rayon")]
190 #[test]
191 fn par_check_simpson_integration() {
192 let quad = Simpson::new(2).unwrap();
193 let integral = quad.par_integrate(0.0, 1.0, |x| x * x);
194 approx::assert_abs_diff_eq!(integral, 1.0 / 3.0, epsilon = 0.0001);
195 }
196
197 #[test]
198 fn check_simpson_error() {
199 let simpson_rule = Simpson::new(0);
200 assert!(simpson_rule.is_err());
201 assert_eq!(
202 format!("{}", simpson_rule.err().unwrap()),
203 "the degree of the Simpson rule must be at least 1."
204 );
205 }
206
207 #[test]
208 fn check_derives() {
209 let quad = Simpson::new(10).unwrap();
210 let quad_clone = quad.clone();
211 assert_eq!(quad, quad_clone);
212 let other_quad = Simpson::new(3).unwrap();
213 assert_ne!(quad, other_quad);
214 }
215}