#![allow(dead_code)]
use crate::{orthogonal, SpecialError, SpecialResult};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::numeric::Complex64;
use std::f64::consts::PI;
#[allow(dead_code)]
pub fn ellip_harm(h2: f64, k2: f64, n: usize, p: usize, s: f64) -> SpecialResult<f64> {
if h2 < 0.0 || k2 < 0.0 {
return Err(SpecialError::ValueError(
"Parameters h² and k² must be non-negative".to_string(),
));
}
if p > n {
return Err(SpecialError::ValueError(
"Order p must not exceed degree n".to_string(),
));
}
if s <= 0.0 {
return Err(SpecialError::ValueError(
"Coordinate parameter s must be positive".to_string(),
));
}
let mu = if s > 1e10 {
1.0 - 1.0 / (2.0 * s)
} else if s < 1e-10 {
s.sqrt() * (1.0 - s / 8.0 + s * s / 64.0)
} else {
s.sqrt()
};
let mu_clamped = mu.clamp(-1.0, 1.0);
let legendre = if p == 0 {
orthogonal::legendre(n, mu_clamped)
} else {
let legendre_val = orthogonal::legendre_assoc(n, p as i32, mu_clamped);
if !legendre_val.is_finite() {
0.0
} else {
legendre_val
}
};
let h_factor = if h2 > 0.0 && h2 < 10.0 {
let correction = h2 * (n as f64 + 0.5) / (2.0 * n as f64 + 1.0);
if correction < 100.0 {
1.0 + correction
} else {
1.0 + 100.0 }
} else {
1.0
};
let k_factor = if k2 > 0.0 && k2 < 10.0 && p > 0 {
let correction = k2 * p as f64 / (n as f64 + 1.0);
if correction < 100.0 {
1.0 + correction
} else {
1.0 + 100.0 }
} else {
1.0
};
let result = legendre * h_factor * k_factor;
if result.is_finite() {
Ok(result)
} else {
Ok(0.0)
}
}
#[allow(dead_code)]
pub fn ellip_harm_2(h2: f64, k2: f64, n: usize, p: usize, s: f64) -> SpecialResult<f64> {
if h2 < 0.0 || k2 < 0.0 {
return Err(SpecialError::ValueError(
"Parameters h² and k² must be non-negative".to_string(),
));
}
if p > n {
return Err(SpecialError::ValueError(
"Order p must not exceed degree n".to_string(),
));
}
if s <= 0.0 {
return Err(SpecialError::ValueError(
"Coordinate parameter s must be positive".to_string(),
));
}
let first_kind = ellip_harm(h2, k2, n, p, s)?;
let s_squared = s * s;
let nu = if s_squared > 1.0 {
if s_squared < 1e10 {
(s_squared - 1.0).sqrt()
} else {
s
}
} else {
0.0
};
let q_factor = if nu > 1e-15 {
let exp_arg = -nu;
if exp_arg > -100.0 {
exp_arg.exp() / (2.0 * nu)
} else {
0.0 }
} else {
0.5
};
let normalization = if n >= p {
let num = 2.0 * n as f64 + 1.0;
let factorial_ratio = if n + p <= 20 {
factorial(n - p) as f64 / factorial(n + p) as f64
} else {
let stirling_ratio = ((n - p) as f64 / (n + p) as f64).powi(n as i32);
let correction = ((2.0 * (n - p) as f64 + 1.0) / (2.0 * (n + p) as f64 + 1.0)).sqrt();
stirling_ratio * correction
};
num * factorial_ratio
} else {
0.0 };
let result = first_kind * q_factor * normalization;
if result.is_finite() {
Ok(result)
} else {
Ok(0.0)
}
}
#[allow(dead_code)]
pub fn ellip_normal(h2: f64, k2: f64, n: usize, p: usize) -> SpecialResult<f64> {
if h2 < 0.0 || k2 < 0.0 {
return Err(SpecialError::ValueError(
"Parameters h² and k² must be non-negative".to_string(),
));
}
if p > n {
return Err(SpecialError::ValueError(
"Order p must not exceed degree n".to_string(),
));
}
let spherical_norm = ((2.0 * n as f64 + 1.0) / (4.0 * PI) * factorial(n - p) as f64
/ factorial(n + p) as f64)
.sqrt();
let h_correction = if h2 > 0.0 {
(1.0 + h2 * (n as f64 * n as f64 + n as f64 + 0.5) / (2.0 * n as f64 + 1.0)).sqrt()
} else {
1.0
};
let k_correction = if k2 > 0.0 && p > 0 {
(1.0 + k2 * p as f64 * (p as f64 + 1.0) / (2.0 * n as f64 + 1.0)).sqrt()
} else {
1.0
};
Ok(spherical_norm * h_correction * k_correction)
}
#[allow(dead_code)]
pub fn ellip_harm_array(
h2: f64,
k2: f64,
n: usize,
p: usize,
s_array: &ArrayView1<f64>,
) -> SpecialResult<Array1<f64>> {
let mut result = Array1::zeros(s_array.len());
for (i, &s) in s_array.iter().enumerate() {
result[i] = ellip_harm(h2, k2, n, p, s)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn ellip_harm_coefficients(
h2: f64,
k2: f64,
max_degree: usize,
max_order: usize,
) -> SpecialResult<Array2<f64>> {
if h2 < 0.0 || k2 < 0.0 {
return Err(SpecialError::ValueError(
"Parameters h² and k² must be non-negative".to_string(),
));
}
if max_order > max_degree {
return Err(SpecialError::ValueError(
"Maximum _order cannot exceed maximum _degree".to_string(),
));
}
let mut coefficients = Array2::zeros((max_degree + 1, max_order + 1));
for n in 0..=max_degree {
for p in 0..=max_order.min(n) {
let norm = ellip_normal(h2, k2, n, p)?;
let base_coeff = (2.0 * n as f64 + 1.0) / (4.0 * PI);
coefficients[[n, p]] = base_coeff * norm;
}
}
Ok(coefficients)
}
#[allow(dead_code)]
pub fn ellip_harm_complex(
h2: Complex64,
k2: Complex64,
n: usize,
p: usize,
z: Complex64,
) -> SpecialResult<Complex64> {
if p > n {
return Err(SpecialError::ValueError(
"Order p must not exceed degree n".to_string(),
));
}
let mu = z.sqrt();
let legendre_complex = if p == 0 {
legendre_polynomial_complex(n, mu)
} else {
associated_legendre_complex(n, p, mu)
};
let h_factor = Complex64::new(1.0, 0.0) + h2 * (n as f64 + 0.5) / (2.0 * n as f64 + 1.0);
let k_factor = if p > 0 {
Complex64::new(1.0, 0.0) + k2 * p as f64 / (n as f64 + 1.0)
} else {
Complex64::new(1.0, 0.0)
};
Ok(legendre_complex * h_factor * k_factor)
}
#[allow(dead_code)]
fn factorial(n: usize) -> usize {
match n {
0 | 1 => 1,
_ => n * factorial(n - 1),
}
}
#[allow(dead_code)]
fn legendre_polynomial_complex(n: usize, z: Complex64) -> Complex64 {
match n {
0 => Complex64::new(1.0, 0.0),
1 => z,
_ => {
let mut p0 = Complex64::new(1.0, 0.0);
let mut p1 = z;
for k in 1..n {
let p2 = ((2 * k + 1) as f64 * z * p1 - k as f64 * p0) / (k + 1) as f64;
p0 = p1;
p1 = p2;
}
p1
}
}
}
#[allow(dead_code)]
fn associated_legendre_complex(n: usize, p: usize, z: Complex64) -> Complex64 {
if p == 0 {
return legendre_polynomial_complex(n, z);
}
let factor = (Complex64::new(1.0, 0.0) - z * z).powf(p as f64 / 2.0);
let base_poly = legendre_polynomial_complex(n, z);
let diff_factor = factorial(n + p) as f64 / factorial(n - p) as f64;
factor * base_poly * diff_factor / (2.0_f64.powi(p as i32) * factorial(p) as f64)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_ellip_harm_basic() {
let h2 = 0.1;
let k2 = 0.05;
let n = 2;
let p = 1;
let s = 1.5;
let result = ellip_harm(h2, k2, n, p, s).expect("Operation failed");
assert!(result.is_finite());
assert!(result.abs() < 100.0);
}
#[test]
fn test_ellip_harm_validation() {
assert!(ellip_harm(-0.1, 0.05, 2, 1, 1.5).is_err());
assert!(ellip_harm(0.1, -0.05, 2, 1, 1.5).is_err());
assert!(ellip_harm(0.1, 0.05, 2, 3, 1.5).is_err()); assert!(ellip_harm(0.1, 0.05, 2, 1, -1.5).is_err()); }
#[test]
fn test_ellip_harm_2() {
let h2 = 0.05;
let k2 = 0.1;
let n = 3;
let p = 2;
let s = 2.0;
let result = ellip_harm_2(h2, k2, n, p, s).expect("Operation failed");
assert!(result.is_finite());
}
#[test]
fn test_ellip_normal() {
let h2 = 0.1;
let k2 = 0.05;
let n = 4;
let p = 3;
let norm = ellip_normal(h2, k2, n, p).expect("Operation failed");
assert!(norm > 0.0);
assert!(norm.is_finite());
}
#[test]
fn test_ellip_harm_array() {
let h2 = 0.1;
let k2 = 0.05;
let n = 2;
let p = 1;
let s_values = Array1::linspace(1.0, 3.0, 10);
let result = ellip_harm_array(h2, k2, n, p, &s_values.view()).expect("Operation failed");
assert_eq!(result.len(), 10);
assert!(result.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_ellip_harm_coefficients() {
let h2 = 0.1;
let k2 = 0.05;
let max_n = 5;
let max_p = 3;
let coeffs = ellip_harm_coefficients(h2, k2, max_n, max_p).expect("Operation failed");
assert_eq!(coeffs.dim(), (6, 4));
for row in coeffs.axis_iter(scirs2_core::ndarray::Axis(0)) {
for &coeff in row.iter() {
assert!(coeff.is_finite());
}
}
}
#[test]
fn test_ellip_harm_complex() {
let h2 = Complex64::new(0.1, 0.02);
let k2 = Complex64::new(0.05, 0.01);
let n = 2;
let p = 1;
let z = Complex64::new(1.5, 0.5);
let result = ellip_harm_complex(h2, k2, n, p, z).expect("Operation failed");
assert!(result.norm().is_finite());
}
#[test]
fn test_spherical_limit() {
let h2 = 0.0;
let k2 = 0.0;
let n = 2;
let p = 0;
let s = 1.0;
let ellip_result = ellip_harm(h2, k2, n, p, s).expect("Operation failed");
let legendre_result = orthogonal::legendre(n, 1.0);
assert_relative_eq!(ellip_result, legendre_result, epsilon = 1e-10);
}
}