use crate::error::SpecialResult;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::f64;
use std::f64::consts::PI;
use std::fmt::Debug;
#[allow(dead_code)]
pub fn sph_harm<F>(l: usize, m: i32, theta: F, phi: F) -> SpecialResult<F>
where
F: Float + FromPrimitive + Debug,
{
let m_abs = m.unsigned_abs() as usize;
if m_abs > l {
return Ok(F::zero()); }
let cos_theta = theta.cos();
let x = cos_theta.to_f64().unwrap_or(0.0);
let bar_plm = normalized_assoc_legendre(l, m_abs, x);
let bar_plm_f = F::from(bar_plm).unwrap_or(F::zero());
let angular_part: F;
if m == 0 {
angular_part = F::one();
} else {
let sqrt2 = F::from(std::f64::consts::SQRT_2).unwrap_or(F::one());
let m_f = F::from(m_abs).unwrap_or(F::one());
let m_phi = m_f * phi;
if m > 0 {
angular_part = sqrt2 * m_phi.cos();
} else {
angular_part = sqrt2 * m_phi.sin();
}
}
Ok(bar_plm_f * angular_part)
}
fn normalized_assoc_legendre(l: usize, m: usize, x: f64) -> f64 {
if m > l {
return 0.0;
}
let x = x.clamp(-1.0, 1.0);
let sin_theta = ((1.0 - x) * (1.0 + x)).sqrt();
let inv_4pi = 1.0 / (4.0 * std::f64::consts::PI);
let mut bar_pmm = inv_4pi.sqrt(); for k in 1..=m {
let k_f = k as f64;
bar_pmm *= ((2.0 * k_f - 1.0) / (2.0 * k_f)).sqrt() * sin_theta;
}
bar_pmm *= ((2 * m + 1) as f64).sqrt();
if l == m {
return bar_pmm;
}
let mut bar_pm1 = ((2 * m + 3) as f64).sqrt() * x * bar_pmm;
if l == m + 1 {
return bar_pm1;
}
let mut bar_prev2 = bar_pmm;
let mut bar_prev1 = bar_pm1;
let mut bar_cur = 0.0;
let m2 = (m * m) as f64;
for ll in (m + 2)..=l {
let ll_f = ll as f64;
let ll2 = ll_f * ll_f;
let denom = ll2 - m2;
let alpha = ((4.0 * ll2 - 1.0) / denom).sqrt();
let beta = ((2.0 * ll_f + 1.0) * (ll_f + m as f64 - 1.0) * (ll_f - m as f64 - 1.0)
/ ((2.0 * ll_f - 3.0) * denom))
.sqrt();
bar_cur = alpha * x * bar_prev1 - beta * bar_prev2;
bar_prev2 = bar_prev1;
bar_prev1 = bar_cur;
}
bar_cur
}
#[allow(dead_code)]
pub fn sph_harm_complex<F>(l: usize, m: i32, theta: F, phi: F) -> SpecialResult<(F, F)>
where
F: Float + FromPrimitive + Debug,
{
let m_abs = m.unsigned_abs() as usize;
if m_abs > l {
return Ok((F::zero(), F::zero())); }
let cos_theta = theta.cos();
let x = cos_theta.to_f64().unwrap_or(0.0);
let bar_plm = normalized_assoc_legendre(l, m_abs, x);
let bar_plm_f = F::from(bar_plm).unwrap_or(F::zero());
let cs_phase = if m_abs.is_multiple_of(2) {
F::one()
} else {
-F::one()
};
let m_f64 = m as f64;
let m_phi = F::from(m_f64).unwrap_or(F::zero()) * phi;
let cos_m_phi = m_phi.cos();
let sin_m_phi = m_phi.sin();
let amplitude = if m >= 0 {
cs_phase * bar_plm_f
} else {
bar_plm_f
};
let real_part = amplitude * cos_m_phi;
let imag_part = amplitude * sin_m_phi;
Ok((real_part, imag_part))
}
pub fn sph_harm_normalization(l: usize, m: i32) -> f64 {
let m_abs = m.unsigned_abs() as usize;
if m_abs > l {
return 0.0;
}
let two_l_plus_1 = (2 * l + 1) as f64;
let four_pi = 4.0 * PI;
let mut factorial_ratio = 1.0;
if m_abs > 0 {
for i in (l - m_abs + 1)..=(l + m_abs) {
factorial_ratio /= i as f64;
}
}
(two_l_plus_1 / four_pi * factorial_ratio).sqrt()
}
pub fn solid_harmonic_regular(
l: usize,
m: i32,
r: f64,
theta: f64,
phi: f64,
) -> SpecialResult<f64> {
let y_lm: f64 = sph_harm(l, m, theta, phi)?;
let factor = (4.0 * PI / (2 * l + 1) as f64).sqrt();
Ok(factor * r.powi(l as i32) * y_lm)
}
pub fn solid_harmonic_irregular(
l: usize,
m: i32,
r: f64,
theta: f64,
phi: f64,
) -> SpecialResult<f64> {
if r <= 0.0 {
return Err(crate::SpecialError::DomainError(
"r must be > 0 for irregular solid harmonic".to_string(),
));
}
let y_lm: f64 = sph_harm(l, m, theta, phi)?;
let factor = (4.0 * PI / (2 * l + 1) as f64).sqrt();
Ok(factor / r.powi((l + 1) as i32) * y_lm)
}
pub fn sph_harm_cos_angle(theta1: f64, phi1: f64, theta2: f64, phi2: f64) -> f64 {
theta1.sin() * theta2.sin() * (phi1 - phi2).cos() + theta1.cos() * theta2.cos()
}
pub fn sph_harm_addition_theorem_check(
l: usize,
theta1: f64,
phi1: f64,
theta2: f64,
phi2: f64,
) -> SpecialResult<(f64, f64)> {
use crate::orthogonal::legendre;
let cos_gamma = sph_harm_cos_angle(theta1, phi1, theta2, phi2);
let p_l = legendre(l, cos_gamma);
let factor = 4.0 * PI / (2 * l + 1) as f64;
let mut sum = 0.0;
for m_i in -(l as i32)..=(l as i32) {
let y1: f64 = sph_harm(l, m_i, theta1, phi1)?;
let y2: f64 = sph_harm(l, m_i, theta2, phi2)?;
sum += y1 * y2;
}
Ok((p_l, factor * sum))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::f64::consts::{PI, SQRT_2};
#[test]
fn test_real_spherical_harmonics() {
let expected_y00 = 0.5 / f64::sqrt(PI);
assert_relative_eq!(
sph_harm(0, 0, 0.0, 0.0).expect("Operation failed"),
expected_y00,
epsilon = 1e-10
);
assert_relative_eq!(
sph_harm(0, 0, PI / 2.0, 0.0).expect("Operation failed"),
expected_y00,
epsilon = 1e-10
);
assert_relative_eq!(
sph_harm(0, 0, PI, 0.0).expect("Operation failed"),
expected_y00,
epsilon = 1e-10
);
let factor_y10 = f64::sqrt(3.0 / (4.0 * PI));
assert_relative_eq!(
sph_harm(1, 0, 0.0, 0.0).expect("Operation failed"),
factor_y10,
epsilon = 1e-10
); assert_relative_eq!(
sph_harm(1, 0, PI / 2.0, 0.0).expect("Operation failed"),
0.0,
epsilon = 1e-10
); assert_relative_eq!(
sph_harm(1, 0, PI, 0.0).expect("Operation failed"),
-factor_y10,
epsilon = 1e-10
);
let factor_y11 = f64::sqrt(3.0 / (8.0 * PI)) * SQRT_2;
assert_relative_eq!(
sph_harm(1, 1, PI / 2.0, 0.0).expect("Operation failed"),
factor_y11,
epsilon = 1e-10
);
assert_relative_eq!(
sph_harm(1, 1, PI / 2.0, PI / 2.0).expect("Operation failed"),
0.0,
epsilon = 1e-10
);
let factor_y20 = f64::sqrt(5.0 / (16.0 * PI));
assert_relative_eq!(
sph_harm(2, 0, 0.0, 0.0).expect("Operation failed"),
factor_y20 * 2.0,
epsilon = 1e-10
);
assert_relative_eq!(
sph_harm(1, 2, PI / 2.0, 0.0).expect("Operation failed"),
0.0,
epsilon = 1e-10
);
}
#[test]
fn test_complex_spherical_harmonics() {
let expected_y00 = 0.5 / f64::sqrt(PI);
let (re, im) = sph_harm_complex(0, 0, 0.0, 0.0).expect("Operation failed");
assert_relative_eq!(re, expected_y00, epsilon = 1e-10);
assert_relative_eq!(im, 0.0, epsilon = 1e-10);
let factor_y10 = f64::sqrt(3.0 / (4.0 * PI));
let (re, im) = sph_harm_complex(1, 0, 0.0, 0.0).expect("Operation failed");
assert_relative_eq!(re, factor_y10, epsilon = 1e-10);
assert_relative_eq!(im, 0.0, epsilon = 1e-10);
let factor_y11 = -f64::sqrt(3.0 / (8.0 * PI));
let (re, im) = sph_harm_complex(1, 1, PI / 2.0, 0.0).expect("Operation failed");
assert_relative_eq!(re, factor_y11, epsilon = 1e-10);
assert_relative_eq!(im, 0.0, epsilon = 1e-10);
let (re, im) = sph_harm_complex(1, 1, PI / 2.0, PI / 2.0).expect("Operation failed");
assert_relative_eq!(re, 0.0, epsilon = 1e-10);
assert_relative_eq!(im, factor_y11, epsilon = 1e-10);
let factor_y1_neg1 = f64::sqrt(3.0 / (8.0 * PI));
let (re, im) = sph_harm_complex(1, -1, PI / 2.0, 0.0).expect("Operation failed");
assert_relative_eq!(re, factor_y1_neg1, epsilon = 1e-10);
assert_relative_eq!(im, 0.0, epsilon = 1e-10);
let (re, im) = sph_harm_complex(1, -1, PI / 2.0, PI / 2.0).expect("Operation failed");
assert_relative_eq!(re, 0.0, epsilon = 1e-10);
assert_relative_eq!(im, -factor_y1_neg1, epsilon = 1e-10);
}
#[test]
fn test_sph_harm_orthogonality_same_l() {
let n_theta = 50;
let n_phi = 50;
let d_theta = PI / (n_theta as f64);
let d_phi = 2.0 * PI / (n_phi as f64);
let mut integral = 0.0;
for i in 0..n_theta {
let theta = (i as f64 + 0.5) * d_theta;
for j in 0..n_phi {
let phi = (j as f64 + 0.5) * d_phi;
let y10: f64 = sph_harm(1, 0, theta, phi).expect("failed");
let y11: f64 = sph_harm(1, 1, theta, phi).expect("failed");
integral += y10 * y11 * theta.sin() * d_theta * d_phi;
}
}
assert!(
integral.abs() < 0.05,
"Y_1^0 and Y_1^1 should be orthogonal, integral = {integral}"
);
}
#[test]
fn test_sph_harm_normalization_function() {
let k00 = sph_harm_normalization(0, 0);
assert_relative_eq!(k00, 0.5 / PI.sqrt(), epsilon = 1e-14);
let k10 = sph_harm_normalization(1, 0);
let expected = (3.0 / (4.0 * PI)).sqrt();
assert_relative_eq!(k10, expected, epsilon = 1e-14);
}
#[test]
fn test_sph_harm_normalization_zero_for_invalid_m() {
assert_relative_eq!(sph_harm_normalization(1, 3), 0.0, epsilon = 1e-14);
}
#[test]
fn test_sph_harm_cos_angle_same_direction() {
let cos_g = sph_harm_cos_angle(PI / 4.0, PI / 3.0, PI / 4.0, PI / 3.0);
assert_relative_eq!(cos_g, 1.0, epsilon = 1e-12);
}
#[test]
fn test_sph_harm_cos_angle_opposite() {
let cos_g = sph_harm_cos_angle(0.0, 0.0, PI, 0.0);
assert_relative_eq!(cos_g, -1.0, epsilon = 1e-12);
}
#[test]
fn test_sph_harm_cos_angle_perpendicular() {
let cos_g = sph_harm_cos_angle(0.0, 0.0, PI / 2.0, 0.0);
assert_relative_eq!(cos_g, 0.0, epsilon = 1e-12);
}
#[test]
fn test_solid_harmonic_regular_l0() {
let r = solid_harmonic_regular(0, 0, 1.0, PI / 4.0, 0.0).expect("failed");
assert_relative_eq!(r, 1.0, epsilon = 1e-10);
}
#[test]
fn test_solid_harmonic_regular_scales_with_r() {
let r1 = solid_harmonic_regular(2, 0, 1.0, PI / 4.0, 0.0).expect("failed");
let r2 = solid_harmonic_regular(2, 0, 2.0, PI / 4.0, 0.0).expect("failed");
assert_relative_eq!(r2, r1 * 4.0, epsilon = 1e-8);
}
#[test]
fn test_solid_harmonic_irregular_l0() {
let r = solid_harmonic_irregular(0, 0, 1.0, PI / 4.0, 0.0).expect("failed");
assert_relative_eq!(r, 1.0, epsilon = 1e-10);
}
#[test]
fn test_solid_harmonic_irregular_zero_r_error() {
assert!(solid_harmonic_irregular(0, 0, 0.0, 0.0, 0.0).is_err());
}
#[test]
fn test_solid_harmonic_irregular_scales_with_r() {
let r1 = solid_harmonic_irregular(1, 0, 1.0, PI / 4.0, 0.0).expect("failed");
let r2 = solid_harmonic_irregular(1, 0, 2.0, PI / 4.0, 0.0).expect("failed");
assert_relative_eq!(r2 / r1, 0.25, epsilon = 1e-8);
}
#[test]
fn test_addition_theorem_l0() {
let (p_val, sum_val) =
sph_harm_addition_theorem_check(0, PI / 4.0, 0.0, PI / 3.0, PI / 6.0).expect("failed");
assert_relative_eq!(p_val, 1.0, epsilon = 1e-10);
assert_relative_eq!(sum_val, 1.0, epsilon = 1e-8);
}
#[test]
fn test_addition_theorem_same_direction() {
let (p_val, sum_val) =
sph_harm_addition_theorem_check(2, PI / 4.0, PI / 3.0, PI / 4.0, PI / 3.0)
.expect("failed");
assert_relative_eq!(p_val, 1.0, epsilon = 1e-10);
assert_relative_eq!(sum_val, 1.0, epsilon = 1e-4);
}
#[test]
fn test_sph_harm_pi_theta_finite() {
let pairs = [
(1, 0),
(1, 1),
(2, 0),
(2, 1),
(2, 2),
(5, 3),
(10, 5),
(10, 10),
(20, 15),
(50, 50),
(100, 100),
(150, 150),
];
for (l, m) in pairs {
let val: f64 = sph_harm(l, m, PI, 0.0).expect("sph_harm failed");
assert!(
val.is_finite(),
"sph_harm({l},{m},PI,0) = {val} is not finite"
);
}
}
#[test]
fn test_sph_harm_large_l_eq_m_finite() {
let large_lm = [100, 130, 150, 151, 152, 200, 250, 500];
for l in large_lm {
let val: f64 = sph_harm(l, l as i32, PI / 2.0, 0.0).expect("sph_harm failed");
assert!(
val.is_finite(),
"sph_harm({l},{l},PI/2,0) = {val} is not finite"
);
}
}
#[test]
fn test_sph_harm_correctness_after_fix() {
let y00: f64 = sph_harm(0, 0, 1.0, 0.0).expect("failed");
assert_relative_eq!(y00, 0.5 / PI.sqrt(), epsilon = 1e-10);
let theta = PI / 4.0;
let y10: f64 = sph_harm(1, 0, theta, 0.0).expect("failed");
let expected_y10 = (3.0 / (4.0 * PI)).sqrt() * theta.cos();
assert_relative_eq!(y10, expected_y10, epsilon = 1e-10);
let y11: f64 = sph_harm(1, 1, PI / 2.0, 0.0).expect("failed");
let expected_y11 = SQRT_2 * (3.0 / (8.0 * PI)).sqrt() * 1.0 * 1.0;
assert_relative_eq!(y11, expected_y11, epsilon = 1e-10);
let y20: f64 = sph_harm(2, 0, 0.0, 0.0).expect("failed");
let expected_y20 = (5.0 / (16.0 * PI)).sqrt() * 2.0;
assert_relative_eq!(y20, expected_y20, epsilon = 1e-10);
}
}