copula_core/numerical/
mod.rs1use crate::{CopulaError, Result};
23
24pub fn bisection<F>(f: F, mut a: f64, mut b: f64, tol: f64, max_iter: usize) -> Result<f64>
39where
40 F: Fn(f64) -> f64,
41{
42 let mut fa = f(a);
43
44 if fa * f(b) > 0.0 {
45 return Err(CopulaError::numerical(
46 "bisection: f(a) and f(b) must have opposite signs",
47 ));
48 }
49
50 for _ in 0..max_iter {
51 let c = (a + b) / 2.0;
52 let fc = f(c);
53
54 if fc.abs() < tol || (b - a) / 2.0 < tol {
55 return Ok(c);
56 }
57
58 if fa * fc < 0.0 {
59 b = c;
60 } else {
61 a = c;
62 fa = fc;
63 }
64 }
65
66 Err(CopulaError::numerical(
67 "bisection: maximum iterations exceeded",
68 ))
69}
70
71pub fn trapezoid_integrate<F>(f: F, a: f64, b: f64, n: usize) -> f64
82where
83 F: Fn(f64) -> f64,
84{
85 let h = (b - a) / n as f64;
86 let mut sum = 0.5 * (f(a) + f(b));
87
88 for i in 1..n {
89 sum += f(a + i as f64 * h);
90 }
91
92 h * sum
93}
94
95pub fn simpson_integrate<F>(f: F, a: f64, b: f64, n: usize) -> f64
109where
110 F: Fn(f64) -> f64,
111{
112 assert!(n.is_multiple_of(2), "n must be even for Simpson's rule");
113
114 let h = (b - a) / n as f64;
115 let mut sum = f(a) + f(b);
116
117 for i in 1..n {
118 let x = a + i as f64 * h;
119 if i % 2 == 0 {
120 sum += 2.0 * f(x);
121 } else {
122 sum += 4.0 * f(x);
123 }
124 }
125
126 h * sum / 3.0
127}
128
129pub fn forward_diff<F>(f: F, x: f64, h: f64) -> f64
139where
140 F: Fn(f64) -> f64,
141{
142 (f(x + h) - f(x)) / h
143}
144
145pub fn central_diff<F>(f: F, x: f64, h: f64) -> f64
155where
156 F: Fn(f64) -> f64,
157{
158 (f(x + h) - f(x - h)) / (2.0 * h)
159}
160
161pub fn second_diff<F>(f: F, x: f64, h: f64) -> f64
171where
172 F: Fn(f64) -> f64,
173{
174 (f(x + h) - 2.0 * f(x) + f(x - h)) / (h * h)
175}
176
177pub fn linear_interp(x0: f64, y0: f64, x1: f64, y1: f64, x: f64) -> f64 {
187 y0 + (y1 - y0) * (x - x0) / (x1 - x0)
188}
189
190pub fn log_sum_exp(values: &[f64]) -> f64 {
200 if values.is_empty() {
201 return f64::NEG_INFINITY;
202 }
203
204 let max_val = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
205 if !max_val.is_finite() {
206 return max_val;
207 }
208
209 let sum: f64 = values.iter().map(|&x| (x - max_val).exp()).sum();
210 max_val + sum.ln()
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_bisection() {
219 let root = bisection(|x| x * x - 2.0, 0.0, 2.0, 1e-10, 100).unwrap();
221 assert!((root - 2.0_f64.sqrt()).abs() < 1e-9);
222 }
223
224 #[test]
225 fn test_trapezoid() {
226 let integral = trapezoid_integrate(|x| x * x, 0.0, 1.0, 1000);
228 assert!((integral - 1.0 / 3.0).abs() < 1e-6);
229 }
230
231 #[test]
232 fn test_simpson() {
233 let integral = simpson_integrate(|x| x * x * x, 0.0, 2.0, 100);
235 assert!((integral - 4.0).abs() < 1e-10);
236 }
237
238 #[test]
239 fn test_central_diff() {
240 let deriv = central_diff(|x| x * x, 3.0, 1e-5);
242 assert!((deriv - 6.0).abs() < 1e-8);
243 }
244
245 #[test]
246 fn test_log_sum_exp() {
247 let values = vec![1.0, 2.0, 3.0];
248 let result = log_sum_exp(&values);
249 let expected = (1.0_f64.exp() + 2.0_f64.exp() + 3.0_f64.exp()).ln();
250 assert!((result - expected).abs() < 1e-10);
251 }
252
253 #[test]
254 fn test_bisection_no_sign_change() {
255 let result = bisection(|x| x * x + 1.0, 0.0, 2.0, 1e-10, 100);
256 assert!(result.is_err());
257 }
258
259 #[test]
260 fn test_bisection_exact_root() {
261 let root = bisection(|x| x, -1.0, 1.0, 1e-10, 100).unwrap();
262 assert!(root.abs() < 1e-10);
263 }
264
265 #[test]
266 fn test_forward_diff() {
267 let deriv = forward_diff(|x| x * x, 3.0, 1e-7);
268 assert!((deriv - 6.0).abs() < 1e-5);
269 }
270
271 #[test]
272 fn test_second_diff() {
273 let deriv2 = second_diff(|x| x * x, 5.0, 1e-4);
275 assert!((deriv2 - 2.0).abs() < 1e-4);
276 }
277
278 #[test]
279 fn test_linear_interp() {
280 let y = linear_interp(0.0, 0.0, 1.0, 1.0, 0.5);
281 assert!((y - 0.5).abs() < 1e-12);
282
283 let y = linear_interp(0.0, 10.0, 10.0, 20.0, 5.0);
284 assert!((y - 15.0).abs() < 1e-12);
285 }
286
287 #[test]
288 fn test_log_sum_exp_empty() {
289 assert_eq!(log_sum_exp(&[]), f64::NEG_INFINITY);
290 }
291
292 #[test]
293 fn test_log_sum_exp_single() {
294 let result = log_sum_exp(&[5.0]);
295 assert!((result - 5.0).abs() < 1e-10);
296 }
297
298 #[test]
299 fn test_log_sum_exp_large_values() {
300 let result = log_sum_exp(&[1000.0, 1001.0]);
302 assert!(result.is_finite());
303 assert!(result > 1000.0);
304 }
305
306 #[test]
307 fn test_simpson_accuracy() {
308 let integral = simpson_integrate(|x| x.sin(), 0.0, std::f64::consts::PI, 1000);
310 assert!((integral - 2.0).abs() < 1e-8);
311 }
312
313 #[test]
314 fn test_trapezoid_accuracy() {
315 let integral = trapezoid_integrate(|x| x.exp(), 0.0, 1.0, 10000);
317 let expected = std::f64::consts::E - 1.0;
318 assert!((integral - expected).abs() < 1e-6);
319 }
320}