1#![allow(dead_code)]
9
10use std::f64::consts::FRAC_1_SQRT_2;
11
12pub struct GaussLegendreQuad {
16 pub n: usize,
18 pub nodes: Vec<f64>,
20 pub weights: Vec<f64>,
22}
23
24impl GaussLegendreQuad {
25 pub fn new(n: usize) -> Self {
27 let (nodes, weights) = gauss_legendre_nodes_weights(n);
28 Self { n, nodes, weights }
29 }
30
31 pub fn integrate<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> f64 {
33 let mid = 0.5 * (a + b);
34 let half = 0.5 * (b - a);
35 self.nodes
36 .iter()
37 .zip(self.weights.iter())
38 .map(|(&x, &w)| w * f(mid + half * x))
39 .sum::<f64>()
40 * half
41 }
42
43 pub fn integrate_composite<F: Fn(f64) -> f64>(&self, a: f64, b: f64, m: usize, f: &F) -> f64 {
45 let h = (b - a) / m as f64;
46 (0..m)
47 .map(|i| {
48 let ai = a + i as f64 * h;
49 let bi = ai + h;
50 self.integrate(ai, bi, f)
51 })
52 .sum()
53 }
54}
55
56fn gauss_legendre_nodes_weights(n: usize) -> (Vec<f64>, Vec<f64>) {
59 match n {
60 1 => (vec![0.0], vec![2.0]),
61 2 => (
62 vec![-0.5773502691896257, 0.5773502691896257],
63 vec![1.0, 1.0],
64 ),
65 3 => (
66 vec![-0.7745966692414834, 0.0, 0.7745966692414834],
67 vec![0.5555555555555556, 0.8888888888888888, 0.5555555555555556],
68 ),
69 4 => (
70 vec![
71 -0.8611363115940526,
72 -0.3399810435848563,
73 0.3399810435848563,
74 0.8611363115940526,
75 ],
76 vec![
77 0.3478548451374538,
78 0.6521451548625461,
79 0.6521451548625461,
80 0.3478548451374538,
81 ],
82 ),
83 5 => (
84 vec![
85 -0.906_179_845_938_664,
86 -0.5384693101056831,
87 0.0,
88 0.5384693101056831,
89 0.906_179_845_938_664,
90 ],
91 vec![
92 0.2369268850561891,
93 0.4786286704993665,
94 0.5688888888888889,
95 0.4786286704993665,
96 0.2369268850561891,
97 ],
98 ),
99 6 => (
100 vec![
101 -0.932_469_514_203_152,
102 -0.6612093864662645,
103 -0.2386191860831969,
104 0.2386191860831969,
105 0.6612093864662645,
106 0.932_469_514_203_152,
107 ],
108 vec![
109 0.1713244923791704,
110 0.3607615730481386,
111 0.467_913_934_572_691,
112 0.467_913_934_572_691,
113 0.3607615730481386,
114 0.1713244923791704,
115 ],
116 ),
117 7 => (
118 vec![
119 -0.9491079123427585,
120 -0.7415311855993945,
121 -0.4058451513773972,
122 0.0,
123 0.4058451513773972,
124 0.7415311855993945,
125 0.9491079123427585,
126 ],
127 vec![
128 0.1294849661688697,
129 0.2797053914892767,
130 0.3818300505051189,
131 0.4179591836734694,
132 0.3818300505051189,
133 0.2797053914892767,
134 0.1294849661688697,
135 ],
136 ),
137 8 => (
138 vec![
139 -0.9602898564975363,
140 -0.7966664774136267,
141 -0.525_532_409_916_329,
142 -0.1834346424956498,
143 0.1834346424956498,
144 0.525_532_409_916_329,
145 0.7966664774136267,
146 0.9602898564975363,
147 ],
148 vec![
149 0.1012285362903763,
150 0.2223810344533745,
151 0.3137066458778873,
152 0.362_683_783_378_362,
153 0.362_683_783_378_362,
154 0.3137066458778873,
155 0.2223810344533745,
156 0.1012285362903763,
157 ],
158 ),
159 9 => (
160 vec![
161 -0.9681602395076261,
162 -0.8360311073266358,
163 -0.6133714327005904,
164 -0.3242534234038089,
165 0.0,
166 0.3242534234038089,
167 0.6133714327005904,
168 0.8360311073266358,
169 0.9681602395076261,
170 ],
171 vec![
172 0.0812743883615744,
173 0.1806481606948574,
174 0.2606106964029354,
175 0.3123470770400029,
176 0.3302393550012598,
177 0.3123470770400029,
178 0.2606106964029354,
179 0.1806481606948574,
180 0.0812743883615744,
181 ],
182 ),
183 _ => gauss_legendre_gw(n),
184 }
185}
186
187fn gauss_legendre_gw(n: usize) -> (Vec<f64>, Vec<f64>) {
190 let mut nodes = Vec::with_capacity(n);
191 let mut weights = Vec::with_capacity(n);
192 let m = n.div_ceil(2);
193 for i in 0..m {
194 let theta = std::f64::consts::PI * (i as f64 + 0.75) / (n as f64 + 0.5);
196 let mut x = theta.cos();
197 for _ in 0..100 {
199 let (p, dp) = legendre_pn_dpn(n, x);
200 let dx = -p / dp;
201 x += dx;
202 if dx.abs() < 1e-15 {
203 break;
204 }
205 }
206 let (_, dp) = legendre_pn_dpn(n, x);
207 let w = 2.0 / ((1.0 - x * x) * dp * dp);
208 nodes.push(-x);
209 weights.push(w);
210 }
211 let mut nodes_full: Vec<f64> = nodes.clone();
213 let mut weights_full: Vec<f64> = weights.clone();
214 let start = if n % 2 == 1 { m - 1 } else { m };
215 for i in (0..start).rev() {
216 nodes_full.push(-nodes[i]);
217 weights_full.push(weights[i]);
218 }
219 nodes_full.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
220 let mut result_nodes = Vec::with_capacity(n);
222 let mut result_weights = Vec::with_capacity(n);
223 for &x in &nodes_full {
224 let (_, dp) = legendre_pn_dpn(n, x);
225 let w = 2.0 / ((1.0 - x * x) * dp * dp);
226 result_nodes.push(x);
227 result_weights.push(w);
228 }
229 (result_nodes, result_weights)
230}
231
232fn legendre_pn_dpn(n: usize, x: f64) -> (f64, f64) {
234 let mut p0 = 1.0_f64;
235 let mut p1 = x;
236 for k in 1..n {
237 let kf = k as f64;
238 let p2 = ((2.0 * kf + 1.0) * x * p1 - kf * p0) / (kf + 1.0);
239 p0 = p1;
240 p1 = p2;
241 }
242 if n == 0 {
243 (1.0, 0.0)
244 } else {
245 let pn = p1;
246 let nf = n as f64;
247 let dpn = nf * (p0 - x * p1) / (1.0 - x * x);
248 (pn, dpn)
249 }
250}
251
252pub struct GaussHermiteQuad {
254 pub n: usize,
256 pub nodes: Vec<f64>,
258 pub weights: Vec<f64>,
260}
261
262impl GaussHermiteQuad {
263 pub fn new(n: usize) -> Self {
265 let (nodes, weights) = gauss_hermite_nw(n);
266 Self { n, nodes, weights }
267 }
268
269 pub fn integrate<F: Fn(f64) -> f64>(&self, f: &F) -> f64 {
271 self.nodes
272 .iter()
273 .zip(self.weights.iter())
274 .map(|(&x, &w)| w * f(x))
275 .sum()
276 }
277}
278
279fn gauss_hermite_nw(n: usize) -> (Vec<f64>, Vec<f64>) {
282 match n {
283 2 => (
284 vec![-FRAC_1_SQRT_2, FRAC_1_SQRT_2],
285 vec![0.886_226_925_452_758, 0.886_226_925_452_758],
286 ),
287 3 => (
288 vec![-1.224_744_871_391_589, 0.0, 1.224_744_871_391_589],
289 vec![0.2954089751509193, 1.1816359006036772, 0.2954089751509193],
290 ),
291 4 => (
292 vec![
293 -1.6506801238857845,
294 -0.5246476232752903,
295 0.5246476232752903,
296 1.6506801238857845,
297 ],
298 vec![
299 0.08131283544724518,
300 0.8049140900055128,
301 0.8049140900055128,
302 0.08131283544724518,
303 ],
304 ),
305 5 => (
306 vec![
307 -2.0201828704560856,
308 -0.9585724646138185,
309 0.0,
310 0.9585724646138185,
311 2.0201828704560856,
312 ],
313 vec![
314 0.01995324205904592,
315 0.3936193231522412,
316 0.9453087204829419,
317 0.3936193231522412,
318 0.01995324205904592,
319 ],
320 ),
321 _ => gauss_hermite_iter(n),
322 }
323}
324
325fn gauss_hermite_iter(n: usize) -> (Vec<f64>, Vec<f64>) {
328 let nf = n as f64;
329 let m = n.div_ceil(2);
330 let mut nodes = Vec::with_capacity(n);
331 let mut weights = Vec::with_capacity(n);
332 for i in 0..m {
333 let mut x = (2.0 * nf + 1.0).sqrt()
335 - 1.85575
336 * (2.0 * nf + 1.0_f64).powf(-1.0 / 6.0)
337 * ((i as f64 + 1.0) * std::f64::consts::PI / (nf + 0.5)).cos();
338 for _ in 0..100 {
339 let (hn, hn1) = hermite_hn_hn1(n, x);
340 let dx = -hn / (2.0 * nf * hn1);
341 x += dx;
342 if dx.abs() < 1e-14 {
343 break;
344 }
345 }
346 let (_, hn1) = hermite_hn_hn1(n, x);
347 let w = (2.0_f64.powi(n as i32 - 1) * factorial_approx(n) * std::f64::consts::PI.sqrt())
348 / (nf * nf * hn1 * hn1);
349 nodes.push(-x);
350 weights.push(w);
351 }
352 let start = if n % 2 == 1 { m - 1 } else { m };
353 let mut full_nodes = nodes.clone();
354 let mut full_weights = weights.clone();
355 for i in (0..start).rev() {
356 full_nodes.push(-nodes[i]);
357 full_weights.push(weights[i]);
358 }
359 let mut pairs: Vec<(f64, f64)> = full_nodes.into_iter().zip(full_weights).collect();
360 pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
361 let (sorted_nodes, sorted_weights) = pairs.into_iter().unzip();
362 (sorted_nodes, sorted_weights)
363}
364
365fn hermite_hn_hn1(n: usize, x: f64) -> (f64, f64) {
367 let mut h0 = 1.0_f64;
368 let mut h1 = 2.0 * x;
369 if n == 0 {
370 return (h0, 0.0);
371 }
372 if n == 1 {
373 return (h1, h0);
374 }
375 for k in 1..n {
376 let kf = k as f64;
377 let h2 = 2.0 * x * h1 - 2.0 * kf * h0;
378 h0 = h1;
379 h1 = h2;
380 }
381 (h1, h0)
382}
383
384fn factorial_approx(n: usize) -> f64 {
386 (1..=n).map(|k| k as f64).product()
387}
388
389pub struct TrapezoidalRule {
391 pub tol: f64,
393 pub max_levels: usize,
395}
396
397impl TrapezoidalRule {
398 pub fn new(tol: f64, max_levels: usize) -> Self {
400 Self { tol, max_levels }
401 }
402
403 pub fn integrate<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> f64 {
405 let mut h = b - a;
406 let mut t = 0.5 * h * (f(a) + f(b));
407 let mut prev = t;
408 for k in 1..=self.max_levels {
409 let n = 1usize << k;
410 let new_h = h / 2.0;
411 let sum_mid: f64 = (0..n / 2).map(|i| f(a + (2 * i + 1) as f64 * new_h)).sum();
413 t = 0.5 * t + new_h * sum_mid;
414 h = new_h;
415 let t_rich = (4.0 * t - prev) / 3.0;
417 if (t_rich - prev).abs() < self.tol * (1.0 + t_rich.abs()) {
418 return t_rich;
419 }
420 prev = t;
421 }
422 t
423 }
424}
425
426pub struct SimpsonRule {
428 pub tol: f64,
430 pub max_depth: usize,
432}
433
434impl SimpsonRule {
435 pub fn new(tol: f64, max_depth: usize) -> Self {
437 Self { tol, max_depth }
438 }
439
440 pub fn integrate<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> f64 {
442 let fa = f(a);
443 let fb = f(b);
444 let fm = f(0.5 * (a + b));
445 let s = simpson13(a, b, fa, fm, fb);
446 self.recursive(a, b, fa, fm, fb, s, self.tol, self.max_depth, f)
447 }
448
449 #[allow(clippy::too_many_arguments)]
450 fn recursive<F: Fn(f64) -> f64>(
451 &self,
452 a: f64,
453 b: f64,
454 fa: f64,
455 fm: f64,
456 fb: f64,
457 s: f64,
458 tol: f64,
459 depth: usize,
460 f: &F,
461 ) -> f64 {
462 let mid = 0.5 * (a + b);
463 let fml = f(0.5 * (a + mid));
464 let fmr = f(0.5 * (mid + b));
465 let sl = simpson13(a, mid, fa, fml, fm);
466 let sr = simpson13(mid, b, fm, fmr, fb);
467 let err = ((sl + sr) - s).abs() / 15.0;
468 if depth == 0 || err < tol {
469 sl + sr + err
470 } else {
471 self.recursive(a, mid, fa, fml, fm, sl, tol / 2.0, depth - 1, f)
472 + self.recursive(mid, b, fm, fmr, fb, sr, tol / 2.0, depth - 1, f)
473 }
474 }
475
476 pub fn integrate38<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> f64 {
478 let h = (b - a) / 3.0;
479 (3.0 * h / 8.0) * (f(a) + 3.0 * f(a + h) + 3.0 * f(a + 2.0 * h) + f(b))
480 }
481}
482
483#[inline]
484fn simpson13(a: f64, b: f64, fa: f64, fm: f64, fb: f64) -> f64 {
485 (b - a) / 6.0 * (fa + 4.0 * fm + fb)
486}
487
488pub struct RombergIntegration {
490 pub tol: f64,
492 pub max_rows: usize,
494}
495
496impl RombergIntegration {
497 pub fn new(tol: f64, max_rows: usize) -> Self {
499 Self { tol, max_rows }
500 }
501
502 pub fn integrate<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> (f64, Vec<Vec<f64>>) {
504 let mut table: Vec<Vec<f64>> = Vec::new();
505 let mut h = b - a;
506 table.push(vec![0.5 * h * (f(a) + f(b))]);
508
509 for k in 1..self.max_rows {
510 let n = 1usize << k;
511 let new_h = h / 2.0;
512 let sum: f64 = (0..n / 2).map(|i| f(a + (2 * i + 1) as f64 * new_h)).sum();
513 let t = 0.5 * table[k - 1][0] + new_h * sum;
514 h = new_h;
515
516 let mut row = vec![t];
517 for j in 1..=k {
518 let prev = row[j - 1];
519 let older = table[k - 1][j - 1];
520 let fac = (4u64.pow(j as u32)) as f64;
521 row.push((fac * prev - older) / (fac - 1.0));
522 }
523
524 let last = *row.last().expect("row is non-empty");
525 let prev_last = *table[k - 1].last().expect("table row is non-empty");
526 table.push(row);
527
528 if (last - prev_last).abs() < self.tol * (1.0 + last.abs()) {
529 return (last, table);
530 }
531 }
532 let last = *table
533 .last()
534 .expect("table is non-empty")
535 .last()
536 .expect("row is non-empty");
537 (last, table)
538 }
539}
540
541pub struct GaussKronrod {
543 pub tol: f64,
545 pub max_depth: usize,
547}
548
549impl GaussKronrod {
550 const K15_NODES: [f64; 15] = [
552 -0.991_455_371_120_813,
553 -0.949_107_912_342_758,
554 -0.864_864_423_359_769,
555 -0.741_531_185_599_394,
556 -0.586_087_235_467_691,
557 -0.405_845_151_377_397,
558 -0.207_784_955_007_898,
559 0.0,
560 0.207_784_955_007_898,
561 0.405_845_151_377_397,
562 0.586_087_235_467_691,
563 0.741_531_185_599_394,
564 0.864_864_423_359_769,
565 0.949_107_912_342_758,
566 0.991_455_371_120_813,
567 ];
568
569 const K15_WEIGHTS: [f64; 15] = [
571 0.022_935_322_010_529,
572 0.063_092_092_629_979,
573 0.104_790_010_322_250,
574 0.140_653_259_715_525,
575 0.169_004_726_639_267,
576 0.190_350_578_064_785,
577 0.204_432_940_075_298,
578 0.209_482_141_084_728,
579 0.204_432_940_075_298,
580 0.190_350_578_064_785,
581 0.169_004_726_639_267,
582 0.140_653_259_715_525,
583 0.104_790_010_322_250,
584 0.063_092_092_629_979,
585 0.022_935_322_010_529,
586 ];
587
588 const G7_WEIGHTS: [f64; 7] = [
590 0.129_484_966_168_870,
591 0.279_705_391_489_277,
592 0.381_830_050_505_119,
593 0.417_959_183_673_469,
594 0.381_830_050_505_119,
595 0.279_705_391_489_277,
596 0.129_484_966_168_870,
597 ];
598
599 const G7_IDX: [usize; 7] = [1, 3, 5, 7, 9, 11, 13];
601
602 pub fn new(tol: f64, max_depth: usize) -> Self {
604 Self { tol, max_depth }
605 }
606
607 pub fn integrate<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F) -> f64 {
609 self.adaptive(a, b, f, self.max_depth)
610 }
611
612 fn adaptive<F: Fn(f64) -> f64>(&self, a: f64, b: f64, f: &F, depth: usize) -> f64 {
613 let mid = 0.5 * (a + b);
614 let half = 0.5 * (b - a);
615 let fvals: Vec<f64> = Self::K15_NODES.iter().map(|&x| f(mid + half * x)).collect();
616
617 let k15: f64 = Self::K15_WEIGHTS
618 .iter()
619 .zip(fvals.iter())
620 .map(|(&w, &fv)| w * fv)
621 .sum::<f64>()
622 * half;
623
624 let g7: f64 = Self::G7_IDX
625 .iter()
626 .zip(Self::G7_WEIGHTS.iter())
627 .map(|(&idx, &w)| w * fvals[idx])
628 .sum::<f64>()
629 * half;
630
631 let err = (k15 - g7).abs();
632 if depth == 0 || err < self.tol * (1.0 + k15.abs()) {
633 k15
634 } else {
635 self.adaptive(a, mid, f, depth - 1) + self.adaptive(mid, b, f, depth - 1)
636 }
637 }
638}
639
640#[derive(Debug, Clone, Copy)]
642pub enum RbfKind {
643 Multiquadric,
645 InverseMultiquadric,
647 Gaussian,
649 ThinPlateSpline,
651}
652
653pub struct RadialBasisFunction {
655 pub kind: RbfKind,
657 pub shape: f64,
659 pub centers: Vec<Vec<f64>>,
661 pub coeffs: Vec<f64>,
663}
664
665impl RadialBasisFunction {
666 pub fn fit(kind: RbfKind, shape: f64, points: &[Vec<f64>], values: &[f64]) -> Self {
671 let n = points.len();
672 let mut k_mat = vec![0.0f64; n * n];
674 for i in 0..n {
675 for j in 0..n {
676 let r = euclidean_dist(&points[i], &points[j]);
677 k_mat[i * n + j] = rbf_kernel(kind, r, shape);
678 }
679 }
680 let coeffs = solve_linear_system(&k_mat, values, n);
682 Self {
683 kind,
684 shape,
685 centers: points.to_vec(),
686 coeffs,
687 }
688 }
689
690 pub fn eval(&self, x: &[f64]) -> f64 {
692 self.centers
693 .iter()
694 .zip(self.coeffs.iter())
695 .map(|(c, &alpha)| {
696 let r = euclidean_dist(x, c);
697 alpha * rbf_kernel(self.kind, r, self.shape)
698 })
699 .sum()
700 }
701}
702
703fn euclidean_dist(a: &[f64], b: &[f64]) -> f64 {
704 a.iter()
705 .zip(b.iter())
706 .map(|(&ai, &bi)| (ai - bi).powi(2))
707 .sum::<f64>()
708 .sqrt()
709}
710
711fn rbf_kernel(kind: RbfKind, r: f64, c: f64) -> f64 {
712 match kind {
713 RbfKind::Multiquadric => (r * r + c * c).sqrt(),
714 RbfKind::InverseMultiquadric => 1.0 / (r * r + c * c).sqrt(),
715 RbfKind::Gaussian => (-(r * r) / (2.0 * c * c)).exp(),
716 RbfKind::ThinPlateSpline => {
717 if r < 1e-14 {
718 0.0
719 } else {
720 r * r * r.ln()
721 }
722 }
723 }
724}
725
726pub fn solve_linear_system(a_flat: &[f64], b: &[f64], n: usize) -> Vec<f64> {
728 let mut mat = a_flat.to_vec();
729 let mut rhs = b.to_vec();
730
731 for col in 0..n {
732 let mut max_row = col;
734 let mut max_val = mat[col * n + col].abs();
735 for row in (col + 1)..n {
736 let v = mat[row * n + col].abs();
737 if v > max_val {
738 max_val = v;
739 max_row = row;
740 }
741 }
742 if max_row != col {
743 for k in 0..n {
744 mat.swap(col * n + k, max_row * n + k);
745 }
746 rhs.swap(col, max_row);
747 }
748 let pivot = mat[col * n + col];
749 if pivot.abs() < 1e-14 {
750 continue;
751 }
752 for row in (col + 1)..n {
753 let factor = mat[row * n + col] / pivot;
754 for k in col..n {
755 let v = mat[col * n + k];
756 mat[row * n + k] -= factor * v;
757 }
758 rhs[row] -= factor * rhs[col];
759 }
760 }
761 let mut x = vec![0.0f64; n];
763 for i in (0..n).rev() {
764 let mut s = rhs[i];
765 for j in (i + 1)..n {
766 s -= mat[i * n + j] * x[j];
767 }
768 x[i] = s / mat[i * n + i];
769 }
770 x
771}
772
773pub struct SplineInterpolation {
775 pub xs: Vec<f64>,
777 pub ys: Vec<f64>,
779 pub m: Vec<f64>,
781 pub kind: SplineKind,
783}
784
785#[derive(Debug, Clone, Copy)]
787pub enum SplineKind {
788 Natural,
790 Clamped(f64, f64),
792 NotAKnot,
794}
795
796impl SplineInterpolation {
797 pub fn fit(xs: &[f64], ys: &[f64], kind: SplineKind) -> Self {
799 let n = xs.len();
800 assert!(n >= 3, "Need at least 3 points for cubic spline");
801 let m = compute_spline_second_derivs(xs, ys, kind);
802 Self {
803 xs: xs.to_vec(),
804 ys: ys.to_vec(),
805 m,
806 kind,
807 }
808 }
809
810 pub fn eval(&self, x: f64) -> f64 {
812 let n = self.xs.len();
813 let mut idx = 0;
815 for i in 1..n - 1 {
816 if x >= self.xs[i] {
817 idx = i;
818 }
819 }
820 let h = self.xs[idx + 1] - self.xs[idx];
821 let t = (x - self.xs[idx]) / h;
822 let a = self.ys[idx];
823 let b_val = self.ys[idx + 1];
824 let ma = self.m[idx];
825 let mb = self.m[idx + 1];
826 (1.0 - t) * a
828 + t * b_val
829 + t * (1.0 - t)
830 * ((1.0 - t) * h * h * ma / 6.0 - t * h * h * mb / 6.0 - h * h * (ma - mb) / 6.0
831 + h * h * ma / 6.0 * (2.0 * t - 1.0))
832 }
833
834 pub fn eval_cubic(&self, x: f64) -> f64 {
836 let n = self.xs.len();
837 let mut idx = 0;
838 for i in 1..n - 1 {
839 if x >= self.xs[i] {
840 idx = i;
841 }
842 }
843 let h = self.xs[idx + 1] - self.xs[idx];
844 let dx = x - self.xs[idx];
845 let dx2 = self.xs[idx + 1] - x;
846 let a = self.ys[idx];
847 let b_val = self.ys[idx + 1];
848 let ma = self.m[idx];
849 let mb = self.m[idx + 1];
850 (dx2 / h) * a
851 + (dx / h) * b_val
852 + (dx2 / h) * ((dx2 * dx2 / (h * h) - 1.0) * h * h * ma / 6.0)
853 + (dx / h) * ((dx * dx / (h * h) - 1.0) * h * h * mb / 6.0)
854 }
855}
856
857fn compute_spline_second_derivs(xs: &[f64], ys: &[f64], kind: SplineKind) -> Vec<f64> {
859 let n = xs.len();
860 let mut h = vec![0.0f64; n - 1];
861 for i in 0..n - 1 {
862 h[i] = xs[i + 1] - xs[i];
863 }
864
865 let mut diag = vec![0.0f64; n];
867 let mut upper = vec![0.0f64; n];
868 let mut lower = vec![0.0f64; n];
869 let mut rhs = vec![0.0f64; n];
870
871 for i in 1..n - 1 {
872 lower[i] = h[i - 1];
873 diag[i] = 2.0 * (h[i - 1] + h[i]);
874 upper[i] = h[i];
875 rhs[i] = 6.0 * ((ys[i + 1] - ys[i]) / h[i] - (ys[i] - ys[i - 1]) / h[i - 1]);
876 }
877
878 match kind {
879 SplineKind::Natural => {
880 diag[0] = 1.0;
881 upper[0] = 0.0;
882 rhs[0] = 0.0;
883 diag[n - 1] = 1.0;
884 lower[n - 1] = 0.0;
885 rhs[n - 1] = 0.0;
886 }
887 SplineKind::Clamped(d0, dn) => {
888 diag[0] = 2.0 * h[0];
889 upper[0] = h[0];
890 rhs[0] = 6.0 * ((ys[1] - ys[0]) / h[0] - d0);
891 diag[n - 1] = 2.0 * h[n - 2];
892 lower[n - 1] = h[n - 2];
893 rhs[n - 1] = 6.0 * (dn - (ys[n - 1] - ys[n - 2]) / h[n - 2]);
894 }
895 SplineKind::NotAKnot => {
896 diag[0] = h[1];
898 upper[0] = -(h[0] + h[1]);
899 rhs[0] = 0.0;
900 diag[n - 1] = h[n - 3];
902 lower[n - 1] = -(h[n - 3] + h[n - 2]);
903 rhs[n - 1] = 0.0;
904 }
905 }
906
907 thomas_algorithm(&diag, &lower, &upper, &rhs, n)
909}
910
911fn thomas_algorithm(diag: &[f64], lower: &[f64], upper: &[f64], rhs: &[f64], n: usize) -> Vec<f64> {
913 let mut c = vec![0.0f64; n];
914 let mut d = rhs.to_vec();
915 let mut w = vec![0.0f64; n];
916
917 w[0] = upper[0] / diag[0];
918 d[0] /= diag[0];
919
920 for i in 1..n {
921 let denom = diag[i] - lower[i] * w[i - 1];
922 if denom.abs() < 1e-14 {
923 w[i] = 0.0;
924 d[i] = 0.0;
925 continue;
926 }
927 w[i] = upper[i] / denom;
928 d[i] = (d[i] - lower[i] * d[i - 1]) / denom;
929 }
930
931 c[n - 1] = d[n - 1];
932 for i in (0..n - 1).rev() {
933 c[i] = d[i] - w[i] * c[i + 1];
934 }
935 c
936}
937
938pub struct BarycentricInterpolation {
940 pub nodes: Vec<f64>,
942 pub values: Vec<f64>,
944 pub weights: Vec<f64>,
946}
947
948impl BarycentricInterpolation {
949 pub fn fit(nodes: &[f64], values: &[f64]) -> Self {
952 let n = nodes.len();
953 let weights = compute_barycentric_weights(nodes, n);
954 Self {
955 nodes: nodes.to_vec(),
956 values: values.to_vec(),
957 weights,
958 }
959 }
960
961 pub fn eval(&self, x: f64) -> f64 {
963 let n = self.nodes.len();
964 for i in 0..n {
966 if (x - self.nodes[i]).abs() < 1e-14 {
967 return self.values[i];
968 }
969 }
970 let numer: f64 = (0..n)
971 .map(|i| self.weights[i] / (x - self.nodes[i]) * self.values[i])
972 .sum();
973 let denom: f64 = (0..n).map(|i| self.weights[i] / (x - self.nodes[i])).sum();
974 numer / denom
975 }
976}
977
978fn compute_barycentric_weights(nodes: &[f64], n: usize) -> Vec<f64> {
979 let mut w = vec![1.0f64; n];
980 for i in 0..n {
981 for j in 0..n {
982 if i != j {
983 w[i] /= nodes[i] - nodes[j];
984 }
985 }
986 }
987 w
988}
989
990pub struct NumericalDifferentiation {
992 pub h: f64,
994 pub levels: usize,
996}
997
998impl NumericalDifferentiation {
999 pub fn new(h: f64, levels: usize) -> Self {
1001 Self { h, levels }
1002 }
1003
1004 pub fn first_deriv<F: Fn(f64) -> f64>(&self, x: f64, f: &F) -> f64 {
1006 richardson_deriv(x, self.h, self.levels, f, 1)
1007 }
1008
1009 pub fn second_deriv<F: Fn(f64) -> f64>(&self, x: f64, f: &F) -> f64 {
1011 richardson_deriv2(x, self.h, self.levels, f)
1012 }
1013
1014 pub fn mixed_partial<F: Fn(f64, f64) -> f64>(&self, x: f64, y: f64, f: &F) -> f64 {
1016 let h = self.h;
1017 let fpp = f(x + h, y + h);
1018 let fpm = f(x + h, y - h);
1019 let fmp = f(x - h, y + h);
1020 let fmm = f(x - h, y - h);
1021 (fpp - fpm - fmp + fmm) / (4.0 * h * h)
1022 }
1023
1024 #[allow(clippy::too_many_arguments)]
1026 pub fn nth_deriv<F: Fn(f64) -> f64>(&self, x: f64, order: usize, f: &F) -> f64 {
1027 match order {
1028 1 => self.first_deriv(x, f),
1029 2 => self.second_deriv(x, f),
1030 3 => {
1031 let h = self.h;
1032 (f(x + 2.0 * h) - 2.0 * f(x + h) + 2.0 * f(x - h) - f(x - 2.0 * h))
1033 / (2.0 * h * h * h)
1034 }
1035 4 => {
1036 let h = self.h;
1037 (f(x + 2.0 * h) - 4.0 * f(x + h) + 6.0 * f(x) - 4.0 * f(x - h) + f(x - 2.0 * h))
1038 / (h * h * h * h)
1039 }
1040 _ => panic!("Only orders 1-4 supported"),
1041 }
1042 }
1043}
1044
1045fn richardson_deriv<F: Fn(f64) -> f64>(
1046 x: f64,
1047 h0: f64,
1048 levels: usize,
1049 f: &F,
1050 _order: usize,
1051) -> f64 {
1052 let mut table: Vec<Vec<f64>> = Vec::new();
1053 let mut h = h0;
1054 for k in 0..levels {
1055 let d = (f(x + h) - f(x - h)) / (2.0 * h);
1056 if k == 0 {
1057 table.push(vec![d]);
1058 } else {
1059 let mut row = vec![d];
1060 for j in 1..=k {
1061 let fac = (4u64.pow(j as u32)) as f64;
1062 let val = (fac * row[j - 1] - table[k - 1][j - 1]) / (fac - 1.0);
1063 row.push(val);
1064 }
1065 table.push(row);
1066 }
1067 h /= 2.0;
1068 }
1069 *table
1070 .last()
1071 .expect("table is non-empty")
1072 .last()
1073 .expect("row is non-empty")
1074}
1075
1076fn richardson_deriv2<F: Fn(f64) -> f64>(x: f64, h0: f64, levels: usize, f: &F) -> f64 {
1077 let mut table: Vec<Vec<f64>> = Vec::new();
1078 let mut h = h0;
1079 for k in 0..levels {
1080 let d = (f(x + h) - 2.0 * f(x) + f(x - h)) / (h * h);
1081 if k == 0 {
1082 table.push(vec![d]);
1083 } else {
1084 let mut row = vec![d];
1085 for j in 1..=k {
1086 let fac = (4u64.pow(j as u32)) as f64;
1087 let val = (fac * row[j - 1] - table[k - 1][j - 1]) / (fac - 1.0);
1088 row.push(val);
1089 }
1090 table.push(row);
1091 }
1092 h /= 2.0;
1093 }
1094 *table
1095 .last()
1096 .expect("table is non-empty")
1097 .last()
1098 .expect("row is non-empty")
1099}
1100
1101trait DiffExact {
1103 fn diff_exact(self) -> f64;
1104}
1105
1106impl DiffExact for f64 {
1107 fn diff_exact(self) -> f64 {
1108 3.0 * self * self
1109 }
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 use super::*;
1115
1116 #[test]
1119 fn test_gl_exact_degree3() {
1120 let gl = GaussLegendreQuad::new(2);
1122 let result = gl.integrate(0.0, 1.0, &|x| x * x * x);
1123 assert!((result - 0.25).abs() < 1e-12, "GL2 x^3: {result:.6}");
1124 }
1125
1126 #[test]
1127 fn test_gl_exact_degree5() {
1128 let gl = GaussLegendreQuad::new(3);
1130 let result = gl.integrate(-1.0, 1.0, &|x| x.powi(5));
1131 assert!(result.abs() < 1e-12, "GL3 x^5 on [-1,1]: {result:.6}");
1132 }
1133
1134 #[test]
1135 fn test_gl_sine() {
1136 let gl = GaussLegendreQuad::new(5);
1137 let result = gl.integrate(0.0, std::f64::consts::PI, &|x| x.sin());
1138 assert!((result - 2.0).abs() < 1e-4, "GL5 sin: {result:.12}");
1139 }
1140
1141 #[test]
1142 fn test_gl_composite_sine() {
1143 let gl = GaussLegendreQuad::new(3);
1144 let result = gl.integrate_composite(0.0, std::f64::consts::PI, 10, &|x| x.sin());
1145 assert!((result - 2.0).abs() < 1e-8, "GL composite sin: {result:.6}");
1146 }
1147
1148 #[test]
1149 fn test_gl_nodes_count() {
1150 for n in [2, 3, 5, 10] {
1151 let gl = GaussLegendreQuad::new(n);
1152 assert_eq!(gl.nodes.len(), n);
1153 assert_eq!(gl.weights.len(), n);
1154 }
1155 }
1156
1157 #[test]
1158 fn test_gl_weights_sum_to_2() {
1159 for n in [2, 3, 4, 5] {
1160 let gl = GaussLegendreQuad::new(n);
1161 let s: f64 = gl.weights.iter().sum();
1162 assert!((s - 2.0).abs() < 1e-10, "GL{n} weights sum {s:.6}");
1163 }
1164 }
1165
1166 #[test]
1169 fn test_hermite_constant() {
1170 let gh = GaussHermiteQuad::new(5);
1172 let result = gh.integrate(&|_x| 1.0);
1173 assert!(
1174 (result - std::f64::consts::PI.sqrt()).abs() < 1e-8,
1175 "GH constant: {result:.6}"
1176 );
1177 }
1178
1179 #[test]
1180 fn test_hermite_x_squared() {
1181 let gh = GaussHermiteQuad::new(5);
1183 let result = gh.integrate(&|x| x * x);
1184 let exact = std::f64::consts::PI.sqrt() / 2.0;
1185 assert!(
1186 (result - exact).abs() < 1e-8,
1187 "GH x^2: {result:.6} vs {exact:.6}"
1188 );
1189 }
1190
1191 #[test]
1194 fn test_trapezoidal_exp() {
1195 let trap = TrapezoidalRule::new(1e-10, 20);
1196 let result = trap.integrate(0.0, 1.0, &|x| x.exp());
1197 let exact = std::f64::consts::E - 1.0;
1198 assert!((result - exact).abs() < 1e-9, "Trap exp: {result:.6}");
1199 }
1200
1201 #[test]
1202 fn test_trapezoidal_polynomial() {
1203 let trap = TrapezoidalRule::new(1e-12, 20);
1204 let result = trap.integrate(0.0, 1.0, &|x| x * x);
1205 assert!((result - 1.0 / 3.0).abs() < 1e-10, "Trap x^2: {result:.6}");
1206 }
1207
1208 #[test]
1211 fn test_simpson_exp() {
1212 let simp = SimpsonRule::new(1e-10, 20);
1213 let result = simp.integrate(0.0, 1.0, &|x| x.exp());
1214 let exact = std::f64::consts::E - 1.0;
1215 assert!((result - exact).abs() < 1e-8, "Simpson exp: {result:.6}");
1216 }
1217
1218 #[test]
1219 fn test_simpson38() {
1220 let simp = SimpsonRule::new(1e-10, 20);
1221 let result = simp.integrate38(0.0, 1.0, &|x| x * x * x);
1222 assert!((result - 0.25).abs() < 1e-10, "Simpson38 x^3: {result:.6}");
1223 }
1224
1225 #[test]
1228 fn test_romberg_converges_faster() {
1229 let rom = RombergIntegration::new(1e-12, 15);
1230 let (result, table) = rom.integrate(0.0, 1.0, &|x| x.exp());
1231 let exact = std::f64::consts::E - 1.0;
1232 assert!((result - exact).abs() < 1e-11, "Romberg exp: {result:.12}");
1233 assert!(table.len() >= 2);
1235 }
1236
1237 #[test]
1238 fn test_romberg_sine() {
1239 let rom = RombergIntegration::new(1e-12, 15);
1240 let (result, _) = rom.integrate(0.0, std::f64::consts::PI, &|x| x.sin());
1241 assert!((result - 2.0).abs() < 1e-11, "Romberg sin: {result:.12}");
1242 }
1243
1244 #[test]
1245 fn test_romberg_table_diagonal_improves() {
1246 let rom = RombergIntegration::new(1e-14, 8);
1247 let (_, table) = rom.integrate(0.0, 1.0, &|x| x * x);
1248 let last = *table.last().unwrap().last().unwrap();
1250 assert!(
1251 (last - 1.0 / 3.0).abs() < 1e-12,
1252 "Romberg table: {last:.14}"
1253 );
1254 }
1255
1256 #[test]
1259 fn test_gauss_kronrod_exp() {
1260 let gk = GaussKronrod::new(1e-10, 10);
1261 let result = gk.integrate(0.0, 1.0, &|x| x.exp());
1262 let exact = std::f64::consts::E - 1.0;
1263 assert!((result - exact).abs() < 1e-9, "GK exp: {result:.6}");
1264 }
1265
1266 #[test]
1267 fn test_gauss_kronrod_oscillatory() {
1268 let gk = GaussKronrod::new(1e-8, 15);
1269 let result = gk.integrate(0.0, 10.0 * std::f64::consts::PI, &|x| x.sin());
1270 assert!(result.abs() < 1e-6, "GK oscillatory: {result:.6}");
1272 }
1273
1274 #[test]
1277 fn test_rbf_exact_at_data_points_gaussian() {
1278 let pts: Vec<Vec<f64>> = (0..5).map(|i| vec![i as f64]).collect();
1279 let vals: Vec<f64> = pts.iter().map(|p| p[0] * p[0]).collect();
1280 let rbf = RadialBasisFunction::fit(RbfKind::Gaussian, 1.0, &pts, &vals);
1281 for (i, pt) in pts.iter().enumerate() {
1282 let v = rbf.eval(pt);
1283 assert!(
1284 (v - vals[i]).abs() < 1e-8,
1285 "RBF Gaussian at data[{i}]: {v:.6} vs {:.6}",
1286 vals[i]
1287 );
1288 }
1289 }
1290
1291 #[test]
1292 fn test_rbf_exact_at_data_points_mq() {
1293 let pts: Vec<Vec<f64>> = vec![vec![0.0], vec![0.5], vec![1.0]];
1294 let vals = vec![0.0, 0.25, 1.0];
1295 let rbf = RadialBasisFunction::fit(RbfKind::Multiquadric, 0.5, &pts, &vals);
1296 for (i, pt) in pts.iter().enumerate() {
1297 let v = rbf.eval(pt);
1298 assert!((v - vals[i]).abs() < 1e-8, "RBF MQ at data[{i}]: {v:.6}");
1299 }
1300 }
1301
1302 #[test]
1303 fn test_rbf_inv_mq() {
1304 let pts: Vec<Vec<f64>> = vec![vec![0.0], vec![1.0], vec![2.0]];
1305 let vals = vec![1.0, 2.0, 4.0];
1306 let rbf = RadialBasisFunction::fit(RbfKind::InverseMultiquadric, 0.5, &pts, &vals);
1307 for (i, pt) in pts.iter().enumerate() {
1308 let v = rbf.eval(pt);
1309 assert!((v - vals[i]).abs() < 1e-8, "RBF InvMQ at data[{i}]: {v:.6}");
1310 }
1311 }
1312
1313 #[test]
1316 fn test_spline_natural_at_nodes() {
1317 let xs: Vec<f64> = (0..6).map(|i| i as f64).collect();
1318 let ys: Vec<f64> = xs.iter().map(|&x| x.sin()).collect();
1319 let sp = SplineInterpolation::fit(&xs, &ys, SplineKind::Natural);
1320 for i in 0..xs.len() {
1321 let v = sp.eval_cubic(xs[i]);
1322 assert!(
1323 (v - ys[i]).abs() < 1e-10,
1324 "Natural spline at node {i}: {v:.6} vs {:.6}",
1325 ys[i]
1326 );
1327 }
1328 }
1329
1330 #[test]
1331 fn test_spline_natural_zero_second_deriv_endpoints() {
1332 let xs: Vec<f64> = (0..5).map(|i| i as f64).collect();
1333 let ys: Vec<f64> = xs.iter().map(|&x| x * x).collect();
1334 let sp = SplineInterpolation::fit(&xs, &ys, SplineKind::Natural);
1335 assert!(sp.m[0].abs() < 1e-10, "Natural spline M[0]={:.6}", sp.m[0]);
1336 let n = sp.m.len();
1337 assert!(
1338 sp.m[n - 1].abs() < 1e-10,
1339 "Natural spline M[n-1]={:.6}",
1340 sp.m[n - 1]
1341 );
1342 }
1343
1344 #[test]
1345 fn test_spline_clamped_at_nodes() {
1346 let xs: Vec<f64> = (0..5).map(|i| i as f64 * 0.5).collect();
1347 let ys: Vec<f64> = xs.iter().map(|&x| x * x * x).collect();
1348 let sp = SplineInterpolation::fit(&xs, &ys, SplineKind::Clamped(0.0, 3.0 * 2.0 * 2.0));
1349 for i in 0..xs.len() {
1350 let v = sp.eval_cubic(xs[i]);
1351 assert!(
1352 (v - ys[i]).abs() < 1e-8,
1353 "Clamped spline at node {i}: {v:.6}"
1354 );
1355 }
1356 }
1357
1358 #[test]
1359 fn test_spline_not_a_knot_at_nodes() {
1360 let xs: Vec<f64> = (0..5).map(|i| i as f64).collect();
1361 let ys: Vec<f64> = xs.iter().map(|&x| x.exp()).collect();
1362 let sp = SplineInterpolation::fit(&xs, &ys, SplineKind::NotAKnot);
1363 for i in 0..xs.len() {
1364 let v = sp.eval_cubic(xs[i]);
1365 assert!(
1366 (v - ys[i]).abs() < 1e-8,
1367 "NotAKnot spline at node {i}: {v:.6}"
1368 );
1369 }
1370 }
1371
1372 #[test]
1375 fn test_barycentric_exact_at_nodes() {
1376 let nodes: Vec<f64> = (0..5).map(|i| i as f64).collect();
1377 let values: Vec<f64> = nodes.iter().map(|&x| x * x).collect();
1378 let bary = BarycentricInterpolation::fit(&nodes, &values);
1379 for i in 0..nodes.len() {
1380 let v = bary.eval(nodes[i]);
1381 assert!(
1382 (v - values[i]).abs() < 1e-10,
1383 "Barycentric at node {i}: {v:.6}"
1384 );
1385 }
1386 }
1387
1388 #[test]
1389 fn test_barycentric_polynomial_reconstruction() {
1390 let nodes = vec![0.0, 1.0, 2.0, 3.0];
1392 let values: Vec<f64> = nodes.iter().map(|&x| x * x * x - 2.0 * x + 1.0).collect();
1393 let bary = BarycentricInterpolation::fit(&nodes, &values);
1394 let test_pts = [0.5, 1.5, 2.5];
1395 for &x in &test_pts {
1396 let v = bary.eval(x);
1397 let exact = x * x * x - 2.0 * x + 1.0;
1398 assert!(
1399 (v - exact).abs() < 1e-10,
1400 "Barycentric poly at {x}: {v:.6} vs {exact:.6}"
1401 );
1402 }
1403 }
1404
1405 #[test]
1408 fn test_first_deriv_sine() {
1409 let nd = NumericalDifferentiation::new(1e-4, 4);
1410 let x = 1.0;
1411 let d = nd.first_deriv(x, &|t| t.sin());
1412 let exact = x.cos();
1413 assert!(
1414 (d - exact).abs() < 1e-9,
1415 "d/dx sin(1): {d:.9} vs {exact:.9}"
1416 );
1417 }
1418
1419 #[test]
1420 fn test_second_deriv_exp() {
1421 let nd = NumericalDifferentiation::new(1e-4, 4);
1422 let x = 0.5;
1423 let d2 = nd.second_deriv(x, &|t| t.exp());
1424 assert!((d2 - x.exp()).abs() < 1e-5, "d^2/dx^2 exp(0.5): {d2:.6}");
1425 }
1426
1427 #[test]
1428 fn test_mixed_partial_xy() {
1429 let nd = NumericalDifferentiation::new(1e-4, 4);
1430 let y = 2.0;
1432 let d = nd.mixed_partial(1.0, y, &|x, yy| x * yy * yy);
1433 let exact = 2.0 * y;
1434 assert!(
1435 (d - exact).abs() < 1e-7,
1436 "mixed partial: {d:.6} vs {exact:.6}"
1437 );
1438 }
1439
1440 #[test]
1441 fn test_first_deriv_accuracy_order4() {
1442 let nd_coarse = NumericalDifferentiation::new(1e-2, 1);
1444 let nd_fine = NumericalDifferentiation::new(1e-2, 4);
1445 let x = 1.2;
1446 let exact = x.diff_exact(); let d_coarse = nd_coarse.first_deriv(x, &|t| t * t * t);
1448 let d_fine = nd_fine.first_deriv(x, &|t| t * t * t);
1449 assert!(
1451 (d_fine - exact).abs() < (d_coarse - exact).abs(),
1452 "Richardson should improve: coarse err={:.2e} fine err={:.2e}",
1453 (d_coarse - exact).abs(),
1454 (d_fine - exact).abs()
1455 );
1456 }
1457
1458 #[test]
1459 fn test_nth_deriv_third_order() {
1460 let nd = NumericalDifferentiation::new(1e-3, 4);
1461 let x = 1.0;
1463 let d3 = nd.nth_deriv(x, 3, &|t| t.powi(4));
1464 let exact = 24.0 * x;
1465 assert!(
1466 (d3 - exact).abs() < 1e-5,
1467 "d^3/dx^3 x^4 at 1: {d3:.6} vs {exact:.6}"
1468 );
1469 }
1470
1471 #[test]
1472 fn test_nth_deriv_fourth_order() {
1473 let nd = NumericalDifferentiation::new(1e-3, 4);
1474 let x = 1.0;
1476 let d4 = nd.nth_deriv(x, 4, &|t| t.powi(4));
1477 assert!((d4 - 24.0).abs() < 1e-2, "d^4/dx^4 x^4 at 1: {d4:.6}");
1478 }
1479}