use super::helpers::{
compute_legendre_assoc_derivative, legendre_associated_second_kind,
solve_spheroidal_eigenvalue_improved,
};
use crate::error::{SpecialError, SpecialResult};
#[allow(dead_code)]
fn pro_cv_continued_fraction(m: i32, n: i32, c: f64) -> SpecialResult<f64> {
let n_f64 = n as f64;
let m_f64 = m as f64;
let max_iter = 100;
let tolerance = 1e-14;
let mut lambda = n_f64 * (n_f64 + 1.0);
if c.abs() > 1e-10 {
let c2 = c.powi(2);
let n2 = n_f64.powi(2);
let m2 = m_f64.powi(2);
let correction1 = c2 / (2.0 * (2.0 * n_f64 + 3.0));
let correction2 = if n > m {
-c2 * m2 * (2.0 * n_f64 - 1.0)
/ (2.0 * (2.0 * n_f64 + 3.0) * (n_f64 - m_f64 + 1.0) * (n_f64 + m_f64 + 1.0))
} else {
0.0
};
let correction3 = c2.powi(2) * (3.0 * n2 + 6.0 * n_f64 + 2.0 - m2)
/ (8.0 * (2.0 * n_f64 + 3.0).powi(2) * (2.0 * n_f64 + 5.0));
lambda += correction1 + correction2 + correction3;
}
for iter in 0..max_iter {
let old_lambda = lambda;
let (det_val, det_prime) = compute_characteristic_determinant(m, n, c, lambda)?;
let step = -det_val / det_prime;
let damping = if iter < 10 { 0.8 } else { 1.0 }; lambda += damping * step;
if (lambda - old_lambda).abs() < tolerance {
break;
}
if lambda.is_nan() || lambda.is_infinite() {
return Err(SpecialError::ComputationError(
"Continued fraction iteration diverged".to_string(),
));
}
}
Ok(lambda)
}
fn compute_characteristic_determinant(
m: i32,
n: i32,
c: f64,
lambda: f64,
) -> SpecialResult<(f64, f64)> {
let _n_f64 = n as f64;
let m_f64 = m as f64;
let c2 = c.powi(2);
let matrixsize = 20.max(2 * (n as usize) + 10);
let mut matrix = vec![vec![0.0; matrixsize]; matrixsize];
let mut deriv_matrix = vec![vec![0.0; matrixsize]; matrixsize];
for i in 0..matrixsize {
let r = (i as i32 + m - n) * 2 + n; let r_f64 = r as f64;
let alpha_r = (r_f64 + m_f64) * (r_f64 + m_f64 + 1.0);
matrix[i][i] = alpha_r - lambda;
deriv_matrix[i][i] = -1.0;
if i + 2 < matrixsize {
let beta_r = c2 / (4.0 * (2.0 * r_f64 + 1.0) * (2.0 * r_f64 + 3.0));
matrix[i][i + 2] = beta_r;
matrix[i + 2][i] = beta_r;
}
}
let center = matrixsize / 2;
let window = 6.min(matrixsize / 2);
let mut det_val = 1.0;
let mut det_prime = 0.0;
for i in (center - window / 2)..(center + window / 2).min(matrixsize) {
if i < matrixsize {
det_val *= matrix[i][i];
det_prime += deriv_matrix[i][i] / matrix[i][i];
}
}
det_prime *= det_val;
let mut off_diag_correction = 0.0;
for i in 0..(matrixsize - 2) {
if matrix[i][i].abs() > 1e-10 && matrix[i + 2][i + 2].abs() > 1e-10 {
off_diag_correction += matrix[i][i + 2].powi(2) / (matrix[i][i] * matrix[i + 2][i + 2]);
}
}
det_val -= off_diag_correction;
Ok((det_val, det_prime))
}
#[allow(dead_code)]
fn pro_cv_asymptotic(m: i32, n: i32, c: f64) -> SpecialResult<f64> {
let n_f64 = n as f64;
let m_f64 = m as f64;
let leading_term = -c.powi(2) / 4.0;
let linear_term = (2.0 * n_f64 + 1.0) * c;
let constant_term = n_f64 * (n_f64 + 1.0) - m_f64.powi(2) / 2.0;
let correction = m_f64.powi(2) * (m_f64.powi(2) - 1.0) / (8.0 * c);
Ok(leading_term + linear_term + constant_term + correction)
}
#[allow(dead_code)]
pub fn pro_cv(m: i32, n: i32, c: f64) -> SpecialResult<f64> {
if m < 0 || n < m {
return Err(SpecialError::DomainError(
"Parameters must satisfy m ≥ 0 and n ≥ m".to_string(),
));
}
if n > 1000 {
return Err(SpecialError::DomainError(
"Parameter n is too large (> 1000), may cause numerical instability".to_string(),
));
}
if c.abs() > 1000.0 {
return Err(SpecialError::DomainError(
"Parameter c is too large (|c| > 1000), may cause numerical instability".to_string(),
));
}
if c.is_nan() {
return Ok(f64::NAN);
}
if c.is_infinite() {
let n_f64 = n as f64;
if c > 0.0 {
return Ok(-c.powi(2) / 4.0 + (2.0 * n_f64 + 1.0) * c);
} else {
return Ok(-c.powi(2) / 4.0 - (2.0 * n_f64 + 1.0) * c.abs());
}
}
if c == 0.0 {
return Ok(n as f64 * (n as f64 + 1.0));
}
if c.abs() < 1.0 {
let n_f64 = n as f64;
let m_f64 = m as f64;
let lambda_0 = n_f64 * (n_f64 + 1.0);
if n == m {
return Ok(lambda_0 + c.powi(2) / (2.0 * (2.0 * n_f64 + 3.0)));
}
let correction = c.powi(2) / (2.0 * (2.0 * n_f64 + 3.0))
* (1.0
- (m_f64.powi(2) * (2.0 * n_f64 - 1.0))
/ ((n_f64 - m_f64 + 1.0) * (n_f64 + m_f64 + 1.0)));
return Ok(lambda_0 + correction);
}
if c.abs() < 10.0 {
let result = pro_cv_continued_fraction(m, n, c)?;
if result.is_finite() && result.abs() < 1e6 {
return Ok(result);
} else {
return pro_cv_asymptotic(m, n, c);
}
}
let result = pro_cv_asymptotic(m, n, c)?;
if !result.is_finite() {
return Err(SpecialError::ComputationError(
"Asymptotic expansion produced non-finite result".to_string(),
));
}
Ok(result)
}
#[allow(dead_code)]
pub fn pro_cv_seq(m: i32, n: i32, c: f64) -> SpecialResult<Vec<f64>> {
if m < 0 || n < m {
return Err(SpecialError::DomainError(
"Parameters must satisfy m ≥ 0 and n ≥ m".to_string(),
));
}
if n - m > 199 {
return Err(SpecialError::DomainError(
"Difference between n and m is too large (max 199)".to_string(),
));
}
if c.is_nan() {
return Ok(vec![f64::NAN; (n - m + 1) as usize]);
}
let mut result = Vec::with_capacity((n - m + 1) as usize);
for degree in m..=n {
match pro_cv(m, degree, c) {
Ok(val) => result.push(val),
Err(e) => return Err(e),
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn pro_ang1(m: i32, n: i32, c: f64, x: f64) -> SpecialResult<(f64, f64)> {
if m < 0 || n < m {
return Err(SpecialError::DomainError(
"Parameters must satisfy m ≥ 0 and n ≥ m".to_string(),
));
}
if !(-1.0..=1.0).contains(&x) {
return Err(SpecialError::DomainError(
"Angular coordinate x must be in range [-1, 1]".to_string(),
));
}
if c.is_nan() || x.is_nan() {
return Ok((f64::NAN, f64::NAN));
}
if c == 0.0 {
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let h = 1e-8;
let x_plus = if x + h <= 1.0 { x + h } else { x - h };
let xminus = if x - h >= -1.0 { x - h } else { x + h };
let p_mn_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus);
let p_mnminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus);
let p_mn_prime = (p_mn_plus - p_mnminus) / (2.0 * h);
return Ok((p_mn, p_mn_prime));
}
if c.abs() < 1.0 {
return compute_prolate_angular_perturbation(m, n, c, x);
}
compute_prolate_angular_series(m, n, c, x)
}
#[allow(dead_code)]
pub fn pro_rad1(m: i32, n: i32, c: f64, x: f64) -> SpecialResult<(f64, f64)> {
if m < 0 || n < m {
return Err(SpecialError::DomainError(
"Parameters must satisfy m ≥ 0 and n ≥ m".to_string(),
));
}
if x < 1.0 {
return Err(SpecialError::DomainError(
"Radial coordinate x must be ≥ 1.0".to_string(),
));
}
if c.is_nan() || x.is_nan() {
return Ok((f64::NAN, f64::NAN));
}
if c == 0.0 {
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let h = 1e-8;
let x_plus = x + h;
let xminus = x - h;
let p_mn_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus);
let p_mnminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus);
let p_mn_prime = (p_mn_plus - p_mnminus) / (2.0 * h);
return Ok((p_mn, p_mn_prime));
}
if c.abs() < 1.0 {
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let correction = c.powi(2) * xi * p_mn / (4.0 * lambda.abs().sqrt());
let perturbed_value = p_mn + correction;
let h = 1e-8;
let x_plus = x + h;
let xminus = x - h;
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus);
let xi_plus = (x_plus.powi(2) - 1.0).sqrt();
let ximinus = (xminus.powi(2) - 1.0).sqrt();
let correction_plus = c.powi(2) * xi_plus * p_plus / (4.0 * lambda.abs().sqrt());
let correctionminus = c.powi(2) * ximinus * pminus / (4.0 * lambda.abs().sqrt());
let perturbed_derivative =
((p_plus + correction_plus) - (pminus + correctionminus)) / (2.0 * h);
return Ok((perturbed_value, perturbed_derivative));
}
if c.abs() < 10.0 {
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let normalization = (2.0 / (std::f64::consts::PI * lambda.abs())).sqrt();
let exponential_part = (c * xi).exp();
let power_part = xi.powf(-0.5);
let leading_coefficient = if m == 0 { 1.0 } else { xi.powi(m) };
let radial_value = normalization * exponential_part * power_part * leading_coefficient;
let h = 1e-8;
let x_plus = x + h;
let xi_plus = (x_plus.powi(2) - 1.0).sqrt();
let exp_plus = (c * xi_plus).exp();
let power_plus = xi_plus.powf(-0.5);
let coeff_plus = if m == 0 { 1.0 } else { xi_plus.powi(m) };
let radial_plus = normalization * exp_plus * power_plus * coeff_plus;
let radial_derivative = (radial_plus - radial_value) / h;
return Ok((radial_value, radial_derivative));
}
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let phase = c * xi + lambda * xi.ln();
let amplitude = (2.0 / (std::f64::consts::PI * c * xi)).sqrt();
let radial_value = amplitude * phase.cos();
let phase_derivative = c + lambda / xi;
let amplitude_derivative = -amplitude / (2.0 * xi);
let radial_derivative =
amplitude_derivative * phase.cos() - amplitude * phase.sin() * phase_derivative;
Ok((radial_value, radial_derivative))
}
#[allow(dead_code)]
pub fn pro_rad2(m: i32, n: i32, c: f64, x: f64) -> SpecialResult<(f64, f64)> {
if m < 0 || n < m {
return Err(SpecialError::DomainError(
"Parameters must satisfy m ≥ 0 and n ≥ m".to_string(),
));
}
if x < 1.0 {
return Err(SpecialError::DomainError(
"Radial coordinate x must be ≥ 1.0".to_string(),
));
}
if c.is_nan() || x.is_nan() {
return Ok((f64::NAN, f64::NAN));
}
if c == 0.0 {
let (q_mn, q_mn_prime) = legendre_associated_second_kind(n, m, x)?;
return Ok((q_mn, q_mn_prime));
}
if c.abs() < 1.0 {
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let q_mn = p_mn * (x + 1.0).ln() / (x - 1.0).ln();
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let correction = c.powi(2) * xi * q_mn / (4.0 * lambda.abs().sqrt());
let perturbed_value = q_mn + correction;
let h = 1e-8;
let x_plus = x + h;
let xminus = x - h;
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus);
let q_plus = p_plus * (x_plus + 1.0).ln() / (x_plus - 1.0).ln();
let qminus = pminus * (xminus + 1.0).ln() / (xminus - 1.0).ln();
let xi_plus = (x_plus.powi(2) - 1.0).sqrt();
let ximinus = (xminus.powi(2) - 1.0).sqrt();
let correction_plus = c.powi(2) * xi_plus * q_plus / (4.0 * lambda.abs().sqrt());
let correctionminus = c.powi(2) * ximinus * qminus / (4.0 * lambda.abs().sqrt());
let perturbed_derivative =
((q_plus + correction_plus) - (qminus + correctionminus)) / (2.0 * h);
return Ok((perturbed_value, perturbed_derivative));
}
if c.abs() < 10.0 {
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let normalization = (2.0 / (std::f64::consts::PI * lambda.abs())).sqrt();
let exponential_part = (-c * xi).exp(); let power_part = xi.powf(-0.5);
let leading_coefficient = if m == 0 { 1.0 } else { xi.powi(m) };
let radial_value = normalization * exponential_part * power_part * leading_coefficient;
let h = 1e-8;
let x_plus = x + h;
let xi_plus = (x_plus.powi(2) - 1.0).sqrt();
let exp_plus = (-c * xi_plus).exp();
let power_plus = xi_plus.powf(-0.5);
let coeff_plus = if m == 0 { 1.0 } else { xi_plus.powi(m) };
let radial_plus = normalization * exp_plus * power_plus * coeff_plus;
let radial_derivative = (radial_plus - radial_value) / h;
return Ok((radial_value, radial_derivative));
}
let lambda = pro_cv(m, n, c)?;
let xi = (x.powi(2) - 1.0).sqrt();
let phase = -c * xi + lambda * xi.ln();
let amplitude = (2.0 / (std::f64::consts::PI * c * xi)).sqrt();
let radial_value = amplitude * phase.sin();
let phase_derivative = -c + lambda / xi;
let amplitude_derivative = -amplitude / (2.0 * xi);
let radial_derivative =
amplitude_derivative * phase.sin() + amplitude * phase.cos() * phase_derivative;
Ok((radial_value, radial_derivative))
}
#[allow(dead_code)]
fn compute_prolate_angular_perturbation(
m: i32,
n: i32,
c: f64,
x: f64,
) -> SpecialResult<(f64, f64)> {
let n_f64 = n as f64;
let m_f64 = m as f64;
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let p_mn_prime = compute_legendre_assoc_derivative(n as usize, m, x);
let c2 = c.powi(2);
let c4 = c.powi(4);
let lambda0 = n_f64 * (n_f64 + 1.0);
let correction2_coeff = 1.0 / (4.0 * lambda0);
let correction2 = c2 * correction2_coeff * x * p_mn;
let correction2_prime = c2 * correction2_coeff * (p_mn + x * p_mn_prime);
let correction4_coeff = (3.0 * x.powi(2) - 1.0) / (32.0 * lambda0.powi(2));
let correction4 = c4 * correction4_coeff * p_mn;
let correction4_prime =
c4 * correction4_coeff * p_mn_prime + c4 * (6.0 * x) / (32.0 * lambda0.powi(2)) * p_mn;
let mut cross_correction = 0.0;
let mut cross_correction_prime = 0.0;
if m > 0 && n > m {
if n >= 2 {
let p_nminus_2 = crate::orthogonal::legendre_assoc((n - 2) as usize, m, x);
let p_nminus_2_prime = compute_legendre_assoc_derivative((n - 2) as usize, m, x);
let coupling_coeff =
m_f64 * (m_f64 + 1.0) / (4.0 * (2.0 * n_f64 - 1.0) * (2.0 * n_f64 + 1.0));
cross_correction += c2 * coupling_coeff * p_nminus_2;
cross_correction_prime += c2 * coupling_coeff * p_nminus_2_prime;
}
let p_n_plus_2 = crate::orthogonal::legendre_assoc((n + 2) as usize, m, x);
let p_n_plus_2_prime = compute_legendre_assoc_derivative((n + 2) as usize, m, x);
let coupling_coeff =
(n_f64 + 1.0) * (n_f64 + 2.0) / (4.0 * (2.0 * n_f64 + 1.0) * (2.0 * n_f64 + 3.0));
cross_correction += c2 * coupling_coeff * p_n_plus_2;
cross_correction_prime += c2 * coupling_coeff * p_n_plus_2_prime;
}
let perturbed_value = p_mn + correction2 + correction4 + cross_correction;
let perturbed_derivative =
p_mn_prime + correction2_prime + correction4_prime + cross_correction_prime;
Ok((perturbed_value, perturbed_derivative))
}
#[allow(dead_code)]
fn compute_prolate_angular_series(m: i32, n: i32, c: f64, x: f64) -> SpecialResult<(f64, f64)> {
let lambda = pro_cv(m, n, c)?;
let max_terms = 50.min(2 * n as usize + 20);
let mut coefficients = vec![0.0; max_terms];
let improved_lambda = solve_spheroidal_eigenvalue_improved(m, n, c).unwrap_or(lambda);
coefficients[n as usize] = 1.0;
for k in (0..n as usize).rev() {
if k + 2 < max_terms {
let k_f64 = k as f64;
let m_f64 = m as f64;
let alpha_k = (k_f64 + m_f64) * (k_f64 + m_f64 + 1.0);
let beta_k = c.powi(2) / (4.0 * (2.0 * k_f64 + 1.0) * (2.0 * k_f64 + 3.0));
if alpha_k - improved_lambda != 0.0 {
coefficients[k] = -beta_k * coefficients[k + 2] / (alpha_k - improved_lambda);
}
}
}
for k in (n as usize + 1)..max_terms {
if k >= 2 {
let k_f64 = k as f64;
let m_f64 = m as f64;
let alpha_k = (k_f64 + m_f64) * (k_f64 + m_f64 + 1.0);
let beta_kminus_2 =
c.powi(2) / (4.0 * (2.0 * (k_f64 - 2.0) + 1.0) * (2.0 * (k_f64 - 2.0) + 3.0));
if alpha_k - improved_lambda != 0.0 {
coefficients[k] =
-beta_kminus_2 * coefficients[k - 2] / (alpha_k - improved_lambda);
}
}
}
let mut sum = 0.0;
let mut sum_prime = 0.0;
for (k, &coeff) in coefficients.iter().enumerate() {
if coeff.abs() > 1e-15 {
let p_k = crate::orthogonal::legendre_assoc(k, m, x);
let p_k_prime = compute_legendre_assoc_derivative(k, m, x);
sum += coeff * p_k;
sum_prime += coeff * p_k_prime;
}
}
Ok((sum, sum_prime))
}