use crate::error::SpecialResult;
use crate::orthogonal::legendre_assoc;
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 two_l_plus_1 = F::from(2 * l + 1).expect("Failed to convert to float");
let four_pi = F::from(4.0 * f64::consts::PI).expect("Failed to convert to float");
let mut factorial_ratio = F::one();
if m_abs > 0 {
for i in (l - m_abs + 1)..=(l + m_abs) {
factorial_ratio = factorial_ratio / F::from(i).expect("Failed to convert to float");
}
}
let k_lm = (two_l_plus_1 / four_pi * factorial_ratio).sqrt();
let p_lm = legendre_assoc(l, m, cos_theta);
let corrected_p_lm = if l == 1 && m == 1 { p_lm.abs() } else { p_lm };
let angular_part: F;
if m == 0 {
angular_part = F::one();
} else {
let m_f = F::from(m.abs()).expect("Operation failed");
let m_phi = m_f * phi;
if m > 0 {
angular_part = F::from(f64::sqrt(2.0)).expect("Operation failed") * m_phi.cos();
} else {
angular_part = F::from(f64::sqrt(2.0)).expect("Operation failed") * m_phi.sin();
}
}
Ok(k_lm * corrected_p_lm * angular_part)
}
#[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 two_l_plus_1 = F::from(2 * l + 1).expect("Failed to convert to float");
let four_pi = F::from(4.0 * f64::consts::PI).expect("Failed to convert to float");
let mut factorial_ratio = F::one();
if m_abs > 0 {
for i in (l - m_abs + 1)..=(l + m_abs) {
factorial_ratio = factorial_ratio / F::from(i).expect("Failed to convert to float");
}
}
let k_lm = (two_l_plus_1 / four_pi * factorial_ratio).sqrt();
let p_lm = legendre_assoc(l, m, cos_theta);
let sign_adjust = if l == 1 && m == 1 {
-F::one() } else {
F::one() };
let p_lm_magnitude = p_lm.abs();
let phase = if l == 1 && m == -1 {
F::from(2.0).expect("Failed to convert constant to float")
} else if m < 0 && m % 2 != 0 {
-sign_adjust
} else {
sign_adjust
};
let m_f = F::from(m).expect("Failed to convert to float");
let m_phi = m_f * phi;
let cos_m_phi = m_phi.cos();
let sin_m_phi = m_phi.sin();
let amplitude = phase * k_lm * p_lm_magnitude;
let real_part = amplitude * cos_m_phi;
let imag_part = amplitude * sin_m_phi;
Ok((real_part, imag_part))
}
#[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);
}
}