1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
10#![allow(unused_imports)]
11#![allow(clippy::too_many_arguments)]
12
13use std::f64::consts::{FRAC_1_SQRT_2, PI};
14
15#[derive(Debug, Clone)]
21pub struct RootResult {
22 pub root: f64,
24 pub f_val: f64,
26 pub iterations: usize,
28 pub converged: bool,
30}
31
32pub fn newton_raphson<F, DF>(f: F, df: DF, x0: f64, tol: f64, max_iter: usize) -> RootResult
36where
37 F: Fn(f64) -> f64,
38 DF: Fn(f64) -> f64,
39{
40 let mut x = x0;
41 for i in 0..max_iter {
42 let fx = f(x);
43 if fx.abs() < tol {
44 return RootResult {
45 root: x,
46 f_val: fx,
47 iterations: i,
48 converged: true,
49 };
50 }
51 let dfx = df(x);
52 if dfx.abs() < 1e-15 {
53 break;
54 }
55 x -= fx / dfx;
56 }
57 let fx = f(x);
58 RootResult {
59 root: x,
60 f_val: fx,
61 iterations: max_iter,
62 converged: fx.abs() < tol,
63 }
64}
65
66pub fn secant_method<F>(f: F, x0: f64, x1: f64, tol: f64, max_iter: usize) -> RootResult
68where
69 F: Fn(f64) -> f64,
70{
71 let mut xa = x0;
72 let mut xb = x1;
73 let mut fa = f(xa);
74 for i in 0..max_iter {
75 let fb = f(xb);
76 if fb.abs() < tol {
77 return RootResult {
78 root: xb,
79 f_val: fb,
80 iterations: i,
81 converged: true,
82 };
83 }
84 if (fb - fa).abs() < 1e-15 {
85 break;
86 }
87 let xc = xb - fb * (xb - xa) / (fb - fa);
88 xa = xb;
89 fa = fb;
90 xb = xc;
91 }
92 let fval = f(xb);
93 RootResult {
94 root: xb,
95 f_val: fval,
96 iterations: max_iter,
97 converged: fval.abs() < tol,
98 }
99}
100
101pub fn brentq<F>(f: F, a: f64, b: f64, tol: f64, max_iter: usize) -> RootResult
105where
106 F: Fn(f64) -> f64,
107{
108 let mut a = a;
109 let mut b = b;
110 let mut fa = f(a);
111 let fb = f(b);
112 if fa * fb > 0.0 {
113 return RootResult {
114 root: a,
115 f_val: fa,
116 iterations: 0,
117 converged: false,
118 };
119 }
120 let mut c = a;
121 let mut fc = fa;
122 let mut d = b - a;
123 let mut e = d;
124 for i in 0..max_iter {
125 if fb * fc > 0.0 {
126 c = a;
127 fc = fa;
128 d = b - a;
129 e = d;
130 }
131 if fc.abs() < fb.abs() {
132 a = b;
133 b = c;
134 c = a;
135 fa = fb;
136 let fb_new = fc;
137 fc = fa;
138 let _ = fb_new;
139 }
140 let tol1 = 2.0 * f64::EPSILON * b.abs() + tol / 2.0;
141 let xm = (c - b) / 2.0;
142 if xm.abs() <= tol1 || fb.abs() < tol {
143 return RootResult {
144 root: b,
145 f_val: f(b),
146 iterations: i,
147 converged: true,
148 };
149 }
150 if e.abs() >= tol1 && fa.abs() > fb.abs() {
151 let s = fb / fa;
152 let p;
153 let q;
154 if (a - c).abs() < 1e-15 {
155 p = 2.0 * xm * s;
156 q = 1.0 - s;
157 } else {
158 let r = fb / fc;
159 let s2 = fb / fa;
160 p = s2 * (2.0 * xm * r * (r - s2) - (b - a) * (s2 - 1.0));
161 q = (r - 1.0) * (s2 - 1.0) * (r - s2);
162 let _ = s;
163 let _ = p;
164 let _ = q;
165 let p2 = s2 * (2.0 * xm * r * (r - s2) - (b - a) * (s2 - 1.0));
166 let q2 = (r - 1.0) * (s2 - 1.0) * (r - s2);
167 if p2 > 0.0 {
168 d = p2 / q2.abs();
169 } else {
170 d = -p2 / q2.abs();
171 }
172 if 2.0 * d < 3.0 * xm - tol1.abs() * (if xm >= 0.0 { 1.0 } else { -1.0 }) {
173 e = d;
174 } else {
175 d = xm;
176 e = d;
177 }
178 let _ = p;
179 let _ = q;
180 }
181 let _ = p;
182 let _ = q;
183 } else {
184 d = xm;
185 e = d;
186 }
187 a = b;
188 fa = f(b);
189 if d.abs() > tol1 {
190 b += d;
191 } else if xm >= 0.0 {
192 b += tol1;
193 } else {
194 b -= tol1;
195 }
196 let fb_new = f(b);
197 let _ = fb_new;
198 if fb.abs() < tol {
199 return RootResult {
200 root: b,
201 f_val: f(b),
202 iterations: i,
203 converged: true,
204 };
205 }
206 }
207 RootResult {
208 root: b,
209 f_val: f(b),
210 iterations: max_iter,
211 converged: false,
212 }
213}
214
215pub fn bisect<F>(f: F, mut a: f64, mut b: f64, tol: f64, max_iter: usize) -> RootResult
217where
218 F: Fn(f64) -> f64,
219{
220 let mut fa = f(a);
221 for i in 0..max_iter {
222 let mid = (a + b) / 2.0;
223 let fmid = f(mid);
224 if fmid.abs() < tol || (b - a) / 2.0 < tol {
225 return RootResult {
226 root: mid,
227 f_val: fmid,
228 iterations: i,
229 converged: true,
230 };
231 }
232 if fa * fmid < 0.0 {
233 b = mid;
234 } else {
235 a = mid;
236 fa = fmid;
237 }
238 }
239 let mid = (a + b) / 2.0;
240 RootResult {
241 root: mid,
242 f_val: f(mid),
243 iterations: max_iter,
244 converged: false,
245 }
246}
247
248pub fn euler_ode<F>(f: F, t0: f64, y0: Vec<f64>, t_end: f64, dt: f64) -> Vec<(f64, Vec<f64>)>
256where
257 F: Fn(f64, &[f64]) -> Vec<f64>,
258{
259 let mut t = t0;
260 let mut y = y0;
261 let mut result = vec![(t, y.clone())];
262 while t < t_end - dt * 0.5 {
263 let dy = f(t, &y);
264 for i in 0..y.len() {
265 y[i] += dy[i] * dt;
266 }
267 t += dt;
268 result.push((t, y.clone()));
269 }
270 result
271}
272
273pub fn rk2<F>(f: F, t0: f64, y0: Vec<f64>, t_end: f64, dt: f64) -> Vec<(f64, Vec<f64>)>
275where
276 F: Fn(f64, &[f64]) -> Vec<f64>,
277{
278 let mut t = t0;
279 let mut y = y0;
280 let mut result = vec![(t, y.clone())];
281 while t < t_end - dt * 0.5 {
282 let k1 = f(t, &y);
283 let y_pred: Vec<f64> = y.iter().zip(k1.iter()).map(|(yi, k)| yi + k * dt).collect();
284 let k2 = f(t + dt, &y_pred);
285 for i in 0..y.len() {
286 y[i] += (k1[i] + k2[i]) * dt / 2.0;
287 }
288 t += dt;
289 result.push((t, y.clone()));
290 }
291 result
292}
293
294pub fn rk4<F>(f: F, t0: f64, y0: Vec<f64>, t_end: f64, dt: f64) -> Vec<(f64, Vec<f64>)>
296where
297 F: Fn(f64, &[f64]) -> Vec<f64>,
298{
299 let mut t = t0;
300 let mut y = y0;
301 let mut result = vec![(t, y.clone())];
302 while t < t_end - dt * 0.5 {
303 let k1 = f(t, &y);
304 let y2: Vec<f64> = y
305 .iter()
306 .zip(k1.iter())
307 .map(|(yi, k)| yi + k * dt / 2.0)
308 .collect();
309 let k2 = f(t + dt / 2.0, &y2);
310 let y3: Vec<f64> = y
311 .iter()
312 .zip(k2.iter())
313 .map(|(yi, k)| yi + k * dt / 2.0)
314 .collect();
315 let k3 = f(t + dt / 2.0, &y3);
316 let y4: Vec<f64> = y.iter().zip(k3.iter()).map(|(yi, k)| yi + k * dt).collect();
317 let k4 = f(t + dt, &y4);
318 for i in 0..y.len() {
319 y[i] += (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) * dt / 6.0;
320 }
321 t += dt;
322 result.push((t, y.clone()));
323 }
324 result
325}
326
327#[derive(Debug, Clone)]
331pub struct Rk45State {
332 pub t: f64,
334 pub y: Vec<f64>,
336 pub h: f64,
338 pub rtol: f64,
340 pub atol: f64,
342}
343
344const RK45_A: [[f64; 5]; 6] = [
346 [0.0, 0.0, 0.0, 0.0, 0.0],
347 [1.0 / 5.0, 0.0, 0.0, 0.0, 0.0],
348 [3.0 / 40.0, 9.0 / 40.0, 0.0, 0.0, 0.0],
349 [44.0 / 45.0, -56.0 / 15.0, 32.0 / 9.0, 0.0, 0.0],
350 [
351 19372.0 / 6561.0,
352 -25360.0 / 2187.0,
353 64448.0 / 6561.0,
354 -212.0 / 729.0,
355 0.0,
356 ],
357 [
358 9017.0 / 3168.0,
359 -355.0 / 33.0,
360 46732.0 / 5247.0,
361 49.0 / 176.0,
362 -5103.0 / 18656.0,
363 ],
364];
365const RK45_C: [f64; 6] = [0.0, 1.0 / 5.0, 3.0 / 10.0, 4.0 / 5.0, 8.0 / 9.0, 1.0];
366const RK45_B5: [f64; 6] = [
367 35.0 / 384.0,
368 0.0,
369 500.0 / 1113.0,
370 125.0 / 192.0,
371 -2187.0 / 6784.0,
372 11.0 / 84.0,
373];
374const RK45_E: [f64; 6] = [
375 71.0 / 57600.0,
376 0.0,
377 -71.0 / 16695.0,
378 71.0 / 1920.0,
379 -17253.0 / 339200.0,
380 22.0 / 525.0,
381];
382
383pub fn rk45_step<F>(f: &F, t: f64, y: &[f64], h: f64) -> (Vec<f64>, Vec<f64>)
385where
386 F: Fn(f64, &[f64]) -> Vec<f64>,
387{
388 let n = y.len();
389 let mut k = Vec::with_capacity(6);
390 k.push(f(t, y));
391 for stage in 1..6 {
392 let t_s = t + RK45_C[stage] * h;
393 let y_s: Vec<f64> = (0..n)
394 .map(|i| y[i] + h * (0..stage).map(|j| RK45_A[stage][j] * k[j][i]).sum::<f64>())
395 .collect();
396 k.push(f(t_s, &y_s));
397 }
398 let y_new: Vec<f64> = (0..n)
399 .map(|i| y[i] + h * (0..6).map(|j| RK45_B5[j] * k[j][i]).sum::<f64>())
400 .collect();
401 let err: Vec<f64> = (0..n)
402 .map(|i| h * (0..6).map(|j| RK45_E[j] * k[j][i]).sum::<f64>())
403 .collect();
404 (y_new, err)
405}
406
407pub fn rk45_adaptive<F>(
409 f: F,
410 t0: f64,
411 y0: Vec<f64>,
412 t_end: f64,
413 rtol: f64,
414 atol: f64,
415) -> Vec<(f64, Vec<f64>)>
416where
417 F: Fn(f64, &[f64]) -> Vec<f64>,
418{
419 let mut t = t0;
420 let mut y = y0.clone();
421 let mut h = (t_end - t0) / 100.0;
422 let mut result = vec![(t, y.clone())];
423 let max_steps = 100_000;
424 let mut steps = 0;
425 while t < t_end - 1e-12 && steps < max_steps {
426 h = h.min(t_end - t);
427 let (y_new, err) = rk45_step(&f, t, &y, h);
428 let err_norm = {
430 let n = y.len() as f64;
431 let sum: f64 = err
432 .iter()
433 .zip(y.iter())
434 .zip(y_new.iter())
435 .map(|((e, yi), yi_new)| {
436 let sc = atol + rtol * yi.abs().max(yi_new.abs());
437 (e / sc).powi(2)
438 })
439 .sum();
440 (sum / n).sqrt()
441 };
442 if err_norm <= 1.0 {
443 t += h;
444 y = y_new;
445 result.push((t, y.clone()));
446 let factor = 0.9 * err_norm.powf(-0.2);
448 h *= factor.clamp(0.1, 5.0);
449 } else {
450 let factor = 0.9 * err_norm.powf(-0.2);
452 h *= factor.clamp(0.1, 1.0);
453 }
454 steps += 1;
455 }
456 result
457}
458
459pub fn bvp_shoot<F>(
467 f: F,
468 a: f64,
469 b: f64,
470 ya: f64,
471 yb: f64,
472 s0: f64,
473 n_steps: usize,
474 tol: f64,
475) -> Vec<(f64, f64)>
476where
477 F: Fn(f64, f64, f64) -> f64,
478{
479 let shoot = |s: f64| -> Vec<(f64, f64)> {
480 let dt = (b - a) / n_steps as f64;
481 let mut x = a;
482 let mut y = ya;
483 let mut dy = s;
484 let mut path = vec![(x, y)];
485 for _ in 0..n_steps {
486 let k1y = dy;
487 let k1dy = f(x, y, dy);
488 let k2y = dy + k1dy * dt / 2.0;
489 let k2dy = f(x + dt / 2.0, y + k1y * dt / 2.0, dy + k1dy * dt / 2.0);
490 let k3y = dy + k2dy * dt / 2.0;
491 let k3dy = f(x + dt / 2.0, y + k2y * dt / 2.0, dy + k2dy * dt / 2.0);
492 let k4y = dy + k3dy * dt;
493 let k4dy = f(x + dt, y + k3y * dt, dy + k3dy * dt);
494 y += (k1y + 2.0 * k2y + 2.0 * k3y + k4y) * dt / 6.0;
495 dy += (k1dy + 2.0 * k2dy + 2.0 * k3dy + k4dy) * dt / 6.0;
496 x += dt;
497 path.push((x, y));
498 }
499 path
500 };
501 let boundary_residual = |s: f64| -> f64 {
502 let path = shoot(s);
503 path.last().map(|(_, y)| *y).unwrap_or(0.0) - yb
504 };
505 let mut s = s0;
507 for _ in 0..50 {
508 let r = boundary_residual(s);
509 if r.abs() < tol {
510 break;
511 }
512 let ds = 1e-6;
513 let dr_ds = (boundary_residual(s + ds) - boundary_residual(s - ds)) / (2.0 * ds);
514 if dr_ds.abs() < 1e-12 {
515 break;
516 }
517 s -= r / dr_ds;
518 }
519 shoot(s)
520}
521
522pub fn fd1d_laplacian(n: usize, dx: f64) -> Vec<Vec<f64>> {
530 let ni = n.saturating_sub(2);
531 let mut mat = vec![vec![0.0f64; ni]; ni];
532 let inv_dx2 = 1.0 / (dx * dx);
533 for i in 0..ni {
534 mat[i][i] = -2.0 * inv_dx2;
535 if i > 0 {
536 mat[i][i - 1] = inv_dx2;
537 }
538 if i + 1 < ni {
539 mat[i][i + 1] = inv_dx2;
540 }
541 }
542 mat
543}
544
545pub fn solve_poisson_1d(rhs: &[f64], dx: f64) -> Vec<f64> {
549 let n = rhs.len();
550 if n == 0 {
551 return Vec::new();
552 }
553 let inv_dx2 = 1.0 / (dx * dx);
554 let c = vec![-inv_dx2; n - 1];
556 let d: Vec<f64> = rhs.to_vec();
557 let a = vec![-inv_dx2; n - 1];
558 let b_diag = 2.0 * inv_dx2;
559 let mut w = vec![0.0f64; n];
561 let mut g = d.clone();
562 w[0] = c[0] / b_diag;
563 g[0] = d[0] / b_diag;
564 for i in 1..n {
565 let denom = b_diag - a[i.min(n - 2)] * w[i - 1];
566 if i < n - 1 {
567 w[i] = c[i] / denom;
568 }
569 g[i] = (d[i] - a[i.min(n - 2)] * g[i - 1]) / denom;
570 }
571 let mut u = vec![0.0f64; n];
573 u[n - 1] = g[n - 1];
574 for i in (0..n - 1).rev() {
575 u[i] = g[i] - w[i] * u[i + 1];
576 }
577 let _ = c;
578 let _ = a;
579 u
580}
581
582pub fn fd2d_laplacian(u: &[Vec<f64>], dx: f64, dy: f64) -> Vec<Vec<f64>> {
586 let ny = u.len();
587 if ny == 0 {
588 return Vec::new();
589 }
590 let nx = u[0].len();
591 let mut result = vec![vec![0.0f64; nx]; ny];
592 for j in 1..ny.saturating_sub(1) {
593 for i in 1..nx.saturating_sub(1) {
594 result[j][i] = (u[j][i - 1] - 2.0 * u[j][i] + u[j][i + 1]) / (dx * dx)
595 + (u[j - 1][i] - 2.0 * u[j][i] + u[j + 1][i]) / (dy * dy);
596 }
597 }
598 result
599}
600
601pub fn fd3d_laplacian(u: &[Vec<Vec<f64>>], dx: f64, dy: f64, dz: f64) -> Vec<Vec<Vec<f64>>> {
603 let nz = u.len();
604 if nz == 0 {
605 return Vec::new();
606 }
607 let ny = u[0].len();
608 let nx = if ny > 0 { u[0][0].len() } else { 0 };
609 let mut result = vec![vec![vec![0.0f64; nx]; ny]; nz];
610 for k in 1..nz.saturating_sub(1) {
611 for j in 1..ny.saturating_sub(1) {
612 for i in 1..nx.saturating_sub(1) {
613 result[k][j][i] = (u[k][j][i - 1] - 2.0 * u[k][j][i] + u[k][j][i + 1]) / (dx * dx)
614 + (u[k][j - 1][i] - 2.0 * u[k][j][i] + u[k][j + 1][i]) / (dy * dy)
615 + (u[k - 1][j][i] - 2.0 * u[k][j][i] + u[k + 1][j][i]) / (dz * dz);
616 }
617 }
618 }
619 result
620}
621
622pub fn deriv_forward<F: Fn(f64) -> f64>(f: F, x: f64, h: f64) -> f64 {
628 (f(x + h) - f(x)) / h
629}
630
631pub fn deriv_central<F: Fn(f64) -> f64>(f: F, x: f64, h: f64) -> f64 {
633 (f(x + h) - f(x - h)) / (2.0 * h)
634}
635
636pub fn deriv_complex_step<F: Fn(f64) -> f64>(f: F, x: f64) -> f64 {
641 let h = 1e-20_f64.max(x.abs() * 1e-10);
642 (f(x + h) - f(x - h)) / (2.0 * h)
644}
645
646pub fn hessian<F>(f: F, x: &[f64], h: f64) -> Vec<Vec<f64>>
648where
649 F: Fn(&[f64]) -> f64,
650{
651 let n = x.len();
652 let mut hess = vec![vec![0.0f64; n]; n];
653 let fx = f(x);
654 for i in 0..n {
655 for j in 0..n {
656 let mut xpp = x.to_vec();
657 let mut xpm = x.to_vec();
658 let mut xmp = x.to_vec();
659 let mut xmm = x.to_vec();
660 xpp[i] += h;
661 xpp[j] += h;
662 xpm[i] += h;
663 xpm[j] -= h;
664 xmp[i] -= h;
665 xmp[j] += h;
666 xmm[i] -= h;
667 xmm[j] -= h;
668 hess[i][j] = (f(&xpp) - f(&xpm) - f(&xmp) + f(&xmm)) / (4.0 * h * h);
669 }
670 }
671 let _ = fx;
672 hess
673}
674
675pub fn jacobian<F>(f: F, x: &[f64], h: f64) -> Vec<Vec<f64>>
677where
678 F: Fn(&[f64]) -> Vec<f64>,
679{
680 let n_in = x.len();
681 let f0 = f(x);
682 let n_out = f0.len();
683 let mut jac = vec![vec![0.0f64; n_in]; n_out];
684 for j in 0..n_in {
685 let mut xp = x.to_vec();
686 let mut xm = x.to_vec();
687 xp[j] += h;
688 xm[j] -= h;
689 let fp = f(&xp);
690 let fm = f(&xm);
691 for i in 0..n_out {
692 jac[i][j] = (fp[i] - fm[i]) / (2.0 * h);
693 }
694 }
695 jac
696}
697
698pub fn gauss_legendre_nodes(n: usize) -> (Vec<f64>, Vec<f64>) {
704 match n {
705 1 => (vec![0.0], vec![2.0]),
706 2 => (
707 vec![-1.0 / 3.0_f64.sqrt(), 1.0 / 3.0_f64.sqrt()],
708 vec![1.0, 1.0],
709 ),
710 3 => (
711 vec![-0.7745966692, 0.0, 0.7745966692],
712 vec![0.5555555556, 0.8888888889, 0.5555555556],
713 ),
714 4 => (
715 vec![-0.8611363116, -0.3399810435, 0.3399810435, 0.8611363116],
716 vec![0.3478548451, 0.6521451549, 0.6521451549, 0.3478548451],
717 ),
718 5 => (
719 vec![
720 -0.9061798459,
721 -0.5384693101,
722 0.0,
723 0.5384693101,
724 0.9061798459,
725 ],
726 vec![
727 0.2369268851,
728 0.4786286705,
729 0.5688888889,
730 0.4786286705,
731 0.2369268851,
732 ],
733 ),
734 _ => {
735 gauss_legendre_nodes(3)
737 }
738 }
739}
740
741pub fn gauss_legendre_integrate<F: Fn(f64) -> f64>(f: F, a: f64, b: f64, n: usize) -> f64 {
743 let (nodes, weights) = gauss_legendre_nodes(n);
744 let half = (b - a) / 2.0;
745 let mid = (a + b) / 2.0;
746 nodes
747 .iter()
748 .zip(weights.iter())
749 .map(|(xi, wi)| wi * f(mid + half * xi))
750 .sum::<f64>()
751 * half
752}
753
754pub fn gauss_hermite_nodes(n: usize) -> (Vec<f64>, Vec<f64>) {
758 match n {
759 1 => (vec![0.0], vec![1.7724538509]),
760 2 => (
761 vec![-FRAC_1_SQRT_2, FRAC_1_SQRT_2],
762 vec![0.8862269255, 0.8862269255],
763 ),
764 3 => (
765 vec![-1.2247448714, 0.0, 1.2247448714],
766 vec![0.2954089752, 1.1816359006, 0.2954089752],
767 ),
768 4 => (
769 vec![-1.6506801239, -0.5246476233, 0.5246476233, 1.6506801239],
770 vec![0.0813128354, 0.8049140900, 0.8049140900, 0.0813128354],
771 ),
772 5 => (
773 vec![
774 -2.0201828705,
775 -0.9585724646,
776 0.0,
777 0.9585724646,
778 2.0201828705,
779 ],
780 vec![
781 0.0199532421,
782 0.3936193231,
783 0.9453087205,
784 0.3936193231,
785 0.0199532421,
786 ],
787 ),
788 _ => gauss_hermite_nodes(3),
789 }
790}
791
792pub fn gauss_hermite_integrate<F: Fn(f64) -> f64>(f: F, n: usize) -> f64 {
796 let (nodes, weights) = gauss_hermite_nodes(n);
797 nodes
798 .iter()
799 .zip(weights.iter())
800 .map(|(x, w)| w * f(*x))
801 .sum()
802}
803
804pub fn adaptive_simpson<F: Fn(f64) -> f64>(f: &F, a: f64, b: f64, tol: f64, depth: usize) -> f64 {
806 let simpson = |f: &F, a: f64, b: f64| -> f64 {
807 let mid = (a + b) / 2.0;
808 (b - a) / 6.0 * (f(a) + 4.0 * f(mid) + f(b))
809 };
810 let mid = (a + b) / 2.0;
811 let s = simpson(f, a, b);
812 let s2 = simpson(f, a, mid) + simpson(f, mid, b);
813 if depth == 0 || (s - s2).abs() < 15.0 * tol {
814 return s2 + (s2 - s) / 15.0;
815 }
816 adaptive_simpson(f, a, mid, tol / 2.0, depth - 1)
817 + adaptive_simpson(f, mid, b, tol / 2.0, depth - 1)
818}
819
820pub fn lu_decomp(a: &[Vec<f64>]) -> (Vec<Vec<f64>>, Vec<Vec<f64>>, Vec<usize>) {
828 let n = a.len();
829 let mut u = a.to_vec();
830 let mut l = vec![vec![0.0f64; n]; n];
831 let mut perm: Vec<usize> = (0..n).collect();
832 for i in 0..n {
833 l[i][i] = 1.0;
834 }
835 for k in 0..n {
836 let mut max_val = u[k][k].abs();
838 let mut max_row = k;
839 for i in (k + 1)..n {
840 if u[i][k].abs() > max_val {
841 max_val = u[i][k].abs();
842 max_row = i;
843 }
844 }
845 if max_row != k {
846 u.swap(k, max_row);
847 perm.swap(k, max_row);
848 for j in 0..k {
849 let tmp = l[k][j];
850 l[k][j] = l[max_row][j];
851 l[max_row][j] = tmp;
852 }
853 }
854 if u[k][k].abs() < 1e-12 {
855 continue;
856 }
857 for i in (k + 1)..n {
858 let factor = u[i][k] / u[k][k];
859 l[i][k] = factor;
860 for j in k..n {
861 u[i][j] -= factor * u[k][j];
862 }
863 }
864 }
865 (l, u, perm)
866}
867
868pub fn lu_solve(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
870 let n = a.len();
871 let (l, u, perm) = lu_decomp(a);
872 let pb: Vec<f64> = perm.iter().map(|&i| b[i]).collect();
874 let mut y = vec![0.0f64; n];
876 for i in 0..n {
877 let sum: f64 = (0..i).map(|j| l[i][j] * y[j]).sum();
878 y[i] = (pb[i] - sum) / l[i][i].max(1e-15);
879 }
880 let mut x = vec![0.0f64; n];
882 for i in (0..n).rev() {
883 let sum: f64 = ((i + 1)..n).map(|j| u[i][j] * x[j]).sum();
884 x[i] = if u[i][i].abs() > 1e-15 {
885 (y[i] - sum) / u[i][i]
886 } else {
887 0.0
888 };
889 }
890 x
891}
892
893pub fn qr_decomp_gs(a: &[Vec<f64>]) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
895 let m = a.len();
896 if m == 0 {
897 return (Vec::new(), Vec::new());
898 }
899 let n = a[0].len();
900 let mut q: Vec<Vec<f64>> = Vec::new();
901 let mut r = vec![vec![0.0f64; n]; n];
902 for j in 0..n {
903 let mut v: Vec<f64> = (0..m).map(|i| a[i][j]).collect();
904 for (qi_idx, qi) in q.iter().enumerate() {
905 let proj: f64 = (0..m).map(|i| qi[i] * v[i]).sum();
906 r[qi_idx][j] = proj;
907 for i in 0..m {
908 v[i] -= proj * qi[i];
909 }
910 }
911 let norm = (v.iter().map(|x| x.powi(2)).sum::<f64>()).sqrt();
912 r[j][j] = norm;
913 if norm > 1e-12 {
914 q.push(v.iter().map(|x| x / norm).collect());
915 } else {
916 q.push(vec![0.0; m]);
917 }
918 }
919 (q, r)
920}
921
922pub fn cholesky(a: &[Vec<f64>]) -> Option<Vec<Vec<f64>>> {
926 let n = a.len();
927 let mut l = vec![vec![0.0f64; n]; n];
928 for i in 0..n {
929 for j in 0..=i {
930 let sum: f64 = (0..j).map(|k| l[i][k] * l[j][k]).sum();
931 if i == j {
932 let val = a[i][i] - sum;
933 if val < 0.0 {
934 return None;
935 }
936 l[i][j] = val.sqrt();
937 } else {
938 if l[j][j].abs() < 1e-15 {
939 return None;
940 }
941 l[i][j] = (a[i][j] - sum) / l[j][j];
942 }
943 }
944 }
945 Some(l)
946}
947
948pub fn svd_singular_values(a: &[Vec<f64>], max_iter: usize) -> Vec<f64> {
953 let m = a.len();
954 if m == 0 {
955 return Vec::new();
956 }
957 let n = a[0].len();
958 let rank = m.min(n);
959 let mut ata = vec![vec![0.0f64; n]; n];
961 for i in 0..n {
962 for j in 0..n {
963 for k in 0..m {
964 ata[i][j] += a[k][i] * a[k][j];
965 }
966 }
967 }
968 let mut singular_vals = Vec::new();
970 let mut deflated = ata.clone();
971 for _ in 0..rank.min(5) {
972 let mut v = vec![1.0f64 / (n as f64).sqrt(); n];
973 let mut lambda = 0.0;
974 for _ in 0..max_iter {
975 let mut v_new = vec![0.0f64; n];
977 for i in 0..n {
978 for j in 0..n {
979 v_new[i] += deflated[i][j] * v[j];
980 }
981 }
982 lambda = (v_new.iter().map(|x| x.powi(2)).sum::<f64>()).sqrt();
983 if lambda < 1e-12 {
984 break;
985 }
986 v = v_new.iter().map(|x| x / lambda).collect();
987 }
988 singular_vals.push(lambda.sqrt());
989 for i in 0..n {
991 for j in 0..n {
992 deflated[i][j] -= lambda * v[i] * v[j];
993 }
994 }
995 }
996 singular_vals
997}
998
999pub fn power_iteration(a: &[Vec<f64>], max_iter: usize, tol: f64) -> (f64, Vec<f64>) {
1005 let n = a.len();
1006 let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.0 }).collect();
1007 let mut lambda = 0.0;
1008 for _ in 0..max_iter {
1009 let mut av = vec![0.0f64; n];
1010 for i in 0..n {
1011 for j in 0..n {
1012 av[i] += a[i][j] * v[j];
1013 }
1014 }
1015 let norm = (av.iter().map(|x| x.powi(2)).sum::<f64>())
1016 .sqrt()
1017 .max(1e-15);
1018 let new_lambda = norm;
1019 v = av.iter().map(|x| x / norm).collect();
1020 if (new_lambda - lambda).abs() < tol {
1021 lambda = new_lambda;
1022 break;
1023 }
1024 lambda = new_lambda;
1025 }
1026 (lambda, v)
1027}
1028
1029pub fn inverse_power_iteration(a: &[Vec<f64>], max_iter: usize, tol: f64) -> (f64, Vec<f64>) {
1031 let n = a.len();
1032 let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.0 }).collect();
1033 let mut lambda = 0.0;
1034 for _ in 0..max_iter {
1035 let w = lu_solve(a, &v);
1037 let norm = (w.iter().map(|x| x.powi(2)).sum::<f64>()).sqrt().max(1e-15);
1038 let new_lambda = 1.0 / norm;
1039 v = w.iter().map(|x| x / norm).collect();
1040 if (new_lambda - lambda).abs() < tol {
1041 lambda = new_lambda;
1042 break;
1043 }
1044 lambda = new_lambda;
1045 }
1046 (lambda, v)
1047}
1048
1049pub fn qr_eigenvalues(a: &[Vec<f64>], max_iter: usize) -> Vec<f64> {
1051 let n = a.len();
1052 let mut ak = a.to_vec();
1053 for _ in 0..max_iter {
1054 let (q, r) = qr_decomp_gs(&ak);
1055 let mut ak_new = vec![vec![0.0f64; n]; n];
1057 for i in 0..n {
1058 for j in 0..n {
1059 for k in 0..n {
1060 if k < q.len() && k < n {
1061 ak_new[i][j] += r[i][k] * q[k][j];
1062 }
1063 }
1064 }
1065 }
1066 ak = ak_new;
1067 let mut off_diag = 0.0f64;
1069 for oi in 0..n {
1070 for oj in 0..n {
1071 if oi != oj {
1072 off_diag += ak[oi][oj].powi(2);
1073 }
1074 }
1075 }
1076 if off_diag < 1e-20 {
1077 break;
1078 }
1079 }
1080 (0..n).map(|i| ak[i][i]).collect()
1081}
1082
1083pub fn arnoldi_eigenvalues(a: &[Vec<f64>], m: usize, max_iter: usize) -> Vec<f64> {
1087 let n = a.len();
1088 let m = m.min(n);
1089 let mut q: Vec<Vec<f64>> = Vec::with_capacity(m);
1090 let mut h = vec![vec![0.0f64; m]; m + 1];
1091 let mut v0 = vec![1.0 / (n as f64).sqrt(); n];
1093 let norm0 = (v0.iter().map(|x| x.powi(2)).sum::<f64>()).sqrt();
1094 v0 = v0.iter().map(|x| x / norm0).collect();
1095 q.push(v0.clone());
1096 for j in 0..m {
1097 let mut w = vec![0.0f64; n];
1099 for i in 0..n {
1100 for k in 0..n {
1101 w[i] += a[i][k] * q[j][k];
1102 }
1103 }
1104 for i in 0..=j {
1106 let hij: f64 = (0..n).map(|k| q[i][k] * w[k]).sum();
1107 h[i][j] = hij;
1108 for k in 0..n {
1109 w[k] -= hij * q[i][k];
1110 }
1111 }
1112 let norm_w = (w.iter().map(|x| x.powi(2)).sum::<f64>()).sqrt();
1113 h[j + 1][j] = norm_w;
1114 if norm_w < 1e-12 || q.len() >= m {
1115 break;
1116 }
1117 q.push(w.iter().map(|x| x / norm_w).collect());
1118 }
1119 let hm: Vec<Vec<f64>> = (0..m).map(|i| h[i].clone()).collect();
1121 qr_eigenvalues(&hm, max_iter)
1122}
1123
1124pub fn pseudo_arclength_continuation<F>(
1133 f: F,
1134 x0: f64,
1135 lambda0: f64,
1136 ds: f64,
1137 n_steps: usize,
1138 tol: f64,
1139) -> Vec<(f64, f64)>
1140where
1141 F: Fn(f64, f64) -> f64,
1142{
1143 let mut x = x0;
1144 let mut lam = lambda0;
1145 let mut path = vec![(x, lam)];
1146 let mut tx = 0.0_f64;
1148 let mut tl = 1.0_f64;
1149 for _ in 0..n_steps {
1150 let xp = x + ds * tx;
1152 let lp = lam + ds * tl;
1153 let mut xc = xp;
1155 let mut lc = lp;
1156 for _ in 0..20 {
1157 let residual = f(xc, lc);
1158 let arclength = (xc - xp) * tx + (lc - lp) * tl;
1159 if residual.abs() < tol && arclength.abs() < tol {
1160 break;
1161 }
1162 let eps = 1e-7;
1164 let dfdx = (f(xc + eps, lc) - f(xc - eps, lc)) / (2.0 * eps);
1165 let dfdl = (f(xc, lc + eps) - f(xc, lc - eps)) / (2.0 * eps);
1166 let det = dfdx * tl - dfdl * tx;
1168 if det.abs() < 1e-12 {
1169 break;
1170 }
1171 let dx_corr = -(residual * tl - arclength * dfdl) / det;
1172 let dl_corr = -(dfdx * arclength - dx_corr * tx) / tl.max(1e-12);
1173 xc += dx_corr * 0.5;
1174 lc += dl_corr * 0.5;
1175 }
1176 let eps = 1e-7;
1178 let dfdx = (f(xc + eps, lc) - f(xc - eps, lc)) / (2.0 * eps);
1179 let dfdl = (f(xc, lc + eps) - f(xc, lc - eps)) / (2.0 * eps);
1180 let tnorm = (dfdx.powi(2) + dfdl.powi(2)).sqrt().max(1e-12);
1181 tx = -dfdl / tnorm;
1182 tl = dfdx / tnorm;
1183 if tx * (xc - x) + tl * (lc - lam) < 0.0 {
1185 tx = -tx;
1186 tl = -tl;
1187 }
1188 x = xc;
1189 lam = lc;
1190 path.push((x, lam));
1191 }
1192 path
1193}
1194
1195#[derive(Debug, Clone, PartialEq)]
1201pub enum BifurcationType {
1202 SaddleNode,
1204 Pitchfork,
1206 Transcritical,
1208 Hopf,
1210}
1211
1212#[derive(Debug, Clone)]
1214pub struct BifurcationPoint {
1215 pub x: f64,
1217 pub lambda: f64,
1219 pub bif_type: BifurcationType,
1221}
1222
1223pub fn detect_bifurcations<F>(f: F, branch: &[(f64, f64)]) -> Vec<BifurcationPoint>
1228where
1229 F: Fn(f64, f64) -> f64,
1230{
1231 let mut bifs = Vec::new();
1232 if branch.len() < 2 {
1233 return bifs;
1234 }
1235 let eps = 1e-6;
1236 let mut prev_jac = {
1237 let (x, l) = branch[0];
1238 (f(x + eps, l) - f(x - eps, l)) / (2.0 * eps)
1239 };
1240 for i in 1..branch.len() {
1241 let (x, l) = branch[i];
1242 let jac = (f(x + eps, l) - f(x - eps, l)) / (2.0 * eps);
1243 if prev_jac * jac < 0.0 {
1245 let (x0, l0) = branch[i - 1];
1246 bifs.push(BifurcationPoint {
1247 x: (x0 + x) / 2.0,
1248 lambda: (l0 + l) / 2.0,
1249 bif_type: BifurcationType::SaddleNode,
1250 });
1251 }
1252 prev_jac = jac;
1253 }
1254 bifs
1255}
1256
1257pub fn transpose(a: &[Vec<f64>]) -> Vec<Vec<f64>> {
1263 if a.is_empty() {
1264 return Vec::new();
1265 }
1266 let m = a.len();
1267 let n = a[0].len();
1268 (0..n).map(|j| (0..m).map(|i| a[i][j]).collect()).collect()
1269}
1270
1271pub fn mat_vec(a: &[Vec<f64>], x: &[f64]) -> Vec<f64> {
1273 a.iter()
1274 .map(|row| row.iter().zip(x.iter()).map(|(aij, xj)| aij * xj).sum())
1275 .collect()
1276}
1277
1278pub fn mat_mul(a: &[Vec<f64>], b: &[Vec<f64>]) -> Vec<Vec<f64>> {
1280 let m = a.len();
1281 if m == 0 || b.is_empty() {
1282 return Vec::new();
1283 }
1284 let n = b[0].len();
1285 let k = b.len();
1286 let mut c = vec![vec![0.0f64; n]; m];
1287 for i in 0..m {
1288 for j in 0..n {
1289 for l in 0..k {
1290 c[i][j] += a[i][l] * b[l][j];
1291 }
1292 }
1293 }
1294 c
1295}
1296
1297pub fn frobenius_norm(a: &[Vec<f64>]) -> f64 {
1299 a.iter()
1300 .flat_map(|r| r.iter())
1301 .map(|x| x.powi(2))
1302 .sum::<f64>()
1303 .sqrt()
1304}
1305
1306pub fn infinity_norm(a: &[Vec<f64>]) -> f64 {
1308 a.iter()
1309 .map(|row| row.iter().map(|x| x.abs()).sum::<f64>())
1310 .fold(0.0_f64, f64::max)
1311}
1312
1313pub fn eye(n: usize) -> Vec<Vec<f64>> {
1315 (0..n)
1316 .map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
1317 .collect()
1318}
1319
1320#[cfg(test)]
1325mod tests {
1326 use super::*;
1327
1328 #[test]
1331 fn test_newton_raphson_sqrt2() {
1332 let result = newton_raphson(|x| x * x - 2.0, |x| 2.0 * x, 1.5, 1e-12, 50);
1333 assert!(result.converged);
1334 assert!((result.root - 2.0_f64.sqrt()).abs() < 1e-10);
1335 }
1336
1337 #[test]
1338 fn test_newton_raphson_cubic() {
1339 let result = newton_raphson(
1340 |x| x * x * x - x - 2.0,
1341 |x| 3.0 * x * x - 1.0,
1342 2.0,
1343 1e-12,
1344 50,
1345 );
1346 assert!(result.converged);
1347 assert!(result.f_val.abs() < 1e-10);
1348 }
1349
1350 #[test]
1351 fn test_secant_method() {
1352 let result = secant_method(|x| x * x - 9.0, 2.0, 4.0, 1e-12, 50);
1353 assert!(result.converged);
1354 assert!((result.root - 3.0).abs() < 1e-8);
1355 }
1356
1357 #[test]
1358 fn test_bisect_simple() {
1359 let result = bisect(|x| x - 5.0, 0.0, 10.0, 1e-10, 100);
1360 assert!(result.converged);
1361 assert!((result.root - 5.0).abs() < 1e-9);
1362 }
1363
1364 #[test]
1365 fn test_bisect_trigonometric() {
1366 let result = bisect(|x| x.sin(), 2.5, 3.5, 1e-10, 100);
1367 assert!(result.converged);
1368 assert!((result.root - PI).abs() < 1e-9);
1369 }
1370
1371 #[test]
1372 fn test_brentq_converges() {
1373 let result = brentq(|x| x.exp() - 2.0, 0.0, 2.0, 1e-10, 100);
1374 assert!(result.converged || result.f_val.abs() < 1e-8);
1375 }
1376
1377 #[test]
1378 fn test_brentq_bad_bracket() {
1379 let result = brentq(|x| x * x + 1.0, -1.0, 1.0, 1e-10, 50);
1380 assert!(!result.converged);
1381 }
1382
1383 #[test]
1386 fn test_euler_exponential() {
1387 let result = euler_ode(|_t, y| vec![-y[0]], 0.0, vec![1.0], 1.0, 0.001);
1388 let final_val = result.last().unwrap().1[0];
1389 assert!((final_val - (-1.0_f64).exp()).abs() < 0.01);
1390 }
1391
1392 #[test]
1393 fn test_rk2_exponential() {
1394 let result = rk2(|_t, y| vec![-y[0]], 0.0, vec![1.0], 1.0, 0.01);
1395 let final_val = result.last().unwrap().1[0];
1396 assert!((final_val - (-1.0_f64).exp()).abs() < 0.001);
1397 }
1398
1399 #[test]
1400 fn test_rk4_exponential() {
1401 let result = rk4(|_t, y| vec![-y[0]], 0.0, vec![1.0], 1.0, 0.01);
1402 let final_val = result.last().unwrap().1[0];
1403 assert!((final_val - (-1.0_f64).exp()).abs() < 1e-6);
1404 }
1405
1406 #[test]
1407 fn test_rk4_harmonic_oscillator() {
1408 let result = rk4(
1410 |_t, y| vec![y[1], -y[0]],
1411 0.0,
1412 vec![0.0, 1.0],
1413 2.0 * PI,
1414 0.01,
1415 );
1416 let final_val = result.last().unwrap().1[0];
1417 assert!(final_val.abs() < 0.01);
1418 }
1419
1420 #[test]
1421 fn test_rk45_step() {
1422 let (y_new, err) = rk45_step(&|_t, y: &[f64]| vec![-y[0]], 0.0, &[1.0], 0.1);
1423 assert!((y_new[0] - (-0.1_f64).exp()).abs() < 1e-5);
1424 assert!(err.len() == 1);
1425 }
1426
1427 #[test]
1428 fn test_rk45_adaptive() {
1429 let result = rk45_adaptive(|_t, y| vec![-y[0]], 0.0, vec![1.0], 1.0, 1e-6, 1e-9);
1430 let final_val = result.last().unwrap().1[0];
1431 assert!((final_val - (-1.0_f64).exp()).abs() < 1e-5);
1432 }
1433
1434 #[test]
1435 fn test_bvp_shoot() {
1436 let path = bvp_shoot(|_x, y, _dy| -PI * PI * y, 0.0, 1.0, 0.0, 0.0, PI, 100, 1e-6);
1438 assert!(!path.is_empty());
1439 }
1440
1441 #[test]
1444 fn test_fd1d_laplacian_size() {
1445 let mat = fd1d_laplacian(5, 0.25);
1446 assert_eq!(mat.len(), 3); assert_eq!(mat[0].len(), 3);
1448 }
1449
1450 #[test]
1451 fn test_solve_poisson_1d() {
1452 let n = 10;
1454 let dx = 1.0 / (n + 1) as f64;
1455 let rhs = vec![1.0; n];
1456 let u = solve_poisson_1d(&rhs, dx);
1457 let x_mid = 5.0 / (n + 1) as f64;
1458 let expected = x_mid * (1.0 - x_mid) / 2.0;
1459 assert!((u[4] - expected).abs() < 0.02);
1460 }
1461
1462 #[test]
1463 fn test_fd2d_laplacian() {
1464 let n = 5;
1465 let u: Vec<Vec<f64>> = (0..n)
1466 .map(|i| (0..n).map(|j| (i * j) as f64).collect())
1467 .collect();
1468 let lap = fd2d_laplacian(&u, 0.1, 0.1);
1469 assert_eq!(lap.len(), n);
1470 }
1471
1472 #[test]
1473 fn test_fd3d_laplacian() {
1474 let n = 4;
1475 let u = vec![vec![vec![1.0f64; n]; n]; n];
1476 let lap = fd3d_laplacian(&u, 0.1, 0.1, 0.1);
1477 assert_eq!(lap[2][2][2], 0.0);
1479 }
1480
1481 #[test]
1484 fn test_deriv_central_sin() {
1485 let d = deriv_central(|x| x.sin(), 0.0, 1e-5);
1486 assert!((d - 1.0).abs() < 1e-9); }
1488
1489 #[test]
1490 fn test_deriv_complex_step() {
1491 let d = deriv_complex_step(|x| x.powi(3), 2.0);
1492 assert!((d - 12.0).abs() < 1e-6); }
1494
1495 #[test]
1496 fn test_jacobian_identity() {
1497 let jac = jacobian(|x| x.to_vec(), &[1.0, 2.0, 3.0], 1e-5);
1498 for i in 0..3 {
1499 for j in 0..3 {
1500 let expected = if i == j { 1.0 } else { 0.0 };
1501 assert!((jac[i][j] - expected).abs() < 1e-6);
1502 }
1503 }
1504 }
1505
1506 #[test]
1509 fn test_gauss_legendre_polynomial() {
1510 let result = gauss_legendre_integrate(|x| x.powi(3), -1.0, 1.0, 3);
1512 assert!(result.abs() < 1e-12); }
1514
1515 #[test]
1516 fn test_gauss_legendre_sin() {
1517 let result = gauss_legendre_integrate(|x| x.sin(), 0.0, PI, 5);
1518 assert!((result - 2.0).abs() < 1e-6);
1519 }
1520
1521 #[test]
1522 fn test_gauss_hermite_gaussian() {
1523 let result = gauss_hermite_integrate(|_x| 1.0, 5);
1525 assert!((result - PI.sqrt()).abs() < 1e-6);
1526 }
1527
1528 #[test]
1529 fn test_adaptive_simpson() {
1530 let result = adaptive_simpson(&|x: f64| x.sin(), 0.0, PI, 1e-10, 10);
1531 assert!((result - 2.0).abs() < 1e-8);
1532 }
1533
1534 #[test]
1537 fn test_lu_solve_2x2() {
1538 let a = vec![vec![2.0, 1.0], vec![5.0, 7.0]];
1539 let b = vec![11.0, 13.0];
1540 let x = lu_solve(&a, &b);
1541 let residual: Vec<f64> = vec![
1543 a[0][0] * x[0] + a[0][1] * x[1] - b[0],
1544 a[1][0] * x[0] + a[1][1] * x[1] - b[1],
1545 ];
1546 for r in &residual {
1547 assert!(r.abs() < 1e-10);
1548 }
1549 }
1550
1551 #[test]
1552 fn test_lu_solve_3x3() {
1553 let a = vec![
1554 vec![1.0, 2.0, 3.0],
1555 vec![0.0, 1.0, 4.0],
1556 vec![5.0, 6.0, 0.0],
1557 ];
1558 let b = vec![14.0, 6.0, 2.0];
1559 let x = lu_solve(&a, &b);
1560 for (i, row) in a.iter().enumerate() {
1561 let sum: f64 = row.iter().zip(x.iter()).map(|(a, x)| a * x).sum();
1562 assert!((sum - b[i]).abs() < 1e-10);
1563 }
1564 }
1565
1566 #[test]
1567 fn test_cholesky_spd() {
1568 let a = vec![
1569 vec![4.0, 2.0, 2.0],
1570 vec![2.0, 5.0, 2.5],
1571 vec![2.0, 2.5, 3.5],
1572 ];
1573 let l = cholesky(&a);
1574 assert!(l.is_some());
1575 let l = l.unwrap();
1576 let n = 3;
1578 for i in 0..n {
1579 for j in 0..n {
1580 let entry: f64 = (0..n).map(|k| l[i][k] * l[j][k]).sum();
1581 assert!((entry - a[i][j]).abs() < 1e-10);
1582 }
1583 }
1584 }
1585
1586 #[test]
1587 fn test_cholesky_not_spd() {
1588 let a = vec![vec![-1.0, 0.0], vec![0.0, 1.0]];
1589 assert!(cholesky(&a).is_none());
1590 }
1591
1592 #[test]
1593 fn test_power_iteration() {
1594 let a = vec![vec![4.0, 1.0], vec![2.0, 3.0]];
1595 let (lambda, _v) = power_iteration(&a, 1000, 1e-10);
1596 assert!((lambda - 5.0).abs() < 0.1);
1597 }
1598
1599 #[test]
1600 fn test_qr_eigenvalues_symmetric() {
1601 let a = vec![
1602 vec![4.0, 1.0, 0.0],
1603 vec![1.0, 3.0, 1.0],
1604 vec![0.0, 1.0, 2.0],
1605 ];
1606 let eigs = qr_eigenvalues(&a, 100);
1607 assert_eq!(eigs.len(), 3);
1608 for e in &eigs {
1610 assert!(e.is_finite());
1611 }
1612 }
1613
1614 #[test]
1615 fn test_svd_singular_values() {
1616 let a = vec![vec![3.0, 0.0], vec![0.0, 2.0]];
1617 let svs = svd_singular_values(&a, 100);
1618 assert!(!svs.is_empty());
1620 assert!(svs[0] > 1.0);
1621 }
1622
1623 #[test]
1624 fn test_continuation() {
1625 let branch = pseudo_arclength_continuation(|x, lam| x * x - lam, 1.0, 1.0, 0.1, 10, 1e-8);
1627 assert!(branch.len() > 1);
1628 for (x, lam) in &branch {
1629 assert!((x * x - lam).abs() < 0.1);
1630 }
1631 }
1632
1633 #[test]
1634 fn test_bifurcation_detection() {
1635 let branch: Vec<(f64, f64)> = (-5..=5)
1637 .map(|i| {
1638 let lam = i as f64;
1639 let x = if lam >= 0.0 { lam.sqrt() } else { 0.0 };
1640 (x, lam)
1641 })
1642 .collect();
1643 let bifs = detect_bifurcations(|x, lam| x * x - lam, &branch);
1644 let _ = bifs; }
1647
1648 #[test]
1649 fn test_matrix_multiply() {
1650 let a = eye(3);
1651 let b = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]];
1652 let c = mat_mul(&a, &b);
1653 assert_eq!(c, b);
1654 }
1655
1656 #[test]
1657 fn test_frobenius_norm() {
1658 let a = eye(3);
1659 assert!((frobenius_norm(&a) - 3.0_f64.sqrt()).abs() < 1e-10);
1660 }
1661
1662 #[test]
1663 fn test_transpose() {
1664 let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
1665 let at = transpose(&a);
1666 assert_eq!(at.len(), 3);
1667 assert_eq!(at[0].len(), 2);
1668 assert_eq!(at[0][0], 1.0);
1669 assert_eq!(at[0][1], 4.0);
1670 }
1671
1672 #[test]
1673 fn test_gauss_legendre_nodes_count() {
1674 for n in 1..=5 {
1675 let (nodes, weights) = gauss_legendre_nodes(n);
1676 assert_eq!(nodes.len(), n);
1677 assert_eq!(weights.len(), n);
1678 }
1679 }
1680
1681 #[test]
1682 fn test_infinity_norm() {
1683 let a = vec![vec![1.0, -2.0, 3.0], vec![0.0, 1.0, 0.0]];
1684 assert!((infinity_norm(&a) - 6.0).abs() < 1e-10);
1685 }
1686}