1use cjc_repro::KahanAccumulatorF64;
15
16pub fn trapezoid(xs: &[f64], ys: &[f64]) -> Result<f64, String> {
29 if xs.len() != ys.len() {
30 return Err(format!(
31 "trapezoid: xs and ys must have equal length, got {} and {}",
32 xs.len(),
33 ys.len()
34 ));
35 }
36 if xs.len() < 2 {
37 return Err("trapezoid: need at least 2 points".into());
38 }
39
40 let mut acc = KahanAccumulatorF64::new();
41 for i in 0..xs.len() - 1 {
42 let dx = xs[i + 1] - xs[i];
43 let term = dx * (ys[i] + ys[i + 1]) * 0.5;
44 acc.add(term);
45 }
46 Ok(acc.finalize())
47}
48
49pub fn simpson(xs: &[f64], ys: &[f64]) -> Result<f64, String> {
62 if xs.len() != ys.len() {
63 return Err(format!(
64 "simpson: xs and ys must have equal length, got {} and {}",
65 xs.len(),
66 ys.len()
67 ));
68 }
69 let n = xs.len();
70 if n < 3 {
71 return Err("simpson: need at least 3 points".into());
72 }
73 let intervals = n - 1;
74 if intervals % 2 != 0 {
75 return Err(format!(
76 "simpson: number of subintervals must be even, got {}",
77 intervals
78 ));
79 }
80
81 let mut acc = KahanAccumulatorF64::new();
82 let mut i = 0;
83 while i + 2 < n {
84 let h = (xs[i + 2] - xs[i]) / 6.0;
85 let term = h * (ys[i] + 4.0 * ys[i + 1] + ys[i + 2]);
86 acc.add(term);
87 i += 2;
88 }
89 Ok(acc.finalize())
90}
91
92pub fn cumtrapz(xs: &[f64], ys: &[f64]) -> Result<Vec<f64>, String> {
103 if xs.len() != ys.len() {
104 return Err(format!(
105 "cumtrapz: xs and ys must have equal length, got {} and {}",
106 xs.len(),
107 ys.len()
108 ));
109 }
110 if xs.len() < 2 {
111 return Err("cumtrapz: need at least 2 points".into());
112 }
113
114 let mut acc = KahanAccumulatorF64::new();
115 let mut result = Vec::with_capacity(xs.len() - 1);
116 for i in 0..xs.len() - 1 {
117 let dx = xs[i + 1] - xs[i];
118 let term = dx * (ys[i] + ys[i + 1]) * 0.5;
119 acc.add(term);
120 result.push(acc.finalize());
121 }
122 Ok(result)
123}
124
125#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_trapezoid_constant() {
135 let xs: Vec<f64> = (0..=100).map(|i| i as f64 * 0.04).collect();
137 let ys: Vec<f64> = xs.iter().map(|_| 3.0).collect();
138 let result = trapezoid(&xs, &ys).unwrap();
139 assert!((result - 12.0).abs() < 1e-12);
140 }
141
142 #[test]
143 fn test_trapezoid_linear() {
144 let n = 100;
146 let xs: Vec<f64> = (0..=n).map(|i| i as f64 / n as f64).collect();
147 let ys: Vec<f64> = xs.clone();
148 let result = trapezoid(&xs, &ys).unwrap();
149 assert!((result - 0.5).abs() < 1e-12);
150 }
151
152 #[test]
153 fn test_trapezoid_sin() {
154 let n = 10000;
156 let xs: Vec<f64> = (0..=n)
157 .map(|i| i as f64 * std::f64::consts::PI / n as f64)
158 .collect();
159 let ys: Vec<f64> = xs.iter().map(|&x| x.sin()).collect();
160 let result = trapezoid(&xs, &ys).unwrap();
161 assert!(
162 (result - 2.0).abs() < 1e-6,
163 "trapezoid sin: expected ~2.0, got {}",
164 result
165 );
166 }
167
168 #[test]
169 fn test_simpson_quadratic() {
170 let n = 100; let xs: Vec<f64> = (0..=n).map(|i| i as f64 / n as f64).collect();
173 let ys: Vec<f64> = xs.iter().map(|&x| x * x).collect();
174 let result = simpson(&xs, &ys).unwrap();
175 assert!(
176 (result - 1.0 / 3.0).abs() < 1e-10,
177 "simpson x^2: expected ~0.333, got {}",
178 result
179 );
180 }
181
182 #[test]
183 fn test_simpson_odd_intervals_error() {
184 let xs = vec![0.0, 1.0, 2.0, 3.0];
185 let ys = vec![0.0, 1.0, 4.0, 9.0];
186 assert!(simpson(&xs, &ys).is_err());
187 }
188
189 #[test]
190 fn test_cumtrapz_linear() {
191 let xs = vec![0.0, 1.0, 2.0, 3.0, 4.0];
193 let ys = vec![0.0, 1.0, 2.0, 3.0, 4.0];
194 let result = cumtrapz(&xs, &ys).unwrap();
195 assert_eq!(result.len(), 4);
196 assert!((result[0] - 0.5).abs() < 1e-12);
197 assert!((result[1] - 2.0).abs() < 1e-12);
198 assert!((result[2] - 4.5).abs() < 1e-12);
199 assert!((result[3] - 8.0).abs() < 1e-12);
200 }
201
202 #[test]
203 fn test_length_mismatch_error() {
204 assert!(trapezoid(&[0.0, 1.0], &[0.0]).is_err());
205 assert!(simpson(&[0.0, 1.0], &[0.0]).is_err());
206 assert!(cumtrapz(&[0.0, 1.0], &[0.0]).is_err());
207 }
208
209 #[test]
210 fn test_determinism() {
211 let n = 10000;
212 let xs: Vec<f64> = (0..=n)
213 .map(|i| i as f64 * std::f64::consts::PI / n as f64)
214 .collect();
215 let ys: Vec<f64> = xs.iter().map(|&x| x.sin()).collect();
216 let r1 = trapezoid(&xs, &ys).unwrap();
217 let r2 = trapezoid(&xs, &ys).unwrap();
218 assert_eq!(r1.to_bits(), r2.to_bits());
219 }
220}