mod complex;
mod complex_linalg;
mod dense_matrix;
mod eigen;
mod fft;
mod inertia;
mod linalg;
mod number_theory;
mod ode;
mod optimize;
mod rng;
mod roots;
mod solvers;
mod sparse;
mod stability;
mod summation;
mod svd;
pub use complex::Complex;
pub use complex_linalg::*;
pub use dense_matrix::*;
pub use eigen::*;
pub use fft::*;
pub use inertia::*;
pub use linalg::*;
pub use number_theory::*;
pub use ode::*;
pub use optimize::*;
pub use rng::*;
pub use roots::*;
pub use solvers::*;
pub use sparse::*;
pub use stability::*;
pub use summation::*;
pub use svd::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::HisabError;
const EPSILON: f64 = 1e-6;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn newton_sqrt2() {
let root = newton_raphson(|x| x * x - 2.0, |x| 2.0 * x, 1.5, 1e-10, 100).unwrap();
assert!((root - std::f64::consts::SQRT_2).abs() < 1e-9);
}
#[test]
fn newton_cube_root_27() {
let root = newton_raphson(|x| x * x * x - 27.0, |x| 3.0 * x * x, 2.0, 1e-10, 100).unwrap();
assert!(approx_eq(root, 3.0));
}
#[test]
fn newton_no_convergence() {
let result = newton_raphson(|x| x * x + 1.0, |x| 2.0 * x, 1.0, 1e-15, 5);
assert!(result.is_err());
}
#[test]
fn bisection_sqrt2() {
let root = bisection(|x| x * x - 2.0, 1.0, 2.0, 1e-10, 100).unwrap();
assert!((root - std::f64::consts::SQRT_2).abs() < 1e-9);
}
#[test]
fn bisection_cubic_root() {
let root = bisection(|x| x * x * x - 8.0, 1.0, 3.0, 1e-10, 100).unwrap();
assert!(approx_eq(root, 2.0));
}
#[test]
fn bisection_same_sign_error() {
let result = bisection(|x| x * x + 1.0, 1.0, 2.0, 1e-10, 100);
assert!(result.is_err());
}
#[test]
fn bisection_sin_root() {
let root = bisection(f64::sin, 3.0, 4.0, 1e-10, 100).unwrap();
assert!((root - std::f64::consts::PI).abs() < 1e-9);
}
#[test]
fn gaussian_2x2() {
let mut matrix = vec![vec![2.0, 1.0, 5.0], vec![1.0, 3.0, 10.0]];
let x = gaussian_elimination(&mut matrix).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 3.0));
}
#[test]
fn gaussian_3x3() {
let mut matrix = vec![
vec![1.0, 1.0, 1.0, 6.0],
vec![2.0, 1.0, -1.0, 1.0],
vec![1.0, -1.0, 1.0, 2.0],
];
let x = gaussian_elimination(&mut matrix).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 2.0));
assert!(approx_eq(x[2], 3.0));
}
#[test]
fn gaussian_singular_matrix() {
let mut matrix = vec![vec![1.0, 2.0, 3.0], vec![2.0, 4.0, 6.0]];
let result = gaussian_elimination(&mut matrix);
assert!(result.is_err());
}
#[test]
fn gaussian_empty_matrix() {
let mut matrix: Vec<Vec<f64>> = vec![];
let result = gaussian_elimination(&mut matrix);
assert!(result.is_err());
}
#[test]
fn newton_zero_derivative_error() {
let result = newton_raphson(|x| x * x + 1.0, |_| 0.0, 2.0, 1e-10, 100);
assert!(result.is_err());
}
#[test]
fn error_display() {
let e = HisabError::NoConvergence(50);
assert_eq!(e.to_string(), "no convergence after 50 iterations");
let e = HisabError::SingularPivot;
assert!(e.to_string().contains("singular"));
}
#[test]
fn error_display_invalid_input() {
let e = HisabError::InvalidInput("bad data".to_string());
assert_eq!(e.to_string(), "invalid input: bad data");
}
#[test]
fn newton_linear_root() {
let root = newton_raphson(|x| 2.0 * x - 6.0, |_| 2.0, 0.0, 1e-10, 10).unwrap();
assert!(approx_eq(root, 3.0));
}
#[test]
fn newton_sin_root_near_zero() {
let root = newton_raphson(f64::sin, f64::cos, 3.0, 1e-12, 50).unwrap();
assert!((root - std::f64::consts::PI).abs() < 1e-10);
}
#[test]
fn bisection_linear() {
let root = bisection(|x| x - 5.0, 0.0, 10.0, 1e-10, 100).unwrap();
assert!(approx_eq(root, 5.0));
}
#[test]
fn bisection_negative_interval() {
let root = bisection(|x| x + 3.0, -5.0, 0.0, 1e-10, 100).unwrap();
assert!(approx_eq(root, -3.0));
}
#[test]
fn bisection_swaps_when_f_lo_positive() {
let root = bisection(|x| x - 1.0, 2.0, 0.0, 1e-10, 100).unwrap();
assert!(approx_eq(root, 1.0));
}
#[test]
fn gaussian_1x1() {
let mut matrix = vec![vec![3.0, 9.0]];
let x = gaussian_elimination(&mut matrix).unwrap();
assert!(approx_eq(x[0], 3.0));
}
#[test]
fn gaussian_needs_pivoting() {
let mut matrix = vec![vec![0.0, 1.0, 3.0], vec![1.0, 1.0, 5.0]];
let x = gaussian_elimination(&mut matrix).unwrap();
assert!(approx_eq(x[0], 2.0));
assert!(approx_eq(x[1], 3.0));
}
#[test]
fn gaussian_wrong_column_count() {
let mut matrix = vec![vec![1.0, 2.0], vec![3.0, 4.0, 5.0]];
let result = gaussian_elimination(&mut matrix);
assert!(result.is_err());
}
#[test]
fn gaussian_4x4() {
let mut matrix = vec![
vec![1.0, 1.0, 1.0, 1.0, 4.0],
vec![2.0, 1.0, 1.0, 1.0, 5.0],
vec![1.0, 3.0, 1.0, 1.0, 6.0],
vec![1.0, 1.0, 1.0, 4.0, 7.0],
];
let x = gaussian_elimination(&mut matrix).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 1.0));
assert!(approx_eq(x[2], 1.0));
assert!(approx_eq(x[3], 1.0));
}
#[test]
fn newton_converges_exactly_at_root() {
let root = newton_raphson(|x| x - 5.0, |_| 1.0, 5.0, 1e-10, 1).unwrap();
assert!(approx_eq(root, 5.0));
}
#[test]
fn bisection_exact_root_at_midpoint() {
let root = bisection(|x| x, -1.0, 1.0, 1e-10, 1).unwrap();
assert!(approx_eq(root, 0.0));
}
#[test]
fn lu_decompose_2x2() {
let a = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
let (lu, _pivot) = lu_decompose(&a).unwrap();
assert!(approx_eq(lu[0][0], 2.0));
assert!(approx_eq(lu[0][1], 1.0));
}
#[test]
fn lu_solve_2x2() {
let a = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
let b = [5.0, 10.0];
let (lu, pivot) = lu_decompose(&a).unwrap();
let x = lu_solve(&lu, &pivot, &b).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 3.0));
}
#[test]
fn lu_solve_3x3() {
let a = vec![
vec![1.0, 1.0, 1.0],
vec![2.0, 1.0, -1.0],
vec![1.0, -1.0, 1.0],
];
let b = [6.0, 1.0, 2.0];
let (lu, pivot) = lu_decompose(&a).unwrap();
let x = lu_solve(&lu, &pivot, &b).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 2.0));
assert!(approx_eq(x[2], 3.0));
}
#[test]
fn lu_singular() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
assert!(lu_decompose(&a).is_err());
}
#[test]
fn lu_needs_pivoting() {
let a = vec![vec![0.0, 1.0], vec![1.0, 1.0]];
let b = [3.0, 5.0];
let (lu, pivot) = lu_decompose(&a).unwrap();
let x = lu_solve(&lu, &pivot, &b).unwrap();
assert!(approx_eq(x[0], 2.0));
assert!(approx_eq(x[1], 3.0));
}
#[test]
fn cholesky_2x2() {
let a = vec![vec![4.0, 2.0], vec![2.0, 3.0]];
let l = cholesky(&a).unwrap();
assert!(approx_eq(l[0][0], 2.0));
assert!(approx_eq(l[1][0], 1.0));
assert!(approx_eq(l[1][1], (2.0f64).sqrt()));
}
#[test]
fn cholesky_solve_2x2() {
let a = vec![vec![4.0, 2.0], vec![2.0, 3.0]];
let b = [8.0, 7.0];
let l = cholesky(&a).unwrap();
let x = cholesky_solve(&l, &b).unwrap();
let r0 = 4.0 * x[0] + 2.0 * x[1];
let r1 = 2.0 * x[0] + 3.0 * x[1];
assert!(approx_eq(r0, 8.0));
assert!(approx_eq(r1, 7.0));
}
#[test]
fn cholesky_3x3_identity() {
let a = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
];
let l = cholesky(&a).unwrap();
assert!(approx_eq(l[0][0], 1.0));
assert!(approx_eq(l[1][1], 1.0));
assert!(approx_eq(l[2][2], 1.0));
assert!(approx_eq(l[1][0], 0.0));
assert!(approx_eq(l[2][0], 0.0));
assert!(approx_eq(l[2][1], 0.0));
}
#[test]
fn cholesky_not_positive_definite() {
let a = vec![vec![-1.0, 0.0], vec![0.0, 1.0]];
assert!(cholesky(&a).is_err());
}
#[test]
fn qr_orthogonality() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]; let (q, _r) = qr_decompose(&a).unwrap();
let dot: f64 = (0..3).map(|k| q[0][k] * q[1][k]).sum();
assert!(dot.abs() < 1e-10);
for col in &q {
let norm: f64 = col.iter().map(|x| x * x).sum::<f64>().sqrt();
assert!(approx_eq(norm, 1.0));
}
}
#[test]
fn qr_reconstruct() {
let a = vec![vec![1.0, 0.0, 1.0], vec![1.0, 1.0, 0.0]]; let (q, r) = qr_decompose(&a).unwrap();
for j in 0..2 {
for i in 0..3 {
let mut sum = 0.0;
for k in 0..2 {
sum += q[k][i] * r[j][k];
}
assert!((sum - a[j][i]).abs() < 1e-10);
}
}
}
#[test]
fn qr_dependent_columns() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]]; assert!(qr_decompose(&a).is_err());
}
#[test]
fn least_squares_linear_exact() {
let x = [0.0, 1.0, 2.0, 3.0];
let y = [1.0, 3.0, 5.0, 7.0];
let coeffs = least_squares_poly(&x, &y, 1).unwrap();
assert!(approx_eq(coeffs[0], 1.0)); assert!(approx_eq(coeffs[1], 2.0)); }
#[test]
fn least_squares_quadratic() {
let x = [0.0, 1.0, 2.0, 3.0, 4.0];
let y: Vec<f64> = x.iter().map(|&xi| xi * xi).collect();
let coeffs = least_squares_poly(&x, &y, 2).unwrap();
assert!(approx_eq(coeffs[0], 0.0)); assert!(approx_eq(coeffs[1], 0.0)); assert!(approx_eq(coeffs[2], 1.0)); }
#[test]
fn least_squares_constant() {
let x = [1.0, 2.0, 3.0];
let y = [5.0, 5.0, 5.0];
let coeffs = least_squares_poly(&x, &y, 0).unwrap();
assert!(approx_eq(coeffs[0], 5.0));
}
#[test]
fn least_squares_noisy_linear() {
let x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = [2.1, 4.9, 8.1, 10.9, 14.1, 16.9];
let coeffs = least_squares_poly(&x, &y, 1).unwrap();
assert!((coeffs[0] - 2.0).abs() < 0.5);
assert!((coeffs[1] - 3.0).abs() < 0.2);
}
#[test]
fn least_squares_insufficient_points() {
let x = [1.0];
let y = [5.0];
assert!(least_squares_poly(&x, &y, 2).is_err());
}
#[test]
fn lu_solve_wrong_b_length() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let (lu, pivot) = lu_decompose(&a).unwrap();
assert!(lu_solve(&lu, &pivot, &[1.0]).is_err());
}
#[test]
fn lu_non_square() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
assert!(lu_decompose(&a).is_err());
}
#[test]
fn cholesky_solve_wrong_b_length() {
let a = vec![vec![4.0, 2.0], vec![2.0, 3.0]];
let l = cholesky(&a).unwrap();
assert!(cholesky_solve(&l, &[1.0, 2.0, 3.0]).is_err());
}
#[test]
fn cholesky_non_square() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
assert!(cholesky(&a).is_err());
}
#[test]
fn cholesky_solve_3x3_verify() {
let a = vec![
vec![4.0, 2.0, 1.0],
vec![2.0, 5.0, 2.0],
vec![1.0, 2.0, 6.0],
];
let b = [1.0, 2.0, 3.0];
let l = cholesky(&a).unwrap();
let x = cholesky_solve(&l, &b).unwrap();
for i in 0..3 {
let mut row_sum = 0.0;
for j in 0..3 {
row_sum += a[i][j] * x[j];
}
assert!(approx_eq(row_sum, b[i]));
}
}
#[test]
fn qr_square_matrix() {
let a = vec![
vec![1.0, 4.0, 7.0],
vec![2.0, 5.0, 8.0],
vec![3.0, 6.0, 10.0], ];
let (q, _r) = qr_decompose(&a).unwrap();
for i in 0..3 {
for j in 0..3 {
let dot: f64 = (0..3).map(|k| q[i][k] * q[j][k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!((dot - expected).abs() < 1e-10);
}
}
}
#[test]
fn least_squares_mismatched_lengths() {
let x = [1.0, 2.0, 3.0];
let y = [1.0, 2.0];
assert!(least_squares_poly(&x, &y, 1).is_err());
}
#[test]
fn least_squares_empty() {
let x: [f64; 0] = [];
let y: [f64; 0] = [];
assert!(least_squares_poly(&x, &y, 0).is_err());
}
#[test]
fn lu_multiple_rhs() {
let a = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
let (lu, pivot) = lu_decompose(&a).unwrap();
let x1 = lu_solve(&lu, &pivot, &[5.0, 10.0]).unwrap();
let x2 = lu_solve(&lu, &pivot, &[3.0, 7.0]).unwrap();
assert!(approx_eq(x1[0], 1.0));
assert!(approx_eq(x1[1], 3.0));
assert!(approx_eq(x2[0], 0.4));
assert!(approx_eq(x2[1], 2.2));
}
#[test]
fn eigenvalue_2x2_diagonal() {
let a = vec![vec![5.0, 0.0], vec![0.0, 2.0]];
let (eval, evec) = eigenvalue_power(&a, 1e-10, 100).unwrap();
assert!(approx_eq(eval, 5.0));
assert!(evec[0].abs() > 0.9);
}
#[test]
fn eigenvalue_3x3_symmetric() {
let a = vec![
vec![2.0, 1.0, 0.0],
vec![1.0, 3.0, 1.0],
vec![0.0, 1.0, 2.0],
];
let (eval, _evec) = eigenvalue_power(&a, 1e-10, 200).unwrap();
assert!((eval - 4.0).abs() < 0.01);
}
#[test]
fn eigenvalue_identity() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let (eval, _) = eigenvalue_power(&a, 1e-10, 100).unwrap();
assert!(approx_eq(eval, 1.0));
}
#[test]
fn eigenvalue_empty() {
let a: Vec<Vec<f64>> = vec![];
assert!(eigenvalue_power(&a, 1e-10, 100).is_err());
}
#[test]
fn complex_arithmetic() {
let a = Complex::new(3.0, 4.0);
let b = Complex::new(1.0, -2.0);
let sum = a + b;
assert!(approx_eq(sum.re, 4.0));
assert!(approx_eq(sum.im, 2.0));
let diff = a - b;
assert!(approx_eq(diff.re, 2.0));
assert!(approx_eq(diff.im, 6.0));
let prod = a * b;
assert!(approx_eq(prod.re, 11.0));
assert!(approx_eq(prod.im, -2.0));
}
#[test]
fn complex_abs_and_conj() {
let z = Complex::new(3.0, 4.0);
assert!(approx_eq(z.abs(), 5.0));
let c = z.conj();
assert!(approx_eq(c.re, 3.0));
assert!(approx_eq(c.im, -4.0));
}
#[test]
fn fft_dc_signal() {
let mut data = [
Complex::from_real(1.0),
Complex::from_real(1.0),
Complex::from_real(1.0),
Complex::from_real(1.0),
];
fft(&mut data).unwrap();
assert!(approx_eq(data[0].re, 4.0));
assert!(approx_eq(data[0].im, 0.0));
assert!(approx_eq(data[1].abs(), 0.0));
assert!(approx_eq(data[2].abs(), 0.0));
assert!(approx_eq(data[3].abs(), 0.0));
}
#[test]
fn fft_single_frequency() {
let mut data = [
Complex::from_real(1.0),
Complex::from_real(-1.0),
Complex::from_real(1.0),
Complex::from_real(-1.0),
];
fft(&mut data).unwrap();
assert!(approx_eq(data[0].abs(), 0.0)); assert!(approx_eq(data[2].re, 4.0)); }
#[test]
fn fft_ifft_roundtrip() {
let original = [
Complex::new(1.0, 0.0),
Complex::new(2.0, -1.0),
Complex::new(0.0, 3.0),
Complex::new(-1.0, 2.0),
];
let mut data = original;
fft(&mut data).unwrap();
ifft(&mut data).unwrap();
for i in 0..4 {
assert!((data[i].re - original[i].re).abs() < 1e-10);
assert!((data[i].im - original[i].im).abs() < 1e-10);
}
}
#[test]
fn fft_8_point() {
let mut data: Vec<Complex> = (0..8).map(|i| Complex::from_real(i as f64)).collect();
fft(&mut data).unwrap();
assert!(approx_eq(data[0].re, 28.0));
assert!(approx_eq(data[0].im, 0.0));
}
#[test]
fn fft_ifft_roundtrip_8() {
let original: Vec<Complex> = (0..8)
.map(|i| Complex::new(i as f64, (i as f64 * 0.5).sin()))
.collect();
let mut data = original.clone();
fft(&mut data).unwrap();
ifft(&mut data).unwrap();
for i in 0..8 {
assert!((data[i].re - original[i].re).abs() < 1e-10);
assert!((data[i].im - original[i].im).abs() < 1e-10);
}
}
#[test]
fn fft_parseval() {
let original: Vec<Complex> = vec![
Complex::new(1.0, 0.0),
Complex::new(0.0, 1.0),
Complex::new(-1.0, 0.0),
Complex::new(0.0, -1.0),
];
let time_energy: f64 = original.iter().map(|c| c.re * c.re + c.im * c.im).sum();
let mut freq = original;
fft(&mut freq).unwrap();
let freq_energy: f64 = freq.iter().map(|c| c.re * c.re + c.im * c.im).sum();
assert!((4.0 * time_energy - freq_energy).abs() < 1e-10);
}
#[test]
fn fft_single_element() {
let mut data = [Complex::new(42.0, -7.0)];
fft(&mut data).unwrap();
assert!(approx_eq(data[0].re, 42.0));
assert!(approx_eq(data[0].im, -7.0));
}
#[test]
fn eigenvalue_non_square() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
assert!(eigenvalue_power(&a, 1e-10, 100).is_err());
}
#[test]
fn eigenvalue_negative_dominant() {
let a = vec![vec![-3.0, 0.0], vec![0.0, 1.0]];
let (eval, _) = eigenvalue_power(&a, 1e-10, 100).unwrap();
assert!(approx_eq(eval, -3.0));
}
#[test]
fn eigenvalue_1x1() {
let a = vec![vec![7.0]];
let (eval, evec) = eigenvalue_power(&a, 1e-10, 10).unwrap();
assert!(approx_eq(eval, 7.0));
assert!(approx_eq(evec[0], 1.0));
}
#[test]
fn complex_default() {
let c = Complex::default();
assert!(approx_eq(c.re, 0.0));
assert!(approx_eq(c.im, 0.0));
}
#[test]
fn complex_display() {
assert_eq!(Complex::new(3.0, 4.0).to_string(), "3+4i");
assert_eq!(Complex::new(3.0, -4.0).to_string(), "3-4i");
assert_eq!(Complex::new(0.0, 0.0).to_string(), "0+0i");
}
#[test]
fn complex_from_real() {
let c = Complex::from_real(5.0);
assert!(approx_eq(c.re, 5.0));
assert!(approx_eq(c.im, 0.0));
}
#[test]
fn complex_scalar_mul() {
let c = Complex::new(3.0, 4.0) * 2.0;
assert!(approx_eq(c.re, 6.0));
assert!(approx_eq(c.im, 8.0));
}
#[test]
fn fft_linearity() {
let x: Vec<Complex> = (0..4).map(|i| Complex::from_real(i as f64)).collect();
let y: Vec<Complex> = (0..4)
.map(|i| Complex::from_real((i as f64).sin()))
.collect();
let mut fx = x.clone();
fft(&mut fx).unwrap();
let mut fy = y.clone();
fft(&mut fy).unwrap();
let mut combined: Vec<Complex> = (0..4).map(|i| x[i] * 2.0 + y[i] * 3.0).collect();
fft(&mut combined).unwrap();
for i in 0..4 {
let expected = fx[i] * 2.0 + fy[i] * 3.0;
assert!((combined[i].re - expected.re).abs() < 1e-10);
assert!((combined[i].im - expected.im).abs() < 1e-10);
}
}
#[test]
fn ifft_single_element() {
let mut data = [Complex::new(42.0, -7.0)];
ifft(&mut data).unwrap();
assert!(approx_eq(data[0].re, 42.0));
assert!(approx_eq(data[0].im, -7.0));
}
#[test]
fn fft_non_power_of_two_panics() {
let mut data = vec![Complex::default(); 3];
assert!(fft(&mut data).is_err());
}
#[test]
fn dst_idst_roundtrip() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let transformed = dst(&data).unwrap();
let recovered = idst(&transformed).unwrap();
for i in 0..data.len() {
assert!((recovered[i] - data[i]).abs() < 1e-10);
}
}
#[test]
fn dst_known_values() {
let data = vec![1.0, 1.0, 1.0];
let result = dst(&data).unwrap();
let expected_0 = 1.0 + std::f64::consts::SQRT_2;
assert!((result[0] - expected_0).abs() < 1e-10);
}
#[test]
fn dst_single_element() {
let result = dst(&[7.0]).unwrap();
assert!((result[0] - 7.0).abs() < 1e-10);
}
#[test]
fn dst_empty_error() {
assert!(dst(&[]).is_err());
}
#[test]
fn idst_empty_error() {
assert!(idst(&[]).is_err());
}
#[test]
fn dct_idct_roundtrip() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let transformed = dct(&data).unwrap();
let recovered = idct(&transformed).unwrap();
for i in 0..data.len() {
assert!((recovered[i] - data[i]).abs() < 1e-10);
}
}
#[test]
fn dct_dc_component() {
let data = vec![1.0, 2.0, 3.0, 4.0];
let result = dct(&data).unwrap();
assert!((result[0] - 10.0).abs() < 1e-10);
}
#[test]
fn dct_single_element() {
let result = dct(&[5.0]).unwrap();
assert!((result[0] - 5.0).abs() < 1e-10);
}
#[test]
fn dct_empty_error() {
assert!(dct(&[]).is_err());
}
#[test]
fn idct_empty_error() {
assert!(idct(&[]).is_err());
}
#[test]
fn dct_idct_roundtrip_large() {
let data: Vec<f64> = (0..16).map(|i| (i as f64 * 0.3).sin()).collect();
let transformed = dct(&data).unwrap();
let recovered = idct(&transformed).unwrap();
for i in 0..data.len() {
assert!((recovered[i] - data[i]).abs() < 1e-10);
}
}
#[test]
fn dst_idst_roundtrip_large() {
let data: Vec<f64> = (0..16).map(|i| (i as f64 * 0.7).cos()).collect();
let transformed = dst(&data).unwrap();
let recovered = idst(&transformed).unwrap();
for i in 0..data.len() {
assert!((recovered[i] - data[i]).abs() < 1e-10);
}
}
#[test]
fn rk4_exponential_growth() {
let y = rk4(
|_t, y, out: &mut [f64]| {
out[0] = y[0];
},
0.0,
&[1.0],
1.0,
1000,
)
.unwrap();
assert!((y[0] - std::f64::consts::E).abs() < 1e-8);
}
#[test]
fn rk4_linear_ode() {
let y = rk4(
|_t, _y, out: &mut [f64]| {
out[0] = 2.0;
},
0.0,
&[0.0],
5.0,
100,
)
.unwrap();
assert!(approx_eq(y[0], 10.0));
}
#[test]
fn rk4_harmonic_oscillator() {
let y = rk4(
|_t, y, out: &mut [f64]| {
out[0] = y[1];
out[1] = -y[0];
},
0.0,
&[1.0, 0.0],
std::f64::consts::PI,
10000,
)
.unwrap();
assert!((y[0] - (-1.0)).abs() < 1e-6); assert!(y[1].abs() < 1e-6); }
#[test]
fn rk4_quadratic_exact() {
let y = rk4(
|t, _y, out: &mut [f64]| {
out[0] = 2.0 * t;
},
0.0,
&[0.0],
3.0,
10,
)
.unwrap();
assert!((y[0] - 9.0).abs() < 1e-10);
}
#[test]
fn rk4_system_2d() {
let y = rk4(
|_t, y, out: &mut [f64]| {
out[0] = -y[1];
out[1] = y[0];
},
0.0,
&[1.0, 0.0],
std::f64::consts::FRAC_PI_2,
1000,
)
.unwrap();
assert!(y[0].abs() < 1e-6);
assert!((y[1] - 1.0).abs() < 1e-6);
}
#[test]
fn rk4_trajectory_length() {
let traj = rk4_trajectory(
|_t, y, out: &mut [f64]| {
out[0] = y[0];
},
0.0,
&[1.0],
1.0,
100,
)
.unwrap();
assert_eq!(traj.len(), 101); assert!(approx_eq(traj[0].0, 0.0));
assert!((traj[100].0 - 1.0).abs() < 1e-10);
}
#[test]
fn rk4_trajectory_matches_final() {
let f = |_t: f64, y: &[f64], out: &mut [f64]| {
out[0] = y[0];
};
let final_state = rk4(f, 0.0, &[1.0], 1.0, 100).unwrap();
let traj = rk4_trajectory(f, 0.0, &[1.0], 1.0, 100).unwrap();
let traj_final = &traj[100].1;
assert!((final_state[0] - traj_final[0]).abs() < 1e-12);
}
#[test]
fn rk4_damped_oscillator() {
let y = rk4(
|_t, y, out: &mut [f64]| {
out[0] = y[1];
out[1] = -y[0] - 0.1 * y[1];
},
0.0,
&[1.0, 0.0],
20.0,
10000,
)
.unwrap();
assert!(y[0].abs() < 0.5);
}
#[test]
fn complex_div() {
let a = Complex::new(4.0, 2.0);
let b = Complex::new(1.0, 1.0);
let c = a / b;
assert!(approx_eq(c.re, 3.0));
assert!(approx_eq(c.im, -1.0));
}
#[test]
fn complex_div_scalar() {
let a = Complex::new(6.0, 4.0);
let c = a / 2.0;
assert!(approx_eq(c.re, 3.0));
assert!(approx_eq(c.im, 2.0));
}
#[test]
fn complex_neg() {
let a = Complex::new(3.0, -4.0);
let b = -a;
assert!(approx_eq(b.re, -3.0));
assert!(approx_eq(b.im, 4.0));
}
#[test]
fn complex_from_f64() {
let c: Complex = 5.0.into();
assert!(approx_eq(c.re, 5.0));
assert!(approx_eq(c.im, 0.0));
}
#[test]
fn complex_from_tuple() {
let c: Complex = (3.0, 4.0).into();
assert!(approx_eq(c.re, 3.0));
assert!(approx_eq(c.im, 4.0));
}
#[test]
fn complex_serde_roundtrip() {
let c = Complex::new(1.5, -2.5);
let json = serde_json::to_string(&c).unwrap();
let c2: Complex = serde_json::from_str(&json).unwrap();
assert_eq!(c, c2);
}
#[test]
fn determinant_2x2() {
let a = vec![vec![3.0, 8.0], vec![4.0, 6.0]];
let det = matrix_determinant(&a).unwrap();
assert!(approx_eq(det, -14.0));
}
#[test]
fn determinant_3x3() {
let a = vec![
vec![6.0, 1.0, 1.0],
vec![4.0, -2.0, 5.0],
vec![2.0, 8.0, 7.0],
];
let det = matrix_determinant(&a).unwrap();
assert!((det - (-306.0)).abs() < 1e-6);
}
#[test]
fn determinant_identity() {
let a = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
];
let det = matrix_determinant(&a).unwrap();
assert!(approx_eq(det, 1.0));
}
#[test]
fn determinant_singular() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
let det = matrix_determinant(&a).unwrap();
assert!(approx_eq(det, 0.0));
}
#[test]
fn determinant_empty() {
let a: Vec<Vec<f64>> = vec![];
assert!(matrix_determinant(&a).is_err());
}
#[test]
fn trace_3x3() {
let a = vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0, 6.0],
vec![7.0, 8.0, 9.0],
];
let tr = matrix_trace(&a).unwrap();
assert!(approx_eq(tr, 15.0));
}
#[test]
fn trace_identity() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
assert!(approx_eq(matrix_trace(&a).unwrap(), 2.0));
}
#[test]
fn trace_empty() {
let a: Vec<Vec<f64>> = vec![];
assert!(matrix_trace(&a).is_err());
}
#[test]
fn multiply_2x2() {
let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
let b = vec![vec![5.0, 6.0], vec![7.0, 8.0]];
let c = matrix_multiply(&a, &b).unwrap();
assert!(approx_eq(c[0][0], 19.0));
assert!(approx_eq(c[0][1], 22.0));
assert!(approx_eq(c[1][0], 43.0));
assert!(approx_eq(c[1][1], 50.0));
}
#[test]
fn multiply_non_square() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let b = vec![vec![7.0, 8.0], vec![9.0, 10.0], vec![11.0, 12.0]];
let c = matrix_multiply(&a, &b).unwrap();
assert_eq!(c.len(), 2);
assert_eq!(c[0].len(), 2);
assert!(approx_eq(c[0][0], 58.0));
assert!(approx_eq(c[1][1], 154.0));
}
#[test]
fn multiply_dimension_mismatch() {
let a = vec![vec![1.0, 2.0]];
let b = vec![vec![1.0], vec![2.0], vec![3.0]];
assert!(matrix_multiply(&a, &b).is_err());
}
#[test]
fn multiply_identity() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let b = vec![vec![3.0, 4.0], vec![5.0, 6.0]];
let c = matrix_multiply(&a, &b).unwrap();
assert!(approx_eq(c[0][0], 3.0));
assert!(approx_eq(c[1][1], 6.0));
}
#[test]
fn lu_in_place_2x2() {
let mut a = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
let pivot = lu_decompose_in_place(&mut a).unwrap();
let x = lu_solve(&a, &pivot, &[5.0, 10.0]).unwrap();
assert!(approx_eq(x[0], 1.0));
assert!(approx_eq(x[1], 3.0));
}
#[test]
fn lu_in_place_singular() {
let mut a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
assert!(lu_decompose_in_place(&mut a).is_err());
}
#[test]
fn qr_in_place_reconstruct() {
let original = vec![
vec![1.0, 4.0, 7.0],
vec![2.0, 5.0, 8.0],
vec![3.0, 6.0, 10.0],
];
let mut a = original.clone();
let r = qr_decompose_in_place(&mut a).unwrap();
let n = a.len();
let m = a[0].len();
for i in 0..n {
for j in 0..n {
let dot: f64 = (0..m).map(|k| a[i][k] * a[j][k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!((dot - expected).abs() < 1e-10);
}
}
drop(r);
drop(original);
}
#[allow(clippy::needless_range_loop)]
fn svd_check_reconstruction(a: &[Vec<f64>], tol: f64) {
let result = svd(a).unwrap();
let m = a.len();
let n = a[0].len();
let k = result.sigma.len();
for i in 0..m {
for j in 0..n {
let mut val = 0.0;
for s in 0..k {
val += result.u[s][i] * result.sigma[s] * result.vt[s][j];
}
assert!(
(val - a[i][j]).abs() < tol,
"SVD reconstruction failed at [{i}][{j}]: got {val}, expected {}",
a[i][j]
);
}
}
}
#[test]
fn csr_from_dense_roundtrip() {
let a = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 2.0, 3.0],
vec![4.0, 0.0, 5.0],
];
let csr = CsrMatrix::from_dense(&a);
assert_eq!(csr.nrows, 3);
assert_eq!(csr.ncols, 3);
assert_eq!(csr.nnz(), 5);
let dense = csr.to_dense();
for i in 0..3 {
for j in 0..3 {
assert!(approx_eq(dense[i][j], a[i][j]));
}
}
}
#[test]
fn csr_spmv() {
let a = vec![vec![1.0, 0.0, 2.0], vec![0.0, 3.0, 0.0]];
let csr = CsrMatrix::from_dense(&a);
let x = [1.0, 2.0, 3.0];
let y = csr.spmv(&x).unwrap();
assert!(approx_eq(y[0], 7.0)); assert!(approx_eq(y[1], 6.0)); }
#[test]
fn csr_spmv_wrong_length() {
let csr = CsrMatrix::from_dense(&[vec![1.0, 2.0]]);
assert!(csr.spmv(&[1.0]).is_err());
}
#[test]
fn csr_add() {
let a = CsrMatrix::from_dense(&[vec![1.0, 0.0], vec![0.0, 2.0]]);
let b = CsrMatrix::from_dense(&[vec![0.0, 3.0], vec![4.0, 0.0]]);
let c = a.add(&b).unwrap();
let dense = c.to_dense();
assert!(approx_eq(dense[0][0], 1.0));
assert!(approx_eq(dense[0][1], 3.0));
assert!(approx_eq(dense[1][0], 4.0));
assert!(approx_eq(dense[1][1], 2.0));
}
#[test]
fn csr_add_cancellation() {
let a = CsrMatrix::from_dense(&[vec![1.0, 2.0]]);
let b = CsrMatrix::from_dense(&[vec![-1.0, -2.0]]);
let c = a.add(&b).unwrap();
assert_eq!(c.nnz(), 0);
}
#[test]
fn csr_add_dimension_mismatch() {
let a = CsrMatrix::from_dense(&[vec![1.0, 2.0]]);
let b = CsrMatrix::from_dense(&[vec![1.0], vec![2.0]]);
assert!(a.add(&b).is_err());
}
#[test]
fn csr_transpose() {
let a = vec![vec![1.0, 0.0, 2.0], vec![0.0, 3.0, 0.0]];
let csr = CsrMatrix::from_dense(&a);
let ct = csr.transpose();
assert_eq!(ct.nrows, 3);
assert_eq!(ct.ncols, 2);
let dense = ct.to_dense();
assert!(approx_eq(dense[0][0], 1.0));
assert!(approx_eq(dense[1][1], 3.0));
assert!(approx_eq(dense[2][0], 2.0));
assert!(approx_eq(dense[0][1], 0.0));
}
#[test]
fn csr_empty() {
let a: Vec<Vec<f64>> = vec![vec![0.0; 3]; 2];
let csr = CsrMatrix::from_dense(&a);
assert_eq!(csr.nnz(), 0);
assert_eq!(csr.nrows, 2);
assert_eq!(csr.ncols, 3);
}
#[test]
fn csr_new_validation() {
assert!(CsrMatrix::new(2, 2, vec![1.0], vec![0], vec![0, 1]).is_err());
assert!(CsrMatrix::new(1, 2, vec![1.0], vec![5], vec![0, 1]).is_err());
assert!(CsrMatrix::new(2, 2, vec![1.0, 2.0], vec![0, 1], vec![0, 2, 1]).is_err());
assert!(CsrMatrix::new(1, 3, vec![1.0, 2.0], vec![2, 0], vec![0, 2]).is_err());
assert!(CsrMatrix::new(1, 3, vec![1.0, 2.0], vec![0, 2], vec![0, 2]).is_ok());
}
#[test]
fn matrix_rank_full() {
let a = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 2.0, 0.0],
vec![0.0, 0.0, 3.0],
];
assert_eq!(matrix_rank(&a, None).unwrap(), 3);
}
#[test]
fn matrix_rank_deficient() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
assert_eq!(matrix_rank(&a, None).unwrap(), 1);
}
#[test]
fn matrix_rank_zero() {
let a = vec![vec![0.0, 0.0], vec![0.0, 0.0]];
assert_eq!(matrix_rank(&a, None).unwrap(), 0);
}
#[test]
fn condition_number_identity() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let cond = condition_number(&a).unwrap();
assert!(approx_eq(cond, 1.0));
}
#[test]
fn condition_number_singular() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
let cond = condition_number(&a).unwrap();
assert!(cond.is_infinite());
}
#[test]
fn condition_number_well_conditioned() {
let a = vec![
vec![2.0, 0.0, 0.0],
vec![0.0, 3.0, 0.0],
vec![0.0, 0.0, 4.0],
];
let cond = condition_number(&a).unwrap();
assert!(approx_eq(cond, 2.0)); }
#[test]
fn matrix_inverse_identity() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let inv = matrix_inverse(&a).unwrap();
assert!(approx_eq(inv[0][0], 1.0));
assert!(approx_eq(inv[0][1], 0.0));
assert!(approx_eq(inv[1][0], 0.0));
assert!(approx_eq(inv[1][1], 1.0));
}
#[test]
#[allow(clippy::needless_range_loop)]
fn matrix_inverse_2x2() {
let a = vec![vec![4.0, 7.0], vec![2.0, 6.0]];
let inv = matrix_inverse(&a).unwrap();
let prod = matrix_multiply(&a, &inv).unwrap();
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((prod[i][j] - expected).abs() < 1e-10);
}
}
}
#[test]
fn matrix_inverse_singular_errors() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
assert!(matrix_inverse(&a).is_err());
}
#[test]
#[allow(clippy::needless_range_loop)]
fn pseudo_inverse_square() {
let a = vec![vec![1.0, 0.0], vec![0.0, 2.0]];
let pinv = pseudo_inverse(&a, None).unwrap();
assert!(approx_eq(pinv[0][0], 1.0));
assert!(approx_eq(pinv[1][1], 0.5));
}
#[test]
#[allow(clippy::needless_range_loop)]
fn pseudo_inverse_tall() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![0.0, 0.0]];
let pinv = pseudo_inverse(&a, None).unwrap();
assert_eq!(pinv.len(), 2); assert_eq!(pinv[0].len(), 3); let prod = matrix_multiply(&pinv, &a).unwrap();
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((prod[i][j] - expected).abs() < 1e-10);
}
}
}
#[test]
#[allow(clippy::needless_range_loop)]
fn pseudo_inverse_rank_deficient() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
let pinv = pseudo_inverse(&a, None).unwrap();
let aa_pinv = matrix_multiply(&a, &pinv).unwrap();
let result = matrix_multiply(&aa_pinv, &a).unwrap();
for i in 0..2 {
for j in 0..2 {
assert!(
(result[i][j] - a[i][j]).abs() < 1e-8,
"A·A⁺·A ≠ A at [{i}][{j}]"
);
}
}
}
#[test]
fn svd_identity_2x2() {
let a = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
let result = svd(&a).unwrap();
assert!(approx_eq(result.sigma[0], 1.0));
assert!(approx_eq(result.sigma[1], 1.0));
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn svd_diagonal_3x3() {
let a = vec![
vec![3.0, 0.0, 0.0],
vec![0.0, 5.0, 0.0],
vec![0.0, 0.0, 2.0],
];
let result = svd(&a).unwrap();
assert!(approx_eq(result.sigma[0], 5.0));
assert!(approx_eq(result.sigma[1], 3.0));
assert!(approx_eq(result.sigma[2], 2.0));
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn svd_rank_1() {
let a = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
let result = svd(&a).unwrap();
assert!(result.sigma[0] > 1.0);
assert!(result.sigma[1] < 1e-10);
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn svd_tall_matrix() {
let a = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]];
let result = svd(&a).unwrap();
assert_eq!(result.sigma.len(), 2);
assert!(result.sigma[0] > result.sigma[1]);
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn svd_wide_matrix() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let result = svd(&a).unwrap();
assert_eq!(result.sigma.len(), 2);
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn svd_symmetric_positive_definite() {
let a = vec![
vec![4.0, 2.0, 1.0],
vec![2.0, 5.0, 3.0],
vec![1.0, 3.0, 6.0],
];
let result = svd(&a).unwrap();
for &s in &result.sigma {
assert!(s > 0.0);
}
svd_check_reconstruction(&a, 1e-9);
}
#[test]
fn svd_orthogonality_u() {
let a = vec![vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]];
let result = svd(&a).unwrap();
let m = a.len();
let ncols = result.u.len().min(m);
for i in 0..ncols {
for j in 0..ncols {
let dot: f64 = (0..m).map(|k| result.u[i][k] * result.u[j][k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-10,
"U orthogonality failed at ({i},{j}): dot={dot}"
);
}
}
}
#[test]
fn svd_orthogonality_vt() {
let a = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let result = svd(&a).unwrap();
let n = a[0].len();
for i in 0..n {
for j in 0..n {
let dot: f64 = (0..n).map(|k| result.vt[i][k] * result.vt[j][k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-10,
"Vᵀ orthogonality failed at ({i},{j}): dot={dot}"
);
}
}
}
#[test]
fn svd_1x1() {
let a = vec![vec![7.0]];
let result = svd(&a).unwrap();
assert!(approx_eq(result.sigma[0], 7.0));
}
#[test]
fn svd_zero_matrix() {
let a = vec![vec![0.0, 0.0], vec![0.0, 0.0]];
let result = svd(&a).unwrap();
for &s in &result.sigma {
assert!(s < 1e-10);
}
}
#[test]
fn svd_empty_errors() {
let a: Vec<Vec<f64>> = vec![];
assert!(svd(&a).is_err());
}
#[test]
fn svd_singular_values_descending() {
let a = vec![
vec![1.0, 0.0, 0.0],
vec![0.0, 0.5, 0.0],
vec![0.0, 0.0, 10.0],
];
let result = svd(&a).unwrap();
for w in result.sigma.windows(2) {
assert!(w[0] >= w[1]);
}
}
#[test]
fn svd_negative_values() {
let a = vec![vec![-3.0, 1.0], vec![2.0, -4.0]];
let result = svd(&a).unwrap();
for &s in &result.sigma {
assert!(s >= 0.0);
}
svd_check_reconstruction(&a, 1e-10);
}
#[test]
fn gradient_descent_quadratic() {
let f = |x: &[f64]| x[0] * x[0] + x[1] * x[1];
let grad = |x: &[f64]| vec![2.0 * x[0], 2.0 * x[1]];
let result = gradient_descent(f, grad, &[5.0, 3.0], 0.1, 1e-8, 1000).unwrap();
assert!(result.x[0].abs() < 1e-4);
assert!(result.x[1].abs() < 1e-4);
assert!(result.f_val < 1e-8);
}
#[test]
fn gradient_descent_rosenbrock_partial() {
let f = |x: &[f64]| (1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0] * x[0]).powi(2);
let grad = |x: &[f64]| {
vec![
-2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0] * x[0]),
200.0 * (x[1] - x[0] * x[0]),
]
};
let initial_f = f(&[0.0, 0.0]);
let _ = gradient_descent(f, grad, &[0.0, 0.0], 0.001, 1e-6, 100);
assert!(initial_f > 0.5); }
#[test]
fn gradient_descent_empty_errors() {
assert!(gradient_descent(|_| 0.0, |_| vec![], &[], 0.1, 1e-6, 10).is_err());
}
#[test]
fn conjugate_gradient_identity() {
let a_mul = |x: &[f64]| x.to_vec();
let b = [3.0, 7.0];
let x = conjugate_gradient(a_mul, &b, &[0.0, 0.0], 1e-10, 100).unwrap();
assert!(approx_eq(x[0], 3.0));
assert!(approx_eq(x[1], 7.0));
}
#[test]
fn conjugate_gradient_spd() {
let a_mul = |x: &[f64]| vec![4.0 * x[0] + x[1], x[0] + 3.0 * x[1]];
let b = [1.0, 2.0];
let x = conjugate_gradient(a_mul, &b, &[0.0, 0.0], 1e-10, 100).unwrap();
let ax = [4.0 * x[0] + x[1], x[0] + 3.0 * x[1]];
assert!((ax[0] - b[0]).abs() < 1e-8);
assert!((ax[1] - b[1]).abs() < 1e-8);
}
#[test]
fn conjugate_gradient_empty_errors() {
assert!(conjugate_gradient(|_| vec![], &[], &[], 1e-6, 10).is_err());
}
#[test]
fn bfgs_quadratic() {
let f = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.0).powi(2);
let grad = |x: &[f64]| vec![2.0 * (x[0] - 1.0), 2.0 * (x[1] - 2.0)];
let result = bfgs(f, grad, &[0.0, 0.0], 1e-8, 100).unwrap();
assert!((result.x[0] - 1.0).abs() < 1e-6);
assert!((result.x[1] - 2.0).abs() < 1e-6);
}
#[test]
fn bfgs_rosenbrock() {
let f = |x: &[f64]| (1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0] * x[0]).powi(2);
let grad = |x: &[f64]| {
vec![
-2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0] * x[0]),
200.0 * (x[1] - x[0] * x[0]),
]
};
let result = bfgs(f, grad, &[0.0, 0.0], 1e-6, 1000).unwrap();
assert!((result.x[0] - 1.0).abs() < 1e-3);
assert!((result.x[1] - 1.0).abs() < 1e-3);
}
#[test]
fn bfgs_empty_errors() {
assert!(bfgs(|_| 0.0, |_| vec![], &[], 1e-6, 10).is_err());
}
#[test]
fn levenberg_marquardt_linear() {
let xs = [0.0, 1.0, 2.0];
let ys = [1.0, 3.0, 5.0];
let residuals = |p: &[f64]| -> Vec<f64> {
xs.iter()
.zip(ys.iter())
.map(|(&x, &y)| p[0] * x + p[1] - y)
.collect()
};
let jac = |p: &[f64]| -> Vec<Vec<f64>> {
let _ = p;
xs.iter().map(|&x| vec![x, 1.0]).collect()
};
let result = levenberg_marquardt(residuals, jac, &[0.0, 0.0], 1e-10, 100).unwrap();
assert!((result.x[0] - 2.0).abs() < 1e-4);
assert!((result.x[1] - 1.0).abs() < 1e-4);
}
#[test]
fn levenberg_marquardt_empty_errors() {
assert!(levenberg_marquardt(|_| vec![], |_| vec![], &[], 1e-6, 10).is_err());
}
#[test]
fn dopri45_exponential() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| {
dy[0] = y[0];
};
let traj = dopri45(f, 0.0, &[1.0], 1.0, 1e-8, 0.1).unwrap();
let (t_final, y_final) = traj.last().unwrap();
assert!((*t_final - 1.0).abs() < 1e-6);
assert!((y_final[0] - std::f64::consts::E).abs() < 1e-6);
}
#[test]
fn dopri45_harmonic_oscillator() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| {
dy[0] = y[1];
dy[1] = -y[0];
};
let t_end = std::f64::consts::PI;
let traj = dopri45(f, 0.0, &[1.0, 0.0], t_end, 1e-8, 0.1).unwrap();
let (_, y_final) = traj.last().unwrap();
assert!((y_final[0] - (-1.0)).abs() < 1e-5);
assert!(y_final[1].abs() < 1e-5);
}
#[test]
fn dopri45_adapts_step_size() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| {
dy[0] = y[0];
};
let traj = dopri45(f, 0.0, &[1.0], 1.0, 1e-6, 0.5).unwrap();
assert!(traj.len() < 100);
assert!(traj.len() > 2); }
#[test]
fn dopri45_empty_errors() {
assert!(dopri45(|_, _, _| {}, 0.0, &[], 1.0, 1e-6, 0.1).is_err());
}
#[test]
fn dopri45_invalid_h() {
assert!(dopri45(|_, _, _| {}, 0.0, &[1.0], 1.0, 1e-6, 0.0).is_err());
}
#[test]
fn lbfgs_quadratic() {
let f = |x: &[f64]| (x[0] - 1.0).powi(2) + (x[1] - 2.0).powi(2);
let grad = |x: &[f64]| vec![2.0 * (x[0] - 1.0), 2.0 * (x[1] - 2.0)];
let result = lbfgs(f, grad, &[0.0, 0.0], 5, 1e-8, 100).unwrap();
assert!((result.x[0] - 1.0).abs() < 1e-6);
assert!((result.x[1] - 2.0).abs() < 1e-6);
}
#[test]
fn lbfgs_rosenbrock() {
let f = |x: &[f64]| (1.0 - x[0]).powi(2) + 100.0 * (x[1] - x[0] * x[0]).powi(2);
let grad = |x: &[f64]| {
vec![
-2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0] * x[0]),
200.0 * (x[1] - x[0] * x[0]),
]
};
let result = lbfgs(f, grad, &[0.0, 0.0], 10, 1e-6, 2000).unwrap();
assert!((result.x[0] - 1.0).abs() < 1e-3);
assert!((result.x[1] - 1.0).abs() < 1e-3);
}
#[test]
fn lbfgs_empty_errors() {
assert!(lbfgs(|_| 0.0, |_| vec![], &[], 5, 1e-6, 10).is_err());
}
#[test]
fn lbfgs_zero_m_errors() {
assert!(lbfgs(|_| 0.0, |_| vec![0.0], &[0.0], 0, 1e-6, 10).is_err());
}
#[test]
fn symplectic_euler_harmonic() {
let acc = |_t: f64, pos: &[f64], out: &mut [f64]| out[0] = -pos[0];
let (pos, vel) =
symplectic_euler(acc, &[1.0], &[0.0], 0.0, std::f64::consts::TAU, 10000).unwrap();
assert!((pos[0] - 1.0).abs() < 0.05);
let energy = 0.5 * (vel[0] * vel[0] + pos[0] * pos[0]);
assert!((energy - 0.5).abs() < 0.01);
}
#[test]
fn verlet_harmonic_energy_conservation() {
let acc = |_t: f64, pos: &[f64], out: &mut [f64]| out[0] = -pos[0];
let (pos, vel) = verlet(acc, &[1.0], &[0.0], 0.0, 62.83, 100000).unwrap();
let energy = 0.5 * (vel[0] * vel[0] + pos[0] * pos[0]);
assert!(
(energy - 0.5).abs() < 0.01,
"energy drift: {energy} (expected 0.5)"
);
}
#[test]
fn verlet_zero_steps_error() {
let acc = |_: f64, _: &[f64], _: &mut [f64]| {};
assert!(verlet(acc, &[0.0], &[0.0], 0.0, 1.0, 0).is_err());
}
#[test]
fn symplectic_euler_mismatched_lengths() {
let acc = |_: f64, _: &[f64], _: &mut [f64]| {};
assert!(symplectic_euler(acc, &[0.0], &[0.0, 1.0], 0.0, 1.0, 10).is_err());
}
#[test]
fn pcg32_deterministic() {
let mut a = Pcg32::new(42, 1);
let mut b = Pcg32::new(42, 1);
for _ in 0..100 {
assert_eq!(a.next_u32(), b.next_u32());
}
}
#[test]
fn pcg32_different_seeds() {
let mut a = Pcg32::new(1, 1);
let mut b = Pcg32::new(2, 1);
let mut same = true;
for _ in 0..10 {
if a.next_u32() != b.next_u32() {
same = false;
break;
}
}
assert!(!same);
}
#[test]
fn pcg32_f64_range() {
let mut rng = Pcg32::new(123, 456);
for _ in 0..1000 {
let v = rng.next_f64();
assert!((0.0..1.0).contains(&v));
}
}
#[test]
fn pcg32_range() {
let mut rng = Pcg32::new(99, 1);
for _ in 0..100 {
let v = rng.next_f64_range(5.0, 10.0);
assert!((5.0..10.0).contains(&v));
}
}
#[test]
fn fft_2d_roundtrip() {
let n = 4;
let mut data: Vec<Complex> = (0..(n * n)).map(|i| Complex::from_real(i as f64)).collect();
let original: Vec<Complex> = data.clone();
fft_2d(&mut data, n, n).unwrap();
ifft_2d(&mut data, n, n).unwrap();
for i in 0..data.len() {
assert!(
(data[i].re - original[i].re).abs() < 1e-8,
"2D FFT roundtrip failed at {i}"
);
}
}
#[test]
fn fft_2d_wrong_size() {
let mut data = vec![Complex::from_real(0.0); 6]; assert!(fft_2d(&mut data, 4, 4).is_err());
}
#[test]
fn truncated_svd_basic() {
let a = vec![
vec![3.0, 0.0, 0.0],
vec![0.0, 5.0, 0.0],
vec![0.0, 0.0, 2.0],
];
let result = truncated_svd(&a, 2).unwrap();
assert_eq!(result.sigma.len(), 2);
assert!(approx_eq(result.sigma[0], 5.0));
assert!(approx_eq(result.sigma[1], 3.0));
}
#[test]
fn truncated_svd_k_too_large() {
let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
assert!(truncated_svd(&a, 5).is_err());
}
#[test]
fn truncated_svd_k_zero() {
let a = vec![vec![1.0]];
assert!(truncated_svd(&a, 0).is_err());
}
#[test]
fn inertia_sphere_diagonal() {
let i = inertia_sphere(10.0, 2.0);
let expected = 0.4 * 10.0 * 4.0; assert!(approx_eq(i[0][0], expected));
assert!(approx_eq(i[1][1], expected));
assert!(approx_eq(i[2][2], expected));
assert!(approx_eq(i[0][1], 0.0));
}
#[test]
fn inertia_box_asymmetric() {
let i = inertia_box(12.0, 1.0, 2.0, 3.0);
assert!(approx_eq(i[0][0], 52.0));
assert!(approx_eq(i[1][1], 40.0));
}
#[test]
fn inertia_mesh_unit_cube() {
let v = [
[-0.5, -0.5, -0.5],
[0.5, -0.5, -0.5],
[0.5, 0.5, -0.5],
[-0.5, 0.5, -0.5],
[-0.5, -0.5, 0.5],
[0.5, -0.5, 0.5],
[0.5, 0.5, 0.5],
[-0.5, 0.5, 0.5],
];
let tris: Vec<([f64; 3], [f64; 3], [f64; 3])> = vec![
(v[0], v[2], v[1]),
(v[0], v[3], v[2]), (v[4], v[5], v[6]),
(v[4], v[6], v[7]), (v[0], v[1], v[5]),
(v[0], v[5], v[4]), (v[2], v[3], v[7]),
(v[2], v[7], v[6]), (v[0], v[4], v[7]),
(v[0], v[7], v[3]), (v[1], v[2], v[6]),
(v[1], v[6], v[5]), ];
let (vol, _com, _inertia) = inertia_mesh(&tris);
assert!((vol.abs() - 1.0).abs() < 0.01, "unit cube volume: {vol}");
}
#[test]
fn gmres_identity() {
let a_mul = |x: &[f64]| x.to_vec();
let b = [3.0, 7.0];
let x = gmres(a_mul, &b, &[0.0, 0.0], 10, 1e-10, 100).unwrap();
assert!(approx_eq(x[0], 3.0));
assert!(approx_eq(x[1], 7.0));
}
#[test]
fn gmres_non_symmetric() {
let a_mul = |x: &[f64]| vec![2.0 * x[0] + x[1], 3.0 * x[1]];
let b = [5.0, 9.0]; let x = gmres(a_mul, &b, &[0.0, 0.0], 10, 1e-10, 100).unwrap();
assert!((x[0] - 1.0).abs() < 1e-6);
assert!((x[1] - 3.0).abs() < 1e-6);
}
#[test]
fn eigen_symmetric_diagonal() {
let a = vec![
vec![5.0, 0.0, 0.0],
vec![0.0, 3.0, 0.0],
vec![0.0, 0.0, 1.0],
];
let ed = eigen_symmetric(&a, 1e-12, 1000).unwrap();
assert!(approx_eq(ed.eigenvalues_real[0], 5.0));
assert!(approx_eq(ed.eigenvalues_real[1], 3.0));
assert!(approx_eq(ed.eigenvalues_real[2], 1.0));
}
#[test]
fn eigen_symmetric_2x2() {
let a = vec![vec![2.0, 1.0], vec![1.0, 2.0]];
let ed = eigen_symmetric(&a, 1e-12, 1000).unwrap();
assert!((ed.eigenvalues_real[0] - 3.0).abs() < 1e-8);
assert!((ed.eigenvalues_real[1] - 1.0).abs() < 1e-8);
}
#[test]
#[allow(clippy::needless_range_loop)]
fn eigen_symmetric_verify_av_eq_lambda_v() {
let a = vec![
vec![4.0, 1.0, 0.0],
vec![1.0, 3.0, 1.0],
vec![0.0, 1.0, 2.0],
];
let ed = eigen_symmetric(&a, 1e-12, 1000).unwrap();
let vecs = ed.eigenvectors.as_ref().unwrap();
for k in 0..3 {
let lambda = ed.eigenvalues_real[k];
let v = &vecs[k];
for i in 0..3 {
let mut av_i = 0.0;
for j in 0..3 {
av_i += a[i][j] * v[j];
}
assert!(
(av_i - lambda * v[i]).abs() < 1e-6,
"A*v != λ*v at eigenvalue {k}: {av_i} != {} * {}",
lambda,
v[i]
);
}
}
}
#[test]
#[allow(clippy::needless_range_loop)]
fn eigen_symmetric_orthogonal_eigenvectors() {
let a = vec![
vec![4.0, 1.0, 0.0],
vec![1.0, 3.0, 1.0],
vec![0.0, 1.0, 2.0],
];
let ed = eigen_symmetric(&a, 1e-12, 1000).unwrap();
let vecs = ed.eigenvectors.as_ref().unwrap();
for i in 0..3 {
for j in 0..3 {
let dot: f64 = (0..3).map(|k| vecs[i][k] * vecs[j][k]).sum();
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(dot - expected).abs() < 1e-6,
"eigenvectors not orthonormal: dot({i},{j}) = {dot}"
);
}
}
}
#[test]
fn eigen_symmetric_1x1() {
let a = vec![vec![7.0]];
let ed = eigen_symmetric(&a, 1e-12, 100).unwrap();
assert!(approx_eq(ed.eigenvalues_real[0], 7.0));
}
#[test]
fn eigen_symmetric_negative_eigenvalues() {
let a = vec![vec![-2.0, 0.0], vec![0.0, -5.0]];
let ed = eigen_symmetric(&a, 1e-12, 100).unwrap();
assert!((ed.eigenvalues_real[0] - (-5.0)).abs() < 1e-8);
assert!((ed.eigenvalues_real[1] - (-2.0)).abs() < 1e-8);
}
#[test]
fn eigen_symmetric_empty_errors() {
let a: Vec<Vec<f64>> = vec![];
assert!(eigen_symmetric(&a, 1e-12, 100).is_err());
}
#[test]
fn eigen_symmetric_matches_power_iteration() {
let a = vec![
vec![4.0, 1.0, 0.0],
vec![1.0, 3.0, 1.0],
vec![0.0, 1.0, 2.0],
];
let ed = eigen_symmetric(&a, 1e-12, 1000).unwrap();
let (power_eig, _) = eigenvalue_power(&a, 1e-12, 1000).unwrap();
assert!(
(ed.eigenvalues_real[0] - power_eig).abs() < 1e-4,
"full={} vs power={}",
ed.eigenvalues_real[0],
power_eig
);
}
#[test]
fn backward_euler_stiff_decay() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -1000.0 * y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = -1000.0;
let y = backward_euler(f, jac, 0.0, &[1.0], 1.0, 100, 1e-10, 20).unwrap();
assert!(y[0].abs() < 0.1, "backward Euler unstable: {}", y[0]);
}
#[test]
fn backward_euler_linear() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = -1.0;
let y = backward_euler(f, jac, 0.0, &[1.0], 1.0, 1000, 1e-10, 20).unwrap();
let expected = (-1.0_f64).exp();
assert!(
(y[0] - expected).abs() < 0.01,
"backward Euler: {} vs {}",
y[0],
expected
);
}
#[test]
fn bdf2_stiff_decay() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -1000.0 * y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = -1000.0;
let y = bdf2(f, jac, 0.0, &[1.0], 1.0, 100, 1e-10, 20).unwrap();
assert!(y[0].abs() < 0.1, "BDF-2 unstable: {}", y[0]);
}
#[test]
fn bdf2_linear_more_accurate_than_backward_euler() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = -1.0;
let expected = (-1.0_f64).exp();
let be = backward_euler(f, jac, 0.0, &[1.0], 1.0, 100, 1e-10, 20).unwrap();
let b2 = bdf2(f, jac, 0.0, &[1.0], 1.0, 100, 1e-10, 20).unwrap();
let be_err = (be[0] - expected).abs();
let b2_err = (b2[0] - expected).abs();
assert!(
b2_err < be_err,
"BDF-2 should be more accurate: bdf2_err={b2_err} vs be_err={be_err}"
);
}
#[test]
fn pcg32_normal_distribution() {
let mut rng = Pcg32::new(42, 1);
let mut sum = 0.0;
let mut sum_sq = 0.0;
let n = 10000;
for _ in 0..n {
let x = rng.next_normal();
sum += x;
sum_sq += x * x;
}
let mean = sum / n as f64;
let variance = sum_sq / n as f64 - mean * mean;
assert!(mean.abs() < 0.05, "normal mean: {mean}");
assert!((variance - 1.0).abs() < 0.1, "normal variance: {variance}");
}
#[test]
fn euler_maruyama_zero_noise_recovers_ode() {
let drift = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let diffusion = |_t: f64, _y: &[f64], b: &mut [f64]| b[0] = 0.0;
let mut rng = Pcg32::new(1, 1);
let traj = euler_maruyama(drift, diffusion, 0.0, &[1.0], 1.0, 1000, &mut rng).unwrap();
let y_final = traj.last().unwrap().1[0];
let expected = (-1.0_f64).exp();
assert!(
(y_final - expected).abs() < 0.01,
"EM zero noise: {y_final} vs {expected}"
);
}
#[test]
fn euler_maruyama_deterministic_replay() {
let drift = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let diffusion = |_t: f64, y: &[f64], b: &mut [f64]| b[0] = 0.1 * y[0];
let mut rng1 = Pcg32::new(42, 1);
let mut rng2 = Pcg32::new(42, 1);
let t1 = euler_maruyama(drift, diffusion, 0.0, &[1.0], 1.0, 100, &mut rng1).unwrap();
let t2 = euler_maruyama(drift, diffusion, 0.0, &[1.0], 1.0, 100, &mut rng2).unwrap();
for i in 0..t1.len() {
assert_eq!(t1[i].1[0], t2[i].1[0]);
}
}
#[test]
fn milstein_zero_noise_recovers_ode() {
let drift = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let diffusion = |_t: f64, _y: &[f64], b: &mut [f64]| b[0] = 0.0;
let diff_deriv = |_t: f64, _y: &[f64], db: &mut [f64]| db[0] = 0.0;
let mut rng = Pcg32::new(1, 1);
let traj = milstein(
drift,
diffusion,
diff_deriv,
0.0,
&[1.0],
1.0,
1000,
&mut rng,
)
.unwrap();
let y_final = traj.last().unwrap().1[0];
let expected = (-1.0_f64).exp();
assert!(
(y_final - expected).abs() < 0.01,
"Milstein zero noise: {y_final} vs {expected}"
);
}
#[test]
fn lyapunov_stable_system() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = -y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = -1.0;
let mle = lyapunov_max(f, jac, &[1.0], 10.0, 0.01, 10).unwrap();
assert!(mle < 0.0, "stable system MLE should be negative: {mle}");
}
#[test]
fn lyapunov_unstable_system() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| dy[0] = y[0];
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| j[0][0] = 1.0;
let mle = lyapunov_max(f, jac, &[1.0], 5.0, 0.01, 10).unwrap();
assert!(mle > 0.0, "unstable system MLE should be positive: {mle}");
}
#[test]
fn pgs_unconstrained() {
let a = vec![vec![4.0, 1.0], vec![1.0, 3.0]];
let b = [1.0, 2.0];
let lo = [f64::NEG_INFINITY, f64::NEG_INFINITY];
let hi = [f64::INFINITY, f64::INFINITY];
let x = projected_gauss_seidel(&a, &b, &lo, &hi, &[0.0, 0.0], 100, 1e-10).unwrap();
let r0 = 4.0 * x[0] + x[1];
let r1 = x[0] + 3.0 * x[1];
assert!((r0 - 1.0).abs() < 1e-6);
assert!((r1 - 2.0).abs() < 1e-6);
}
#[test]
fn pgs_box_constrained() {
let a = vec![vec![4.0, 1.0], vec![1.0, 3.0]];
let b = [-1.0, -1.0]; let lo = [0.0, 0.0];
let hi = [f64::INFINITY, f64::INFINITY];
let x = projected_gauss_seidel(&a, &b, &lo, &hi, &[0.0, 0.0], 100, 1e-10).unwrap();
assert!(x[0] >= -1e-10 && x[1] >= -1e-10, "PGS violated bounds");
}
#[test]
fn pgs_sor_matches_standard() {
let a = vec![vec![4.0, 1.0], vec![1.0, 3.0]];
let b = [1.0, 2.0];
let lo = [f64::NEG_INFINITY; 2];
let hi = [f64::INFINITY; 2];
let x_std = projected_gauss_seidel(&a, &b, &lo, &hi, &[0.0, 0.0], 100, 1e-10).unwrap();
let x_sor =
projected_gauss_seidel_sor(&a, &b, &lo, &hi, &[0.0, 0.0], 100, 1e-10, 1.0).unwrap();
assert!((x_std[0] - x_sor[0]).abs() < 1e-10);
assert!((x_std[1] - x_sor[1]).abs() < 1e-10);
}
#[test]
fn pgs_sor_overrelaxation_converges() {
let a = vec![vec![4.0, 1.0], vec![1.0, 3.0]];
let b = [1.0, 2.0];
let lo = [f64::NEG_INFINITY; 2];
let hi = [f64::INFINITY; 2];
let x = projected_gauss_seidel_sor(&a, &b, &lo, &hi, &[0.0, 0.0], 100, 1e-10, 1.5).unwrap();
let r0 = 4.0 * x[0] + x[1];
let r1 = x[0] + 3.0 * x[1];
assert!((r0 - 1.0).abs() < 1e-6);
assert!((r1 - 2.0).abs() < 1e-6);
}
#[test]
fn kahan_sum_basic() {
let vals: Vec<f64> = (0..1000).map(|_| 0.1).collect();
let result = kahan_sum(&vals);
assert!((result - 100.0).abs() < 1e-10);
}
#[test]
fn kahan_sum_empty() {
assert!((kahan_sum(&[]) - 0.0).abs() < 1e-15);
}
#[test]
fn neumaier_sum_basic() {
let vals: Vec<f64> = (0..1000).map(|_| 0.1).collect();
let result = neumaier_sum(&vals);
assert!((result - 100.0).abs() < 1e-10);
}
#[test]
fn neumaier_sum_large_small() {
let vals = vec![1e16, 1.0, -1e16, 1.0];
let result = neumaier_sum(&vals);
assert!((result - 2.0).abs() < 1e-5);
}
#[test]
fn neumaier_sum_empty() {
assert!((neumaier_sum(&[]) - 0.0).abs() < 1e-15);
}
#[test]
fn yoshida4_harmonic_oscillator() {
let acc = |_t: f64, pos: &[f64], out: &mut [f64]| {
out[0] = -pos[0];
};
let (pos, vel) = yoshida4(acc, &[1.0], &[0.0], 0.0, std::f64::consts::TAU, 200).unwrap();
assert!((pos[0] - 1.0).abs() < 1e-6);
assert!(vel[0].abs() < 1e-5);
}
#[test]
fn yoshida4_more_accurate_than_verlet() {
let acc = |_t: f64, pos: &[f64], out: &mut [f64]| {
out[0] = -pos[0];
};
let n = 100;
let t_end = std::f64::consts::TAU;
let (y_pos, _) = yoshida4(acc, &[1.0], &[0.0], 0.0, t_end, n).unwrap();
let (v_pos, _) = verlet(acc, &[1.0], &[0.0], 0.0, t_end, n).unwrap();
let y_err = (y_pos[0] - 1.0).abs();
let v_err = (v_pos[0] - 1.0).abs();
assert!(
y_err < v_err,
"Yoshida error {y_err} should be < Verlet error {v_err}"
);
}
#[test]
fn bicgstab_spd_system() {
let a_mul = |x: &[f64]| vec![4.0 * x[0] + x[1], x[0] + 3.0 * x[1]];
let b = [1.0, 2.0];
let x = bicgstab(a_mul, &b, &[0.0, 0.0], 1e-10, 100).unwrap();
let r0 = 4.0 * x[0] + x[1];
let r1 = x[0] + 3.0 * x[1];
assert!((r0 - 1.0).abs() < 1e-6);
assert!((r1 - 2.0).abs() < 1e-6);
}
#[test]
fn bicgstab_nonsymmetric() {
let a_mul = |x: &[f64]| vec![3.0 * x[0] + x[1], 2.0 * x[1]];
let b = [5.0, 4.0];
let x = bicgstab(a_mul, &b, &[0.0, 0.0], 1e-10, 100).unwrap();
assert!((x[0] - 1.0).abs() < 1e-6);
assert!((x[1] - 2.0).abs() < 1e-6);
}
#[test]
fn halton_base2() {
assert!((halton(1, 2) - 0.5).abs() < 1e-10);
assert!((halton(2, 2) - 0.25).abs() < 1e-10);
assert!((halton(3, 2) - 0.75).abs() < 1e-10);
}
#[test]
fn halton_2d_in_unit_square() {
for i in 1..100 {
let (x, y) = halton_2d(i);
assert!((0.0..1.0).contains(&x));
assert!((0.0..1.0).contains(&y));
}
}
#[test]
fn sobol_in_unit_interval() {
for i in 0..100 {
let v = sobol(i);
assert!((0.0..1.0).contains(&v), "sobol({i}) = {v}");
}
}
#[test]
fn csr_spmvt_basic() {
let a = CsrMatrix::from_dense(&[vec![1.0, 2.0], vec![3.0, 4.0], vec![5.0, 6.0]]);
let y = a.spmvt(&[1.0, 1.0, 1.0]).unwrap();
assert!((y[0] - 9.0).abs() < 1e-10);
assert!((y[1] - 12.0).abs() < 1e-10);
}
#[test]
fn csr_spmvt_matches_transpose_spmv() {
let a = CsrMatrix::from_dense(&[vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]);
let x = [1.0, 2.0];
let y_via_spmvt = a.spmvt(&x).unwrap();
let at = a.transpose();
let y_via_transpose = at.spmv(&x).unwrap();
for (a, b) in y_via_spmvt.iter().zip(y_via_transpose.iter()) {
assert!((a - b).abs() < 1e-10);
}
}
#[test]
fn bdf3_stiff_system() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| {
dy[0] = -50.0 * y[0];
};
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| {
j[0][0] = -50.0;
};
let y = bdf(f, jac, 0.0, &[1.0], 1.0, 500, 1e-10, 20, 3).unwrap();
assert!(y[0].abs() < 1e-10);
}
#[test]
fn bdf5_stiff_system() {
let f = |_t: f64, y: &[f64], dy: &mut [f64]| {
dy[0] = -50.0 * y[0];
};
let jac = |_t: f64, _y: &[f64], j: &mut Vec<Vec<f64>>| {
j[0][0] = -50.0;
};
let y = bdf(f, jac, 0.0, &[1.0], 1.0, 500, 1e-10, 20, 5).unwrap();
assert!(y[0].abs() < 1e-10);
}
#[test]
fn sparse_cholesky_solve_basic() {
let a = CsrMatrix::from_dense(&[vec![4.0, 2.0], vec![2.0, 3.0]]);
let x = sparse_cholesky_solve(&a, &[1.0, 2.0]).unwrap();
let r0 = 4.0 * x[0] + 2.0 * x[1];
let r1 = 2.0 * x[0] + 3.0 * x[1];
assert!((r0 - 1.0).abs() < 1e-8);
assert!((r1 - 2.0).abs() < 1e-8);
}
#[test]
fn sparse_cholesky_identity() {
let a = CsrMatrix::from_dense(&[vec![1.0, 0.0], vec![0.0, 1.0]]);
let x = sparse_cholesky_solve(&a, &[3.0, 7.0]).unwrap();
assert!((x[0] - 3.0).abs() < 1e-10);
assert!((x[1] - 7.0).abs() < 1e-10);
}
#[test]
fn sparse_lu_solve_basic() {
let a = CsrMatrix::from_dense(&[vec![3.0, 1.0], vec![0.0, 2.0]]);
let x = sparse_lu_solve(&a, &[5.0, 4.0]).unwrap();
assert!((x[0] - 1.0).abs() < 1e-8);
assert!((x[1] - 2.0).abs() < 1e-8);
}
}