use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LameConfig {
pub n_fourier: usize,
pub tol: f64,
pub max_degree: usize,
pub n_quadrature: usize,
pub use_small_k_expansion: bool,
}
impl Default for LameConfig {
fn default() -> Self {
LameConfig {
n_fourier: 32,
tol: 1e-12,
max_degree: 8,
n_quadrature: 64,
use_small_k_expansion: true,
}
}
}
#[derive(Debug, Clone)]
pub struct LameResult {
pub value: f64,
pub eigenvalue: f64,
pub degree: usize,
pub species: usize,
pub error_estimate: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EllipsoidalCoord {
pub rho: f64,
pub mu: f64,
pub nu: f64,
}
impl EllipsoidalCoord {
pub fn new(rho: f64, mu: f64, nu: f64, a: f64, b: f64, _c: f64) -> Result<Self, String> {
if rho < a {
return Err(format!("rho ({rho}) must be >= a ({a})"));
}
if mu < b || mu > a {
return Err(format!("mu ({mu}) must be in [{b}, {a}]"));
}
Ok(EllipsoidalCoord { rho, mu, nu })
}
pub fn to_cartesian(&self, h2: f64, k2: f64) -> (f64, f64, f64) {
let rho2 = self.rho * self.rho;
let mu2 = self.mu * self.mu;
let nu2 = self.nu * self.nu;
let x = if h2 > 0.0 && k2 > 0.0 {
(rho2 * mu2 * nu2 / (h2 * k2)).sqrt()
} else {
0.0
};
let y = if h2 > 0.0 && k2 > h2 {
let num = (rho2 - h2) * (mu2 - h2) * (h2 - nu2);
if num >= 0.0 {
(num / (h2 * (k2 - h2))).sqrt()
} else {
0.0
}
} else {
0.0
};
let z = if k2 > 0.0 && k2 > h2 {
let num = (rho2 - k2) * (k2 - mu2) * (k2 - nu2);
if num >= 0.0 {
(num / (k2 * (k2 - h2))).sqrt()
} else {
0.0
}
} else {
0.0
};
(x, y, z)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum LameSpecies {
K(usize),
L(usize),
M(usize),
N(usize),
}
impl LameSpecies {
pub fn all_for_degree(n: usize) -> Vec<LameSpecies> {
let mut species = Vec::with_capacity(2 * n + 1);
let n_k = (n + 1 + 1) / 2; for i in 0..n_k {
species.push(LameSpecies::K(i));
}
let n_l = n / 2;
for i in 0..n_l {
species.push(LameSpecies::L(i));
}
let n_m = (n + 1) / 2;
for i in 0..n_m {
species.push(LameSpecies::M(i));
}
let n_n = (2 * n + 1).saturating_sub(n_k + n_l + n_m);
for i in 0..n_n {
species.push(LameSpecies::N(i));
}
species
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lame_config_default() {
let config = LameConfig::default();
assert_eq!(config.n_fourier, 32);
assert!((config.tol - 1e-12).abs() < f64::EPSILON);
assert_eq!(config.max_degree, 8);
}
#[test]
fn test_ellipsoidal_coord_new() {
let coord = EllipsoidalCoord::new(3.0, 2.0, 1.0, 3.0, 2.0, 1.0);
assert!(coord.is_ok());
let coord2 = EllipsoidalCoord::new(1.0, 2.0, 1.0, 3.0, 2.0, 1.0);
assert!(coord2.is_err());
}
#[test]
fn test_lame_species_count() {
for n in 0..=8 {
let species = LameSpecies::all_for_degree(n);
assert_eq!(
species.len(),
2 * n + 1,
"Degree {n}: expected {} species, got {}",
2 * n + 1,
species.len()
);
}
}
#[test]
fn test_ellipsoidal_to_cartesian() {
let coord = EllipsoidalCoord {
rho: 3.0,
mu: 2.0,
nu: 1.0,
};
let (x, y, z) = coord.to_cartesian(1.0, 2.0);
assert!(x.is_finite());
assert!(y.is_finite());
assert!(z.is_finite());
}
}