pub mod advanced;
use crate::error::SpecialResult;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::f64::consts::PI;
use std::fmt::Debug;
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).expect("Failed to convert constant to target float type - this indicates an incompatible numeric type")
}
const HILL_SIZE: usize = 60;
fn hill_even_a_matrix(q: f64, size: usize) -> (Vec<f64>, Vec<f64>) {
let mut diag = Vec::with_capacity(size);
let mut off = Vec::with_capacity(size - 1);
for k in 0..size {
diag.push((2 * k) as f64 * (2 * k) as f64);
}
if size > 1 {
off.push(q * 2.0_f64.sqrt());
for _ in 1..size - 1 {
off.push(q);
}
}
(diag, off)
}
fn hill_odd_a_matrix(q: f64, size: usize) -> (Vec<f64>, Vec<f64>) {
let mut diag = Vec::with_capacity(size);
let mut off = Vec::with_capacity(size - 1);
for k in 0..size {
let d = (2 * k + 1) as f64 * (2 * k + 1) as f64;
diag.push(d);
}
if !diag.is_empty() {
diag[0] += q;
}
for _ in 0..size.saturating_sub(1) {
off.push(q);
}
(diag, off)
}
fn hill_even_b_matrix(q: f64, size: usize) -> (Vec<f64>, Vec<f64>) {
let mut diag = Vec::with_capacity(size);
let mut off = Vec::with_capacity(size - 1);
for k in 1..=size {
diag.push((2 * k) as f64 * (2 * k) as f64);
}
for _ in 0..size.saturating_sub(1) {
off.push(q);
}
(diag, off)
}
fn hill_odd_b_matrix(q: f64, size: usize) -> (Vec<f64>, Vec<f64>) {
let mut diag = Vec::with_capacity(size);
let mut off = Vec::with_capacity(size - 1);
for k in 0..size {
let d = (2 * k + 1) as f64 * (2 * k + 1) as f64;
diag.push(d);
}
if !diag.is_empty() {
diag[0] -= q;
}
for _ in 0..size.saturating_sub(1) {
off.push(q);
}
(diag, off)
}
fn sturm_count(diag: &[f64], off_diag: &[f64], lambda: f64) -> usize {
let n = diag.len();
if n == 0 {
return 0;
}
let mut count = 0usize;
let mut d_prev = diag[0] - lambda;
if d_prev < 0.0 {
count += 1;
}
for i in 1..n {
let e = if i <= off_diag.len() {
off_diag[i - 1]
} else {
0.0
};
let d_curr = if d_prev.abs() > 1e-200 {
(diag[i] - lambda) - e * e / d_prev
} else if d_prev >= 0.0 {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
if d_curr < 0.0 {
count += 1;
}
d_prev = d_curr;
}
count
}
fn sturm_bisect_kth(
diag: &[f64],
off_diag: &[f64],
k: usize,
global_lo: f64,
global_hi: f64,
) -> f64 {
let mut lo = global_lo;
let mut hi = global_hi;
for _ in 0..60 {
if sturm_count(diag, off_diag, lo) <= k {
break;
}
let width = (hi - lo).abs().max(1.0);
lo -= width;
}
for _ in 0..60 {
if sturm_count(diag, off_diag, hi) > k {
break;
}
let width = (hi - lo).abs().max(1.0);
hi += width;
}
for _ in 0..100 {
let mid = (lo + hi) / 2.0;
if sturm_count(diag, off_diag, mid) <= k {
lo = mid;
} else {
hi = mid;
}
if hi - lo < 1e-13 * (1.0 + lo.abs() + hi.abs()) {
break;
}
}
(lo + hi) / 2.0
}
fn find_eigenvalue_near(diag: &[f64], off_diag: &[f64], target: f64) -> f64 {
let n = diag.len();
if n == 0 {
return f64::NAN;
}
if n == 1 {
return diag[0];
}
let (lo_bound, hi_bound) = gershgorin_bounds(diag, off_diag);
let spread = (hi_bound - lo_bound).abs();
let lo_global = lo_bound - spread - 2.0;
let hi_global = hi_bound + spread + 2.0;
let k_above = sturm_count(diag, off_diag, target);
let candidate_above = if k_above < n {
sturm_bisect_kth(diag, off_diag, k_above, lo_global, hi_global)
} else {
f64::INFINITY
};
let candidate_below = if k_above > 0 {
sturm_bisect_kth(diag, off_diag, k_above - 1, lo_global, hi_global)
} else {
f64::NEG_INFINITY
};
let dist_above = (candidate_above - target).abs();
let dist_below = (candidate_below - target).abs();
if dist_below <= dist_above {
candidate_below
} else {
candidate_above
}
}
fn gershgorin_bounds(diag: &[f64], off_diag: &[f64]) -> (f64, f64) {
let n = diag.len();
let mut lo = f64::INFINITY;
let mut hi = f64::NEG_INFINITY;
for i in 0..n {
let radius = if i == 0 {
if off_diag.is_empty() {
0.0
} else {
off_diag[0].abs()
}
} else if i == n - 1 {
if off_diag.len() >= i {
off_diag[i - 1].abs()
} else {
0.0
}
} else {
let l = if i < off_diag.len() {
off_diag[i - 1].abs()
} else {
0.0
};
let r = if i < off_diag.len() {
off_diag[i].abs()
} else {
0.0
};
l + r
};
if diag[i] - radius < lo {
lo = diag[i] - radius;
}
if diag[i] + radius > hi {
hi = diag[i] + radius;
}
}
(lo, hi)
}
fn tridiag_solve(diag: &[f64], off_diag: &[f64], shift: f64, rhs: &[f64]) -> Option<Vec<f64>> {
let n = diag.len();
let mut a: Vec<f64> = diag.iter().map(|&d| d - shift).collect();
let b: Vec<f64> = {
let mut bv = off_diag.to_vec();
bv.resize(n.saturating_sub(1), 0.0);
bv
};
let mut d = rhs.to_vec();
for i in 1..n {
if a[i - 1].abs() < 1e-30 {
return None;
}
let m = b[i - 1] / a[i - 1];
a[i] -= m * b[i - 1];
d[i] -= m * d[i - 1];
}
if a[n - 1].abs() < 1e-30 {
return None;
}
let mut x = vec![0.0_f64; n];
x[n - 1] = d[n - 1] / a[n - 1];
for i in (0..n - 1).rev() {
x[i] = (d[i] - b[i] * x[i + 1]) / a[i];
}
Some(x)
}
fn normalize_vec(v: &mut [f64]) {
let norm: f64 = v.iter().map(|&x| x * x).sum::<f64>().sqrt();
if norm > 1e-15 {
for vi in v.iter_mut() {
*vi /= norm;
}
}
}
fn hill_eigenvector(
diag: &[f64],
off_diag: &[f64],
eigenvalue: f64,
sign_index: usize,
) -> Vec<f64> {
let n = diag.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![1.0];
}
let shift = eigenvalue - 1e-8;
let mut v = vec![0.0_f64; n];
let idx = sign_index.min(n - 1);
v[idx] = 1.0;
normalize_vec(&mut v);
for _ in 0..30 {
let v_old = v.clone();
match tridiag_solve(diag, off_diag, shift, &v_old) {
Some(mut w) => {
normalize_vec(&mut w);
v = w;
}
None => {
for (i, vi) in v.iter_mut().enumerate() {
*vi += 1e-10 * (i as f64 + 1.0).sin();
}
normalize_vec(&mut v);
}
}
}
normalize_vec(&mut v);
v
}
fn mathieu_a_f64(m: usize, q: f64) -> f64 {
if q == 0.0 {
return (m * m) as f64;
}
if m.is_multiple_of(2) {
let (diag, off) = hill_even_a_matrix(q, HILL_SIZE);
let target = (m * m) as f64;
find_eigenvalue_near(&diag, &off, target)
} else {
let (diag, off) = hill_odd_a_matrix(q, HILL_SIZE);
let target = (m * m) as f64;
find_eigenvalue_near(&diag, &off, target)
}
}
fn mathieu_b_f64(m: usize, q: f64) -> f64 {
if m == 0 {
return f64::INFINITY;
}
if q == 0.0 {
return (m * m) as f64;
}
if m.is_multiple_of(2) {
let (diag, off) = hill_even_b_matrix(q, HILL_SIZE);
let target = (m * m) as f64;
find_eigenvalue_near(&diag, &off, target)
} else {
let (diag, off) = hill_odd_b_matrix(q, HILL_SIZE);
let target = (m * m) as f64;
find_eigenvalue_near(&diag, &off, target)
}
}
#[allow(dead_code)]
pub fn mathieu_a<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let q_f64 = q.to_f64().ok_or_else(|| {
crate::error::SpecialError::ComputationError("Failed to convert q to f64".to_string())
})?;
let result = mathieu_a_f64(m, q_f64);
Ok(const_f64::<F>(result))
}
#[allow(dead_code)]
pub fn mathieu_b<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
if m == 0 {
return Ok(F::infinity());
}
let q_f64 = q.to_f64().ok_or_else(|| {
crate::error::SpecialError::ComputationError("Failed to convert q to f64".to_string())
})?;
let result = mathieu_b_f64(m, q_f64);
Ok(const_f64::<F>(result))
}
#[allow(dead_code)]
pub fn mathieu_even_coef<F>(m: usize, q: F) -> SpecialResult<Vec<F>>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
let q_f64 = q.to_f64().ok_or_else(|| {
crate::error::SpecialError::ComputationError("Failed to convert q to f64".to_string())
})?;
let coeffs_f64 = compute_even_coefficients_f64(m, q_f64);
Ok(coeffs_f64.iter().map(|&c| const_f64::<F>(c)).collect())
}
#[allow(dead_code)]
pub fn mathieu_odd_coef<F>(m: usize, q: F) -> SpecialResult<Vec<F>>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
if m == 0 {
return Ok(Vec::new());
}
let q_f64 = q.to_f64().ok_or_else(|| {
crate::error::SpecialError::ComputationError("Failed to convert q to f64".to_string())
})?;
let coeffs_f64 = compute_odd_coefficients_f64(m, q_f64);
Ok(coeffs_f64.iter().map(|&c| const_f64::<F>(c)).collect())
}
#[allow(dead_code)]
pub fn mathieu_cem<F>(m: usize, q: F, x: F) -> SpecialResult<(F, F)>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
let coeffs = mathieu_even_coef(m, q)?;
evaluate_even_mathieu(m, x, &coeffs)
}
#[allow(dead_code)]
pub fn mathieu_sem<F>(m: usize, q: F, x: F) -> SpecialResult<(F, F)>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
if m == 0 {
return Ok((F::zero(), F::zero()));
}
let coeffs = mathieu_odd_coef(m, q)?;
evaluate_odd_mathieu(m, x, &coeffs)
}
fn compute_even_coefficients_f64(m: usize, q: f64) -> Vec<f64> {
let num_coeffs = 30;
if q == 0.0 {
let mut coeffs = vec![0.0_f64; num_coeffs];
let idx = if m.is_multiple_of(2) {
m / 2
} else {
(m - 1) / 2
};
if idx < num_coeffs {
coeffs[idx] = 1.0;
}
return coeffs;
}
let eigenvalue = mathieu_a_f64(m, q);
let (diag, off) = if m.is_multiple_of(2) {
hill_even_a_matrix(q, HILL_SIZE)
} else {
hill_odd_a_matrix(q, HILL_SIZE)
};
let sign_index = if m.is_multiple_of(2) {
m / 2
} else {
(m - 1) / 2
};
let mut v = hill_eigenvector(&diag, &off, eigenvalue, sign_index);
let idx = sign_index.min(v.len().saturating_sub(1));
if idx < v.len() && v[idx] < 0.0 {
for vi in v.iter_mut() {
*vi = -*vi;
}
}
v.truncate(num_coeffs);
while v.len() < num_coeffs {
v.push(0.0);
}
v
}
fn compute_odd_coefficients_f64(m: usize, q: f64) -> Vec<f64> {
if m == 0 {
return vec![];
}
let num_coeffs = 30;
if q == 0.0 {
let mut coeffs = vec![0.0_f64; num_coeffs];
let idx = if m % 2 == 1 { (m - 1) / 2 } else { (m - 2) / 2 };
if idx < num_coeffs {
coeffs[idx] = 1.0;
}
return coeffs;
}
let eigenvalue = mathieu_b_f64(m, q);
let (diag, off) = if m.is_multiple_of(2) {
hill_even_b_matrix(q, HILL_SIZE)
} else {
hill_odd_b_matrix(q, HILL_SIZE)
};
let sign_index = if m.is_multiple_of(2) {
m / 2 - 1
} else {
(m - 1) / 2
};
let mut v = hill_eigenvector(&diag, &off, eigenvalue, sign_index);
let idx = sign_index.min(v.len().saturating_sub(1));
if idx < v.len() && v[idx] < 0.0 {
for vi in v.iter_mut() {
*vi = -*vi;
}
}
v.truncate(num_coeffs);
while v.len() < num_coeffs {
v.push(0.0);
}
v
}
fn evaluate_even_mathieu<F>(m: usize, x: F, coeffs: &[F]) -> SpecialResult<(F, F)>
where
F: Float + FromPrimitive + Debug,
{
let mut result = F::zero();
let mut derivative = F::zero();
if m.is_multiple_of(2) {
for (k, &coef) in coeffs.iter().enumerate() {
let freq = const_f64::<F>((2 * k) as f64);
let arg = freq * x;
result = result + coef * arg.cos();
derivative = derivative - coef * freq * arg.sin();
}
} else {
for (k, &coef) in coeffs.iter().enumerate() {
let freq = const_f64::<F>((2 * k + 1) as f64);
let arg = freq * x;
result = result + coef * arg.cos();
derivative = derivative - coef * freq * arg.sin();
}
}
Ok((result, derivative))
}
fn evaluate_odd_mathieu<F>(m: usize, x: F, coeffs: &[F]) -> SpecialResult<(F, F)>
where
F: Float + FromPrimitive + Debug,
{
if m == 0 || coeffs.is_empty() {
return Ok((F::zero(), F::zero()));
}
let mut result = F::zero();
let mut derivative = F::zero();
if m % 2 == 1 {
for (k, &coef) in coeffs.iter().enumerate() {
let freq = const_f64::<F>((2 * k + 1) as f64);
let arg = freq * x;
result = result + coef * arg.sin();
derivative = derivative + coef * freq * arg.cos();
}
} else {
for (k, &coef) in coeffs.iter().enumerate() {
let freq = const_f64::<F>((2 * k + 2) as f64);
let arg = freq * x;
result = result + coef * arg.sin();
derivative = derivative + coef * freq * arg.cos();
}
}
Ok((result, derivative))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::f64::consts::PI;
const A0_Q1: f64 = -0.4551386041;
const A1_Q1: f64 = 1.8591080725;
const A2_Q1: f64 = 4.3713009827;
const A3_Q1: f64 = 9.0783688472;
const B1_Q1: f64 = -0.1102488170;
const B2_Q1: f64 = 3.9170247730;
const B3_Q1: f64 = 9.0477392598;
const A0_Q01: f64 = -0.0049945438;
const A1_Q01: f64 = 1.0987343130;
const A2_Q01: f64 = 4.0041611598;
const B1_Q01: f64 = 0.8987655570;
const B2_Q01: f64 = 3.9991667028;
#[test]
fn test_mathieu_a_special_cases() {
assert_relative_eq!(mathieu_a::<f64>(0, 0.0).unwrap(), 0.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a::<f64>(1, 0.0).unwrap(), 1.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a::<f64>(2, 0.0).unwrap(), 4.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a::<f64>(3, 0.0).unwrap(), 9.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a::<f64>(4, 0.0).unwrap(), 16.0, epsilon = 1e-10);
}
#[test]
fn test_mathieu_b_special_cases() {
assert_relative_eq!(mathieu_b::<f64>(1, 0.0).unwrap(), 1.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_b::<f64>(2, 0.0).unwrap(), 4.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_b::<f64>(3, 0.0).unwrap(), 9.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_b::<f64>(4, 0.0).unwrap(), 16.0, epsilon = 1e-10);
}
#[test]
fn test_mathieu_a_q1_reference() {
assert_relative_eq!(mathieu_a::<f64>(0, 1.0).unwrap(), A0_Q1, epsilon = 1e-5);
assert_relative_eq!(mathieu_a::<f64>(1, 1.0).unwrap(), A1_Q1, epsilon = 1e-5);
assert_relative_eq!(mathieu_a::<f64>(2, 1.0).unwrap(), A2_Q1, epsilon = 1e-5);
assert_relative_eq!(mathieu_a::<f64>(3, 1.0).unwrap(), A3_Q1, epsilon = 1e-5);
}
#[test]
fn test_mathieu_b_q1_reference() {
assert_relative_eq!(mathieu_b::<f64>(1, 1.0).unwrap(), B1_Q1, epsilon = 1e-5);
assert_relative_eq!(mathieu_b::<f64>(2, 1.0).unwrap(), B2_Q1, epsilon = 1e-5);
assert_relative_eq!(mathieu_b::<f64>(3, 1.0).unwrap(), B3_Q1, epsilon = 1e-5);
}
#[test]
fn test_mathieu_a_q01_reference() {
assert_relative_eq!(mathieu_a::<f64>(0, 0.1).unwrap(), A0_Q01, epsilon = 1e-6);
assert_relative_eq!(mathieu_a::<f64>(1, 0.1).unwrap(), A1_Q01, epsilon = 1e-6);
assert_relative_eq!(mathieu_a::<f64>(2, 0.1).unwrap(), A2_Q01, epsilon = 1e-6);
}
#[test]
fn test_mathieu_b_q01_reference() {
assert_relative_eq!(mathieu_b::<f64>(1, 0.1).unwrap(), B1_Q01, epsilon = 1e-6);
assert_relative_eq!(mathieu_b::<f64>(2, 0.1).unwrap(), B2_Q01, epsilon = 1e-6);
}
#[test]
fn test_mathieu_small_q() {
let a0 = mathieu_a::<f64>(0, 0.1).unwrap();
let a1 = mathieu_a::<f64>(1, 0.1).unwrap();
let b1 = mathieu_b::<f64>(1, 0.1).unwrap();
let b2 = mathieu_b::<f64>(2, 0.1).unwrap();
assert!(a0 < 0.0);
assert!(a1 > 1.0);
assert!(b1 < 1.0);
assert!(b2 < 4.0);
}
#[test]
fn test_even_fourier_coefficients() {
let coeffs0 = mathieu_even_coef::<f64>(0, 0.0).unwrap();
assert!(!coeffs0.is_empty());
assert_relative_eq!(coeffs0[0], 1.0, epsilon = 1e-10);
let coeffs2 = mathieu_even_coef::<f64>(2, 0.0).unwrap();
assert!(coeffs2.len() > 1);
assert_relative_eq!(coeffs2[1], 1.0, epsilon = 1e-10);
let coeffs_small_q = mathieu_even_coef::<f64>(0, 0.1).unwrap();
assert!(coeffs_small_q.len() > 1);
assert!(coeffs_small_q[0].abs() > 0.99);
assert!(coeffs_small_q[1].abs() < 0.15);
}
#[test]
fn test_odd_fourier_coefficients() {
let coeffs1 = mathieu_odd_coef::<f64>(1, 0.0).unwrap();
assert!(!coeffs1.is_empty());
assert_relative_eq!(coeffs1[0], 1.0, epsilon = 1e-10);
let coeffs3 = mathieu_odd_coef::<f64>(3, 0.0).unwrap();
assert!(coeffs3.len() > 1);
assert_relative_eq!(coeffs3[1], 1.0, epsilon = 1e-10);
let coeffs_small_q = mathieu_odd_coef::<f64>(1, 0.1).unwrap();
assert!(coeffs_small_q.len() > 1);
assert!(coeffs_small_q[0].abs() > 0.99);
assert!(coeffs_small_q[1].abs() < 0.15);
}
#[test]
fn test_mathieu_cem_sem_zero_q() {
let (ce0, ce0_prime) = mathieu_cem(0, 0.0f64, PI / 4.0).unwrap();
assert_relative_eq!(ce0, 1.0, epsilon = 1e-10);
assert_relative_eq!(ce0_prime, 0.0, epsilon = 1e-10);
let (ce1, ce1_prime) = mathieu_cem(1, 0.0f64, PI / 4.0).unwrap();
assert_relative_eq!(ce1, (PI / 4.0).cos(), epsilon = 1e-10);
assert_relative_eq!(ce1_prime, -(PI / 4.0).sin(), epsilon = 1e-10);
let (se1, se1_prime) = mathieu_sem(1, 0.0f64, PI / 4.0).unwrap();
assert_relative_eq!(se1, (PI / 4.0).sin(), epsilon = 1e-10);
assert_relative_eq!(se1_prime, (PI / 4.0).cos(), epsilon = 1e-10);
let (se2, se2_prime) = mathieu_sem(2, 0.0f64, PI / 4.0).unwrap();
assert_relative_eq!(se2, (PI / 2.0).sin(), epsilon = 1e-10);
assert_relative_eq!(se2_prime, 2.0 * (PI / 2.0).cos(), epsilon = 1e-10);
}
#[test]
fn test_mathieu_cem_finite_nonzero_q() {
let (ce, ce_p) = mathieu_cem(0, 1.0f64, PI / 4.0).unwrap();
assert!(ce.is_finite() && ce_p.is_finite());
let (ce1, ce1_p) = mathieu_cem(1, 1.0f64, PI / 4.0).unwrap();
assert!(ce1.is_finite() && ce1_p.is_finite());
}
#[test]
fn test_mathieu_sem_finite_nonzero_q() {
let (se, se_p) = mathieu_sem(1, 1.0f64, PI / 4.0).unwrap();
assert!(se.is_finite() && se_p.is_finite());
let (se2, se2_p) = mathieu_sem(2, 1.0f64, PI / 4.0).unwrap();
assert!(se2.is_finite() && se2_p.is_finite());
}
#[test]
fn test_a_b_interleaving() {
let q = 1.0_f64;
let a0 = mathieu_a::<f64>(0, q).unwrap();
let b1 = mathieu_b::<f64>(1, q).unwrap();
let a1 = mathieu_a::<f64>(1, q).unwrap();
let b2 = mathieu_b::<f64>(2, q).unwrap();
let a2 = mathieu_a::<f64>(2, q).unwrap();
let b3 = mathieu_b::<f64>(3, q).unwrap();
let a3 = mathieu_a::<f64>(3, q).unwrap();
assert!(a0 < b1, "a_0 < b_1 failed: {} vs {}", a0, b1);
assert!(b1 < a1, "b_1 < a_1 failed: {} vs {}", b1, a1);
assert!(a1 < b2, "a_1 < b_2 failed: {} vs {}", a1, b2);
assert!(b2 < a2, "b_2 < a_2 failed: {} vs {}", b2, a2);
assert!(a2 < b3, "a_2 < b_3 failed: {} vs {}", a2, b3);
assert!(b3 < a3, "b_3 < a_3 failed: {} vs {}", b3, a3);
}
}