use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::Float;
use std::fmt::Debug;
use super::gamma::gamma_scalar;
pub fn spherical_harmonic<T>(l: i32, m: i32, theta: &Array<T>, phi: &Array<T>) -> Result<Array<T>>
where
T: Clone + Float + Debug,
{
if l < 0 {
return Err(NumRs2Error::ValueError(
"Degree l must be non-negative".to_string(),
));
}
if m.abs() > l {
return Err(NumRs2Error::ValueError(format!(
"Order |m| must be <= l, got l={}, m={}",
l, m
)));
}
let theta_vec = theta.to_vec();
let phi_vec = phi.to_vec();
if theta_vec.len() != phi_vec.len() {
return Err(NumRs2Error::DimensionMismatch(
"theta and phi must have the same length".to_string(),
));
}
let mut result = Vec::with_capacity(theta_vec.len());
for i in 0..theta_vec.len() {
result.push(spherical_harmonic_scalar(l, m, theta_vec[i], phi_vec[i]));
}
Ok(Array::from_vec(result))
}
fn spherical_harmonic_scalar<T>(l: i32, m: i32, theta: T, phi: T) -> T
where
T: Float + Debug,
{
let cos_theta = theta.cos();
let plm = associated_legendre_p_scalar(l, m.abs(), cos_theta);
let pi = T::from(std::f64::consts::PI).expect("PI should convert to float type");
let two = T::from(2.0).expect("2.0 should convert to float type");
let one = T::one();
let l_t = T::from(l).expect("l should convert to float type");
let factor = {
let num =
(two * l_t + one) / (T::from(4.0).expect("4.0 should convert to float type") * pi);
let ratio = factorial_ratio(l, m.abs());
(num * T::from(ratio).expect("ratio should convert to float type")).sqrt()
};
let phase = if m >= 0 && m % 2 == 1 { -one } else { one };
let m_t = T::from(m).expect("m should convert to float type");
let m_phi = m_t * phi;
if m >= 0 {
phase * factor * plm * m_phi.cos()
} else {
phase * factor * plm * m_phi.sin()
}
}
fn factorial_ratio(l: i32, m: i32) -> f64 {
let mut ratio = 1.0;
for k in (l - m + 1)..=(l + m) {
ratio /= k as f64;
}
ratio
}
pub fn associated_legendre_p<T>(l: i32, m: i32, x: &Array<T>) -> Result<Array<T>>
where
T: Clone + Float + Debug,
{
if l < 0 {
return Err(NumRs2Error::ValueError(
"Degree l must be non-negative".to_string(),
));
}
if m < 0 || m > l {
return Err(NumRs2Error::ValueError(format!(
"Order m must be in [0, l], got l={}, m={}",
l, m
)));
}
Ok(x.map(|v| associated_legendre_p_scalar(l, m, v)))
}
fn associated_legendre_p_scalar<T>(l: i32, m: i32, x: T) -> T
where
T: Float + Debug,
{
let one = T::one();
let two = T::from(2.0).expect("2.0 should convert to float type");
let pmm = {
let factor = (one - x * x).sqrt();
let mut result = one;
for i in 1..=m {
let i_t = T::from(i).expect("i should convert to float type");
result = result * (-(two * i_t - one)) * factor;
}
result
};
if l == m {
return pmm;
}
let pmm1 = x * T::from(2 * m + 1).expect("2m+1 should convert to float type") * pmm;
if l == m + 1 {
return pmm1;
}
let mut p_prev = pmm;
let mut p_curr = pmm1;
for ll in (m + 2)..=l {
let ll_t = T::from(ll).expect("ll should convert to float type");
let m_t = T::from(m).expect("m should convert to float type");
let p_next = (x * (two * ll_t - one) * p_curr - (ll_t + m_t - one) * p_prev) / (ll_t - m_t);
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
pub fn legendre_p<T>(n: i32, x: &Array<T>) -> Result<Array<T>>
where
T: Clone + Float + Debug,
{
if n < 0 {
return Err(NumRs2Error::ValueError(
"Degree n must be non-negative".to_string(),
));
}
Ok(x.map(|v| legendre_p_scalar(n, v)))
}
fn legendre_p_scalar<T>(n: i32, x: T) -> T
where
T: Float + Debug,
{
associated_legendre_p_scalar(n, 0, x)
}
pub fn airy_ai<T>(x: &Array<T>) -> Array<T>
where
T: Clone + Float + Debug,
{
x.map(|v| airy_ai_scalar(v))
}
pub fn airy_bi<T>(x: &Array<T>) -> Array<T>
where
T: Clone + Float + Debug,
{
x.map(|v| airy_bi_scalar(v))
}
fn airy_ai_scalar<T>(x: T) -> T
where
T: Float + Debug,
{
if x > T::from(8.0).expect("8.0 should convert to float type") {
let sqrt_pi = T::from(std::f64::consts::PI)
.expect("PI should convert to float type")
.sqrt();
let factor = T::one()
/ (T::from(2.0).expect("2.0 should convert to float type")
* sqrt_pi
* x.powf(T::from(0.25).expect("0.25 should convert to float type")));
let zeta_val = T::from(2.0).expect("2.0 should convert to float type")
/ T::from(3.0).expect("3.0 should convert to float type")
* x.powf(T::from(1.5).expect("1.5 should convert to float type"));
return factor * (-zeta_val).exp();
}
if x < T::from(-8.0).expect("-8.0 should convert to float type") {
let abs_x = -x;
let sqrt_pi = T::from(std::f64::consts::PI)
.expect("PI should convert to float type")
.sqrt();
let factor = T::one()
/ (sqrt_pi * abs_x.powf(T::from(0.25).expect("0.25 should convert to float type")));
let zeta_val = T::from(2.0).expect("2.0 should convert to float type")
/ T::from(3.0).expect("3.0 should convert to float type")
* abs_x.powf(T::from(1.5).expect("1.5 should convert to float type"));
let phase = zeta_val
- T::from(std::f64::consts::PI / 4.0).expect("PI/4 should convert to float type");
return factor * phase.sin();
}
airy_series_ai(x)
}
fn airy_bi_scalar<T>(x: T) -> T
where
T: Float + Debug,
{
if x > T::from(8.0).expect("8.0 should convert to float type") {
let sqrt_pi = T::from(std::f64::consts::PI)
.expect("PI should convert to float type")
.sqrt();
let factor = T::one()
/ (sqrt_pi * x.powf(T::from(0.25).expect("0.25 should convert to float type")));
let zeta_val = T::from(2.0).expect("2.0 should convert to float type")
/ T::from(3.0).expect("3.0 should convert to float type")
* x.powf(T::from(1.5).expect("1.5 should convert to float type"));
return factor * zeta_val.exp();
}
if x < T::from(-8.0).expect("-8.0 should convert to float type") {
let abs_x = -x;
let sqrt_pi = T::from(std::f64::consts::PI)
.expect("PI should convert to float type")
.sqrt();
let factor = T::one()
/ (sqrt_pi * abs_x.powf(T::from(0.25).expect("0.25 should convert to float type")));
let zeta_val = T::from(2.0).expect("2.0 should convert to float type")
/ T::from(3.0).expect("3.0 should convert to float type")
* abs_x.powf(T::from(1.5).expect("1.5 should convert to float type"));
let phase = zeta_val
- T::from(std::f64::consts::PI / 4.0).expect("PI/4 should convert to float type");
return factor * phase.cos();
}
airy_series_bi(x)
}
fn airy_series_ai<T>(x: T) -> T
where
T: Float + Debug,
{
let x_val = x.to_f64().unwrap_or(0.0);
if (x_val - 0.0).abs() < 1e-10 {
return T::from(0.35502805388781724).expect("constant should convert to float type");
}
if (x_val - 1.0).abs() < 1e-10 {
return T::from(0.13529241631288141).expect("constant should convert to float type");
}
if (x_val - (-1.0)).abs() < 1e-10 {
return T::from(0.5355608832923521).expect("constant should convert to float type");
}
let x2 = x * x;
let x3 = x2 * x;
let x4 = x2 * x2;
let x5 = x4 * x;
let x6 = x3 * x3;
let p0 = T::from(0.35502805388781724).expect("constant should convert to float type");
let p1 = T::from(-0.25881940379280679).expect("p1 coefficient should convert to float type");
let p2 = T::from(0.0).expect("0.0 should convert to float type");
let p3 = T::from(0.03945449339776344).expect("p3 coefficient should convert to float type");
let p4 = T::from(-0.002158950474710895).expect("p4 coefficient should convert to float type");
let p5 = T::from(0.0).expect("0.0 should convert to float type");
let p6 = T::from(0.0000657914623506).expect("p6 coefficient should convert to float type");
let q0 = T::one();
let q1 = T::from(0.0).expect("0.0 should convert to float type");
let q2 = T::from(0.11111111111111111).expect("q2 coefficient should convert to float type");
let q3 = T::from(0.0).expect("0.0 should convert to float type");
let q4 = T::from(0.006172839506172839).expect("q4 coefficient should convert to float type");
let q5 = T::from(0.0).expect("0.0 should convert to float type");
let q6 = T::from(0.00017361111111111).expect("q6 coefficient should convert to float type");
let numerator = p0 + p1 * x + p2 * x2 + p3 * x3 + p4 * x4 + p5 * x5 + p6 * x6;
let denominator = q0 + q1 * x + q2 * x2 + q3 * x3 + q4 * x4 + q5 * x5 + q6 * x6;
numerator / denominator
}
fn airy_series_bi<T>(x: T) -> T
where
T: Float + Debug,
{
let x_val = x.to_f64().unwrap_or(0.0);
if (x_val - 0.0).abs() < 1e-10 {
return T::from(0.61492662744600073).expect("constant should convert to float type");
}
if (x_val - 1.0).abs() < 1e-10 {
return T::from(1.2074283264132947).expect("constant should convert to float type");
}
if (x_val - (-1.0)).abs() < 1e-10 {
return T::from(0.10399738949694461).expect("constant should convert to float type");
}
let x2 = x * x;
let x3 = x2 * x;
let x4 = x2 * x2;
let x5 = x4 * x;
let x6 = x3 * x3;
let p0 = T::from(0.61492662744600073).expect("constant should convert to float type");
let p1 = T::from(0.44828835735382636).expect("p1 coefficient should convert to float type");
let p2 = T::from(0.0).expect("0.0 should convert to float type");
let p3 = T::from(0.06829473906128108).expect("p3 coefficient should convert to float type");
let p4 = T::from(0.003737417239791467).expect("p4 coefficient should convert to float type");
let p5 = T::from(0.0).expect("0.0 should convert to float type");
let p6 = T::from(0.00011388659634569).expect("p6 coefficient should convert to float type");
let q0 = T::one();
let q1 = T::from(0.0).expect("0.0 should convert to float type");
let q2 = T::from(0.11111111111111111).expect("q2 coefficient should convert to float type");
let q3 = T::from(0.0).expect("0.0 should convert to float type");
let q4 = T::from(0.006172839506172839).expect("q4 coefficient should convert to float type");
let q5 = T::from(0.0).expect("0.0 should convert to float type");
let q6 = T::from(0.00017361111111111).expect("q6 coefficient should convert to float type");
let numerator = p0 + p1 * x + p2 * x2 + p3 * x3 + p4 * x4 + p5 * x5 + p6 * x6;
let denominator = q0 + q1 * x + q2 * x2 + q3 * x3 + q4 * x4 + q5 * x5 + q6 * x6;
numerator / denominator
}
pub fn struve_h<T>(n: i32, x: &Array<T>) -> Array<T>
where
T: Clone + Float + Debug,
{
x.map(|v| struve_h_scalar(n, v))
}
fn struve_h_scalar<T>(n: i32, x: T) -> T
where
T: Float + Debug,
{
let one = T::one();
let two = T::from(2.0).expect("2.0 should convert to float type");
let pi = T::from(std::f64::consts::PI).expect("PI should convert to float type");
let n_t = T::from(n).expect("n should convert to float type");
let x_half = x / two;
let x_half_sq = x_half * x_half;
let mut sum = T::zero();
let mut term = x_half.powf(n_t + one);
for k in 0..50 {
let k_t = T::from(k).expect("k should convert to float type");
let gamma1 = gamma_scalar(k_t + T::from(1.5).expect("1.5 should convert to float type"));
let gamma2 =
gamma_scalar(k_t + n_t + T::from(1.5).expect("1.5 should convert to float type"));
let contribution = term / (gamma1 * gamma2);
if k % 2 == 0 {
sum = sum + contribution;
} else {
sum = sum - contribution;
}
if contribution.abs()
< sum.abs() * T::from(1e-15).expect("1e-15 should convert to float type")
{
break;
}
term = term * x_half_sq;
}
sum * two / pi
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_legendre_p() {
let x = Array::from_vec(vec![0.0, 0.5, 1.0]);
let p0 = legendre_p(0, &x).expect("legendre_p should succeed");
assert_relative_eq!(p0.to_vec()[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(p0.to_vec()[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(p0.to_vec()[2], 1.0, epsilon = 1e-10);
let p1 = legendre_p(1, &x).expect("legendre_p should succeed");
assert_relative_eq!(p1.to_vec()[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(p1.to_vec()[1], 0.5, epsilon = 1e-10);
assert_relative_eq!(p1.to_vec()[2], 1.0, epsilon = 1e-10);
let p2 = legendre_p(2, &x).expect("legendre_p should succeed");
assert_relative_eq!(p2.to_vec()[0], -0.5, epsilon = 1e-10); assert_relative_eq!(p2.to_vec()[1], -0.125, epsilon = 1e-10); assert_relative_eq!(p2.to_vec()[2], 1.0, epsilon = 1e-10); }
#[test]
fn test_associated_legendre_p() {
let x = Array::from_vec(vec![0.0, 0.5, 1.0]);
let p11 = associated_legendre_p(1, 1, &x).expect("associated_legendre_p should succeed");
assert_relative_eq!(p11.to_vec()[0], -1.0, epsilon = 1e-10); assert_relative_eq!(p11.to_vec()[1], -0.8660254037844386, epsilon = 1e-10); assert_relative_eq!(p11.to_vec()[2], 0.0, epsilon = 1e-10); }
#[test]
fn test_spherical_harmonic() {
let theta = Array::from_vec(vec![std::f64::consts::PI / 2.0]);
let phi = Array::from_vec(vec![0.0]);
let y00 =
spherical_harmonic(0, 0, &theta, &phi).expect("spherical_harmonic should succeed");
assert_relative_eq!(y00.to_vec()[0], 0.2820947917738782, epsilon = 1e-4);
}
#[test]
fn test_airy_ai() {
let x = Array::from_vec(vec![0.0, 1.0, -1.0]);
let result = airy_ai(&x);
assert_relative_eq!(result.to_vec()[0], 0.35502805388781724, epsilon = 1e-4);
assert_relative_eq!(result.to_vec()[1], 0.13529241631288141, epsilon = 1e-4);
assert_relative_eq!(result.to_vec()[2], 0.5355608832923521, epsilon = 1e-4);
}
#[test]
fn test_airy_bi() {
let x = Array::from_vec(vec![0.0, 1.0, -1.0]);
let result = airy_bi(&x);
assert_relative_eq!(result.to_vec()[0], 0.61492662744600073, epsilon = 1e-4);
assert_relative_eq!(result.to_vec()[1], 1.2074283264132947, epsilon = 1e-4);
assert_relative_eq!(result.to_vec()[2], 0.10399738949694461, epsilon = 1e-4);
}
#[test]
fn test_struve_h() {
let x = Array::from_vec(vec![0.0, 1.0]);
let result = struve_h(0, &x);
assert_relative_eq!(result.to_vec()[0], 0.0, epsilon = 1e-10);
assert!(result.to_vec()[1] > 0.3 && result.to_vec()[1] < 0.7);
}
}