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")
}
#[allow(dead_code)]
pub fn mathieu_a<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
if q.is_zero() {
return Ok(const_f64::<F>((m * m) as f64));
}
if q.abs() < const_f64::<F>(0.1) {
return small_q_approximation_even(m, q);
}
continued_fraction_even(m, q)
}
#[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());
}
if q.is_zero() {
return Ok(const_f64::<F>((m * m) as f64));
}
if q.abs() < const_f64::<F>(0.1) {
return small_q_approximation_odd(m, q);
}
continued_fraction_odd(m, q)
}
#[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 a = mathieu_a(m, q)?;
compute_even_coefficients(m, q, a)
}
#[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 b = mathieu_b(m, q)?;
compute_odd_coefficients(m, q, b)
}
#[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 _a = mathieu_a(m, q)?;
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 _b = mathieu_b(m, q)?;
let coeffs = mathieu_odd_coef(m, q)?;
evaluate_odd_mathieu(m, x, &coeffs)
}
#[allow(dead_code)]
fn small_q_approximation_even<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let m_squared = const_f64::<F>((m * m) as f64);
if m == 0 {
Ok(-q * q / const_f64::<F>(2.0))
} else if m == 1 {
Ok(F::one() + q - q * q / const_f64::<F>(8.0))
} else if m == 2 {
Ok(const_f64::<F>(4.0) - q * q / const_f64::<F>(12.0))
} else {
let factor = const_f64::<F>((2 * (m * m - 1)) as f64);
Ok(m_squared + q * q / factor)
}
}
#[allow(dead_code)]
fn small_q_approximation_odd<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let m_squared = const_f64::<F>((m * m) as f64);
if m == 1 {
Ok(F::one() - q - q * q / const_f64::<F>(8.0))
} else if m == 2 {
Ok(const_f64::<F>(4.0) + q * q / const_f64::<F>(12.0))
} else {
let factor = const_f64::<F>((2 * (m * m - 1)) as f64);
Ok(m_squared - q * q / factor)
}
}
#[allow(dead_code)]
fn continued_fraction_even<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let mut a = if q.abs() < const_f64::<F>(1.0) {
small_q_approximation_even(m, q)?
} else {
if m == 0 {
-const_f64::<F>(2.0) * q.sqrt() + const_f64::<F>(0.5)
} else {
const_f64::<F>((m * m) as f64) + q
}
};
let max_iter = 100;
let tolerance = const_f64::<F>(1e-12);
for _ in 0..max_iter {
let a_new = refine_even_characteristic_value(m, q, a);
if (a_new - a).abs() < tolerance {
return Ok(a_new);
}
a = a_new;
}
Ok(a)
}
#[allow(dead_code)]
fn continued_fraction_odd<F>(m: usize, q: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let mut b = if q.abs() < const_f64::<F>(1.0) {
small_q_approximation_odd(m, q)?
} else {
const_f64::<F>((m * m) as f64) + q
};
let max_iter = 100;
let tolerance = const_f64::<F>(1e-12);
for _ in 0..max_iter {
let b_new = refine_odd_characteristic_value(m, q, b);
if (b_new - b).abs() < tolerance {
return Ok(b_new);
}
b = b_new;
}
Ok(b)
}
#[allow(dead_code)]
fn refine_even_characteristic_value<F>(m: usize, q: F, a: F) -> F
where
F: Float + FromPrimitive + Debug,
{
let q2 = q * q;
let m_f = const_f64::<F>(m as f64);
let max_terms = 50;
let tolerance = const_f64::<F>(1e-14);
let beta = q2 / const_f64::<F>(4.0);
let mut cf_value = F::zero();
let start_index = max_terms;
let mut ratio_prev = F::zero();
let mut ratio_curr = const_f64::<F>(1e-30);
for k in (0..start_index).rev() {
let r = m + 2 * k;
let r_f = const_f64::<F>(r as f64);
let a_r = (r_f + m_f / const_f64::<F>(2.0)).powi(2);
let ratio_next = beta / (a_r - a - beta * ratio_curr);
if k < start_index - 5 && (ratio_curr - ratio_prev).abs() < tolerance {
cf_value = ratio_curr;
break;
}
ratio_prev = ratio_curr;
ratio_curr = ratio_next;
if !ratio_curr.is_finite() {
break;
}
}
let correction = beta * cf_value;
let max_correction = a.abs() * const_f64::<F>(0.1);
let limited_correction = if correction.abs() > max_correction {
correction.signum() * max_correction
} else {
correction
};
a - limited_correction
}
#[allow(dead_code)]
fn refine_odd_characteristic_value<F>(m: usize, q: F, b: F) -> F
where
F: Float + FromPrimitive + Debug,
{
let q2 = q * q;
let _m_f = const_f64::<F>(m as f64);
let max_terms = 50;
let tolerance = const_f64::<F>(1e-14);
let beta = q2 / const_f64::<F>(4.0);
let mut cf_value = F::zero();
let start_index = max_terms;
let mut ratio_prev = F::zero();
let mut ratio_curr = const_f64::<F>(1e-30);
for k in (0..start_index).rev() {
let r = m + 2 * k;
let r_f = const_f64::<F>(r as f64);
let b_r = if m % 2 == 1 {
(r_f + const_f64::<F>(0.5)).powi(2)
} else {
(r_f + F::one()).powi(2)
};
let ratio_next = beta / (b_r - b - beta * ratio_curr);
if k < start_index - 5 && (ratio_curr - ratio_prev).abs() < tolerance {
cf_value = ratio_curr;
break;
}
ratio_prev = ratio_curr;
ratio_curr = ratio_next;
if !ratio_curr.is_finite() {
break;
}
}
let correction = beta * cf_value;
let max_correction = b.abs() * const_f64::<F>(0.1);
let limited_correction = if correction.abs() > max_correction {
correction.signum() * max_correction
} else {
correction
};
b - limited_correction
}
#[allow(dead_code)]
fn compute_even_coefficients<F>(m: usize, q: F, a: F) -> SpecialResult<Vec<F>>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
let num_coeffs = if q.abs() < const_f64::<F>(1.0) {
10
} else if q.abs() < const_f64::<F>(10.0) {
20
} else {
40
};
let mut coeffs = vec![F::zero(); num_coeffs];
if q.is_zero() {
if m < coeffs.len() {
coeffs[m / 2] = F::one();
}
return Ok(coeffs);
}
let extended_coeffs = num_coeffs + 20; let mut temp_coeffs = vec![F::zero(); extended_coeffs];
temp_coeffs[extended_coeffs - 1] = const_f64::<F>(1e-30);
temp_coeffs[extended_coeffs - 2] = const_f64::<F>(1e-30);
let beta = q * q / const_f64::<F>(4.0);
if m.is_multiple_of(2) {
for i in (2..extended_coeffs).rev() {
if i < 2 {
continue;
} let k = const_f64::<F>((2 * i) as f64);
let a_k = k * k;
let denominator = beta;
if denominator.abs() > const_f64::<F>(1e-15) {
let future_term = if i + 2 < extended_coeffs {
beta * temp_coeffs[i + 2]
} else {
F::zero()
};
temp_coeffs[i - 2] = -((a_k - a) * temp_coeffs[i] + future_term) / beta;
} else {
temp_coeffs[i - 2] = F::zero();
}
}
let norm_index = m / 2;
if norm_index < coeffs.len() && temp_coeffs[norm_index].abs() > const_f64::<F>(1e-30) {
let scale = F::one() / temp_coeffs[norm_index];
for i in 0..num_coeffs {
coeffs[i] = temp_coeffs[i] * scale;
}
} else {
coeffs[0] = F::one();
for i in 1..num_coeffs {
let k = const_f64::<F>((2 * i) as f64);
let denominator = a - k * k;
if denominator.abs() > const_f64::<F>(1e-10) {
coeffs[i] = q * coeffs[i - 1] / denominator;
} else {
coeffs[i] = F::zero();
}
}
}
} else {
for i in (2..extended_coeffs).rev() {
let k = const_f64::<F>((2 * i + 1) as f64);
let a_k = k * k;
let denominator = beta;
if denominator.abs() > const_f64::<F>(1e-15) {
temp_coeffs[i - 2] =
-((a_k - a) * temp_coeffs[i] + beta * temp_coeffs[i + 2]) / beta;
} else {
temp_coeffs[i - 2] = F::zero();
}
}
let norm_index = (m - 1) / 2;
if norm_index < coeffs.len() && temp_coeffs[norm_index].abs() > const_f64::<F>(1e-30) {
let scale = F::one() / temp_coeffs[norm_index];
for i in 0..num_coeffs {
coeffs[i] = temp_coeffs[i] * scale;
}
} else {
coeffs[0] = F::one();
for i in 1..num_coeffs {
let k = const_f64::<F>((2 * i + 1) as f64);
let denominator = a - k * k;
if denominator.abs() > const_f64::<F>(1e-10) {
coeffs[i] = q * coeffs[i - 1] / denominator;
} else {
coeffs[i] = F::zero();
}
}
}
}
let sum_of_squares: F = coeffs.iter().map(|&c| c * c).sum();
let norm = sum_of_squares.sqrt();
if !norm.is_zero() {
for c in coeffs.iter_mut() {
*c = *c / norm;
}
}
Ok(coeffs)
}
#[allow(dead_code)]
fn compute_odd_coefficients<F>(m: usize, q: F, b: F) -> SpecialResult<Vec<F>>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
if m == 0 {
return Ok(Vec::new());
}
let num_coeffs = if q.abs() < const_f64::<F>(1.0) {
10
} else if q.abs() < const_f64::<F>(10.0) {
20
} else {
40
};
let mut coeffs = vec![F::zero(); num_coeffs];
if q.is_zero() {
if m - 1 < coeffs.len() {
coeffs[(m - 1) / 2] = F::one();
}
return Ok(coeffs);
}
let extended_coeffs = num_coeffs + 20; let mut temp_coeffs = vec![F::zero(); extended_coeffs];
temp_coeffs[extended_coeffs - 1] = const_f64::<F>(1e-30);
temp_coeffs[extended_coeffs - 2] = const_f64::<F>(1e-30);
let beta = q * q / const_f64::<F>(4.0);
if m % 2 == 1 {
for i in (2..extended_coeffs).rev() {
if i < 2 {
continue;
} let k = const_f64::<F>((2 * i + 1) as f64);
let b_k = k * k;
let denominator = beta;
if denominator.abs() > const_f64::<F>(1e-15) {
let future_term = if i + 2 < extended_coeffs {
beta * temp_coeffs[i + 2]
} else {
F::zero()
};
temp_coeffs[i - 2] = -((b_k - b) * temp_coeffs[i] + future_term) / beta;
} else {
temp_coeffs[i - 2] = F::zero();
}
}
let norm_index = (m - 1) / 2;
if norm_index < coeffs.len() && temp_coeffs[norm_index].abs() > const_f64::<F>(1e-30) {
let scale = F::one() / temp_coeffs[norm_index];
for i in 0..num_coeffs {
coeffs[i] = temp_coeffs[i] * scale;
}
} else {
coeffs[0] = F::one();
for i in 1..num_coeffs {
let k = const_f64::<F>((2 * i + 1) as f64);
let denominator = b - k * k;
if denominator.abs() > const_f64::<F>(1e-10) {
coeffs[i] = q * coeffs[i - 1] / denominator;
} else {
coeffs[i] = F::zero();
}
}
}
} else {
for i in (2..extended_coeffs).rev() {
let k = const_f64::<F>((2 * i + 2) as f64);
let b_k = k * k;
let denominator = beta;
if denominator.abs() > const_f64::<F>(1e-15) {
temp_coeffs[i - 2] =
-((b_k - b) * temp_coeffs[i] + beta * temp_coeffs[i + 2]) / beta;
} else {
temp_coeffs[i - 2] = F::zero();
}
}
let norm_index = (m - 2) / 2;
if norm_index < coeffs.len() && temp_coeffs[norm_index].abs() > const_f64::<F>(1e-30) {
let scale = F::one() / temp_coeffs[norm_index];
for i in 0..num_coeffs {
coeffs[i] = temp_coeffs[i] * scale;
}
} else {
coeffs[0] = F::one();
for i in 1..num_coeffs {
let k = const_f64::<F>((2 * i + 2) as f64);
let denominator = b - k * k;
if denominator.abs() > const_f64::<F>(1e-10) {
coeffs[i] = q * coeffs[i - 1] / denominator;
} else {
coeffs[i] = F::zero();
}
}
}
}
let sum_of_squares: F = coeffs.iter().map(|&c| c * c).sum();
let norm = sum_of_squares.sqrt();
if !norm.is_zero() {
for c in coeffs.iter_mut() {
*c = *c / norm;
}
}
Ok(coeffs)
}
#[allow(dead_code)]
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 arg = const_f64::<F>((2 * k) as f64) * x;
result = result + coef * arg.cos();
derivative = derivative - coef * const_f64::<F>((2 * k) as f64) * arg.sin();
}
} else {
for (k, &coef) in coeffs.iter().enumerate() {
let arg = const_f64::<F>((2 * k + 1) as f64) * x;
result = result + coef * arg.cos();
derivative = derivative - coef * const_f64::<F>((2 * k + 1) as f64) * arg.sin();
}
}
Ok((result, derivative))
}
#[allow(dead_code)]
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 arg = const_f64::<F>((2 * k + 1) as f64) * x;
result = result + coef * arg.sin();
derivative = derivative + coef * const_f64::<F>((2 * k + 1) as f64) * arg.cos();
}
} else {
for (k, &coef) in coeffs.iter().enumerate() {
let arg = const_f64::<F>((2 * k + 2) as f64) * x;
result = result + coef * arg.sin();
derivative = derivative + coef * const_f64::<F>((2 * k + 2) as f64) * arg.cos();
}
}
Ok((result, derivative))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::f64::consts::PI;
#[test]
fn test_mathieu_a_special_cases() {
assert_relative_eq!(
mathieu_a::<f64>(0, 0.0).expect("test/example should not fail"),
0.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_a::<f64>(1, 0.0).expect("test/example should not fail"),
1.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_a::<f64>(2, 0.0).expect("test/example should not fail"),
4.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_a::<f64>(3, 0.0).expect("test/example should not fail"),
9.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_a::<f64>(4, 0.0).expect("test/example should not fail"),
16.0,
epsilon = 1e-10
);
}
#[test]
fn test_mathieu_b_special_cases() {
assert_relative_eq!(
mathieu_b::<f64>(1, 0.0).expect("test/example should not fail"),
1.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_b::<f64>(2, 0.0).expect("test/example should not fail"),
4.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_b::<f64>(3, 0.0).expect("test/example should not fail"),
9.0,
epsilon = 1e-10
);
assert_relative_eq!(
mathieu_b::<f64>(4, 0.0).expect("test/example should not fail"),
16.0,
epsilon = 1e-10
);
}
#[test]
fn test_mathieu_small_q() {
let a0 = mathieu_a::<f64>(0, 0.1).expect("test/example should not fail");
let a1 = mathieu_a::<f64>(1, 0.1).expect("test/example should not fail");
let _b1 = mathieu_b::<f64>(1, 0.1).expect("test/example should not fail");
let b2 = mathieu_b::<f64>(2, 0.1).expect("test/example should not fail");
assert!(a0 < 0.0);
assert!(a1 > 1.0);
if !b2.is_infinite() {
assert!(b2 > 4.0);
}
}
#[test]
fn test_even_fourier_coefficients() {
let coeffs0 = mathieu_even_coef::<f64>(0, 0.0).expect("test/example should not fail");
assert!(!coeffs0.is_empty());
assert_relative_eq!(coeffs0[0], 1.0, epsilon = 1e-10);
let coeffs2 = mathieu_even_coef::<f64>(2, 0.0).expect("test/example should not fail");
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).expect("test/example should not fail");
assert!(coeffs_small_q.len() > 1);
assert!(coeffs_small_q[0].abs() > 0.01); assert!(coeffs_small_q[1].abs() < 2.0); }
#[test]
fn test_odd_fourier_coefficients() {
let coeffs1 = mathieu_odd_coef::<f64>(1, 0.0).expect("test/example should not fail");
assert!(!coeffs1.is_empty());
assert_relative_eq!(coeffs1[0], 1.0, epsilon = 1e-10);
let coeffs3 = mathieu_odd_coef::<f64>(3, 0.0).expect("test/example should not fail");
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).expect("test/example should not fail");
assert!(coeffs_small_q.len() > 1);
assert!(coeffs_small_q[0].abs() > 0.01); assert!(coeffs_small_q[1].abs() < 2.0); }
#[test]
fn test_mathieu_cem_sem_zero_q() {
let (ce0, ce0_prime) = mathieu_cem(0, 0.0, PI / 4.0).expect("test/example should not fail");
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.0, PI / 4.0).expect("test/example should not fail");
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.0, PI / 4.0).expect("test/example should not fail");
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.0, PI / 4.0).expect("test/example should not fail");
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);
}
}