use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct MathieuConfig {
pub n_fourier: usize,
pub max_iter: usize,
pub tol: f64,
}
impl Default for MathieuConfig {
fn default() -> Self {
MathieuConfig {
n_fourier: 64,
max_iter: 200,
tol: 1e-12,
}
}
}
#[derive(Debug, Clone)]
pub struct MathieuCharacteristic {
pub a: f64,
pub q: f64,
pub order: usize,
pub is_even: bool,
}
pub fn tridiag_eigenvalues(diag: &[f64], off_diag: &[f64]) -> Vec<f64> {
let n = diag.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![diag[0]];
}
let config = MathieuConfig::default();
let mut d = diag.to_vec();
let mut e = vec![0.0f64; n]; let copy_len = off_diag.len().min(n - 1);
e[1..copy_len + 1].copy_from_slice(&off_diag[..copy_len]);
implicit_symmetric_qr(&mut d, &mut e, None, config.max_iter, config.tol);
d.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
d
}
pub fn tridiag_eigenvector(diag: &[f64], off_diag: &[f64], eigenvalue: f64) -> Vec<f64> {
let n = diag.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![1.0];
}
let config = MathieuConfig::default();
let shift = eigenvalue + 1e-10;
let mut v = vec![1.0f64 / (n as f64).sqrt(); n];
for (i, vi) in v.iter_mut().enumerate() {
*vi += 0.01 * ((i + 1) as f64).sin();
}
normalize_vec(&mut v);
for _ in 0..config.max_iter {
let v_old = v.clone();
match tridiag_solve(diag, off_diag, shift, &v) {
Some(w) => {
let mut w = w;
normalize_vec(&mut w);
let dot: f64 = w.iter().zip(v_old.iter()).map(|(a, b)| a * b).sum();
let converged = (1.0 - dot.abs()) < config.tol;
v = w;
if converged {
break;
}
}
None => {
for (i, vi) in v.iter_mut().enumerate() {
*vi = ((i + 1) as f64 * 0.1).sin();
}
normalize_vec(&mut v);
break;
}
}
}
normalize_vec(&mut v);
v
}
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> = if off_diag.len() >= n - 1 {
off_diag[..n - 1].to_vec()
} else {
let mut bv = off_diag.to_vec();
bv.resize(n - 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.0f64; 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 implicit_symmetric_qr(
d: &mut [f64],
e: &mut [f64],
_z: Option<&mut Vec<Vec<f64>>>,
max_iter: usize,
tol: f64,
) {
let n = d.len();
if n <= 1 {
return;
}
let mut m = n - 1;
for _ in 0..max_iter * n {
if m == 0 {
break;
}
let mut l = 0;
for i in (1..=m).rev() {
if e[i].abs() <= tol * (d[i - 1].abs() + d[i].abs()) {
l = i;
break;
}
}
if l == m {
m -= 1;
continue;
}
let tm = d[m];
let tm1 = d[m - 1];
let em = e[m];
let delta = (tm1 - tm) / 2.0;
let sign_delta = if delta >= 0.0 { 1.0 } else { -1.0 };
let shift = tm - em * em / (delta + sign_delta * (delta * delta + em * em).sqrt());
let mut g = d[l] - shift;
let mut s = 1.0f64;
let mut c = 1.0f64;
let mut p = 0.0f64;
for i in l..m {
let f = s * e[i + 1];
let b = c * e[i + 1];
let r = (f * f + g * g).sqrt();
if r < 1e-300 {
e[i] = 0.0;
d[i] -= p;
break;
}
e[i] = r;
s = f / r;
c = g / r;
g = d[i] - p;
let r2 = (d[i + 1] - g) * s + 2.0 * c * b;
p = s * r2;
d[i] = g + p;
g = c * r2 - b;
}
d[m] -= p;
e[m] = g;
e[l] = 0.0;
}
}
pub fn mathieu_a(n: usize, q: f64) -> f64 {
let config = MathieuConfig::default();
let size = config.n_fourier;
if q == 0.0 {
return (n * n) as f64;
}
if n.is_multiple_of(2) {
let half = size / 2;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
diag.push(0.0f64);
if half > 1 {
off_diag.push(q * 2.0f64.sqrt()); }
for k in 1..half {
diag.push((2 * k) as f64 * (2 * k) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let eigenvalues = tridiag_eigenvalues(&diag, &off_diag);
let idx = n / 2;
*eigenvalues.get(idx).unwrap_or(&f64::NAN)
} else {
let half = size / 2;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 0..half {
diag.push((2 * k + 1) as f64 * (2 * k + 1) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let eigenvalues = tridiag_eigenvalues(&diag, &off_diag);
let idx = (n - 1) / 2;
*eigenvalues.get(idx).unwrap_or(&f64::NAN)
}
}
pub fn mathieu_b(n: usize, q: f64) -> f64 {
if n == 0 {
return f64::INFINITY; }
let config = MathieuConfig::default();
let size = config.n_fourier;
if q == 0.0 {
return (n * n) as f64;
}
if n.is_multiple_of(2) {
let half = size / 2;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 1..=half {
diag.push((2 * k) as f64 * (2 * k) as f64);
if k < half {
off_diag.push(q);
}
}
let eigenvalues = tridiag_eigenvalues(&diag, &off_diag);
let idx = n / 2 - 1;
*eigenvalues.get(idx).unwrap_or(&f64::NAN)
} else {
let half = size / 2;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 0..half {
diag.push((2 * k + 1) as f64 * (2 * k + 1) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let eigenvalues = tridiag_eigenvalues(&diag, &off_diag);
let idx = (n - 1) / 2;
*eigenvalues.get(idx).unwrap_or(&f64::NAN)
}
}
pub fn mathieu_ce_coefficients(n: usize, q: f64, config: &MathieuConfig) -> Vec<f64> {
let size = config.n_fourier / 2;
let a_n = mathieu_a(n, q);
if n.is_multiple_of(2) {
let half = size;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
diag.push(0.0f64);
if half > 1 {
off_diag.push(q * 2.0f64.sqrt());
}
for k in 1..half {
diag.push((2 * k) as f64 * (2 * k) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let mut coeffs = tridiag_eigenvector(&diag, &off_diag, a_n);
let sign_idx = if n == 0 { 0 } else { n / 2 };
if coeffs[sign_idx] < 0.0 {
for c in coeffs.iter_mut() {
*c = -*c;
}
}
coeffs
} else {
let half = size;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 0..half {
diag.push((2 * k + 1) as f64 * (2 * k + 1) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let mut coeffs = tridiag_eigenvector(&diag, &off_diag, a_n);
let idx = (n - 1) / 2;
if idx < coeffs.len() && coeffs[idx] < 0.0 {
for c in coeffs.iter_mut() {
*c = -*c;
}
}
coeffs
}
}
pub fn mathieu_se_coefficients(n: usize, q: f64, config: &MathieuConfig) -> Vec<f64> {
if n == 0 {
return vec![];
}
let size = config.n_fourier / 2;
let b_n = mathieu_b(n, q);
if n.is_multiple_of(2) {
let half = size;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 1..=half {
diag.push((2 * k) as f64 * (2 * k) as f64);
if k < half {
off_diag.push(q);
}
}
let mut coeffs = tridiag_eigenvector(&diag, &off_diag, b_n);
let idx = n / 2 - 1;
if idx < coeffs.len() && coeffs[idx] < 0.0 {
for c in coeffs.iter_mut() {
*c = -*c;
}
}
coeffs
} else {
let half = size;
let mut diag = Vec::with_capacity(half);
let mut off_diag = Vec::with_capacity(half - 1);
for k in 0..half {
diag.push((2 * k + 1) as f64 * (2 * k + 1) as f64);
if k < half - 1 {
off_diag.push(q);
}
}
let mut coeffs = tridiag_eigenvector(&diag, &off_diag, b_n);
let idx = (n - 1) / 2;
if idx < coeffs.len() && coeffs[idx] < 0.0 {
for c in coeffs.iter_mut() {
*c = -*c;
}
}
coeffs
}
}
pub fn mathieu_ce(n: usize, q: f64, x: f64) -> f64 {
let config = MathieuConfig::default();
let coeffs = mathieu_ce_coefficients(n, q, &config);
if n.is_multiple_of(2) {
let mut sum = 0.0f64;
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k) as f64 * x).cos();
}
sum
} else {
let mut sum = 0.0f64;
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k + 1) as f64 * x).cos();
}
sum
}
}
pub fn mathieu_se(n: usize, q: f64, x: f64) -> f64 {
if n == 0 {
return 0.0;
}
let config = MathieuConfig::default();
let coeffs = mathieu_se_coefficients(n, q, &config);
if n.is_multiple_of(2) {
let mut sum = 0.0f64;
for (k, &bk) in coeffs.iter().enumerate() {
let freq = (2 * (k + 1)) as f64;
sum += bk * (freq * x).sin();
}
sum
} else {
let mut sum = 0.0f64;
for (k, &bk) in coeffs.iter().enumerate() {
sum += bk * ((2 * k + 1) as f64 * x).sin();
}
sum
}
}
pub fn mathieu_ce_batch(n: usize, q: f64, xs: &[f64]) -> Vec<f64> {
let config = MathieuConfig::default();
let coeffs = mathieu_ce_coefficients(n, q, &config);
xs.iter()
.map(|&x| {
if n.is_multiple_of(2) {
let mut sum = 0.0f64;
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k) as f64 * x).cos();
}
sum
} else {
let mut sum = 0.0f64;
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k + 1) as f64 * x).cos();
}
sum
}
})
.collect()
}
pub fn mathieu_modified_m1(n: usize, q: f64, r: f64) -> f64 {
if r <= 0.0 {
return f64::NAN;
}
if r >= 5.0 {
let phase = r - n as f64 * PI / 2.0 - PI / 4.0;
let amplitude = (2.0 / (PI * r)).sqrt();
amplitude * phase.cos()
} else if r < 1.0 {
let config = MathieuConfig::default();
let coeffs = mathieu_ce_coefficients(n, q, &config);
let mut sum = 0.0f64;
if n.is_multiple_of(2) {
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k) as f64 * r).cosh();
}
} else {
for (k, &ak) in coeffs.iter().enumerate() {
sum += ak * ((2 * k + 1) as f64 * r).cosh();
}
}
sum
} else {
let config = MathieuConfig::default();
let coeffs = mathieu_ce_coefficients(n, q, &config);
let mut sum = 0.0f64;
if n.is_multiple_of(2) {
for (k, &ak) in coeffs.iter().enumerate() {
let arg = (2 * k) as f64 * r;
sum += ak * arg.cosh();
}
} else {
for (k, &ak) in coeffs.iter().enumerate() {
let arg = (2 * k + 1) as f64 * r;
sum += ak * arg.cosh();
}
}
sum
}
}
pub fn mathieu_stability_chart(q_max: f64, n_points: usize) -> Vec<(f64, f64, bool)> {
let mut result = Vec::new();
let n_orders = 4;
for qi in 0..=n_points {
let q = q_max * qi as f64 / n_points as f64;
let a_vals: Vec<f64> = (0..n_orders).map(|n| mathieu_a(n, q)).collect();
let b_vals: Vec<f64> = (1..=n_orders).map(|n| mathieu_b(n, q)).collect();
for k in 0..n_orders.min(a_vals.len()) {
result.push((q, a_vals[k], true));
}
for k in 0..n_orders.min(b_vals.len()) {
result.push((q, b_vals[k], false));
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_mathieu_a_q0() {
assert_relative_eq!(mathieu_a(0, 0.0), 0.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a(1, 0.0), 1.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a(2, 0.0), 4.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a(3, 0.0), 9.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_a(4, 0.0), 16.0, epsilon = 1e-10);
}
#[test]
fn test_mathieu_b_q0() {
assert_relative_eq!(mathieu_b(1, 0.0), 1.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_b(2, 0.0), 4.0, epsilon = 1e-10);
assert_relative_eq!(mathieu_b(3, 0.0), 9.0, epsilon = 1e-10);
}
#[test]
fn test_mathieu_a_small_q() {
let a0 = mathieu_a(0, 0.1);
assert!(a0.is_finite(), "a_0(0.1) should be finite");
assert!(a0.abs() < 0.5, "a_0(0.1) should be small");
}
#[test]
fn test_mathieu_ce_normalization() {
let val = mathieu_ce(0, 0.0, 0.0);
assert!(val.is_finite(), "ce_0(0,0) should be finite");
assert!(val.abs() > 0.1, "ce_0(0,0) should be nonzero");
}
#[test]
fn test_mathieu_ce_batch_consistent() {
let xs = vec![0.0, PI / 4.0, PI / 2.0, PI];
let batch = mathieu_ce_batch(1, 0.5, &xs);
for (i, &x) in xs.iter().enumerate() {
let single = mathieu_ce(1, 0.5, x);
assert_relative_eq!(batch[i], single, epsilon = 1e-12);
}
}
#[test]
fn test_mathieu_stability_chart_nonempty() {
let chart = mathieu_stability_chart(5.0, 20);
assert!(
!chart.is_empty(),
"Stability chart should return non-empty vec"
);
assert!(chart.len() > 10, "Should have multiple points");
}
#[test]
fn test_mathieu_ce_se_orthogonality_q0() {
let n_pts = 1000;
let dx = 2.0 * PI / n_pts as f64;
let sum: f64 = (0..n_pts)
.map(|i| {
let x = i as f64 * dx;
mathieu_ce(1, 0.0, x) * mathieu_se(1, 0.0, x)
})
.sum::<f64>()
* dx;
assert!(
sum.abs() < 0.05,
"ce_1 and se_1 should be approximately orthogonal"
);
}
#[test]
fn test_mathieu_modified_m1_large_r() {
let val = mathieu_modified_m1(0, 1.0, 10.0);
assert!(val.is_finite());
assert!(val.abs() < 1.0, "Should be bounded for large r");
}
#[test]
fn test_tridiag_eigenvalues_simple() {
let diag = vec![2.0, 2.0];
let off_diag = vec![1.0];
let eigs = tridiag_eigenvalues(&diag, &off_diag);
assert_eq!(eigs.len(), 2);
assert_relative_eq!(eigs[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(eigs[1], 3.0, epsilon = 1e-10);
}
#[test]
fn test_tridiag_eigenvector() {
let diag = vec![2.0, 2.0];
let off_diag = vec![1.0];
let v = tridiag_eigenvector(&diag, &off_diag, 1.0);
assert_eq!(v.len(), 2);
let ratio = v[0] / v[1];
assert_relative_eq!(ratio.abs(), 1.0, epsilon = 1e-5);
assert!(ratio < 0.0, "Components should have opposite signs");
}
}