use crate::error::{SpecialError, SpecialResult};
pub fn compute_legendre_assoc_derivative(n: usize, m: i32, x: f64) -> f64 {
if n == 0 {
return 0.0;
}
if x.abs() >= 1.0 - 1e-10 {
if m == 0 {
let sign = if x > 0.0 {
if n.is_multiple_of(2) {
1.0
} else {
-1.0
}
} else if n.is_multiple_of(2) {
-1.0
} else {
1.0
};
return (n as f64) * (n as f64 + 1.0) / 2.0 * sign;
} else if m == 1 {
let sign = if x > 0.0 { 1.0 } else { -1.0 };
return sign * (n as f64) * (n as f64 + 1.0) * (n as f64) * (n as f64 - 1.0) / 4.0;
} else {
return 0.0;
}
}
let p_n = crate::orthogonal::legendre_assoc(n, m, x);
if n == 0 {
return 0.0;
}
let p_nminus_1 = crate::orthogonal::legendre_assoc(n - 1, m, x);
let n_f64 = n as f64;
let m_f64 = m as f64;
let numerator = n_f64 * x * p_n - (n_f64 + m_f64) * p_nminus_1;
let denominator = x * x - 1.0;
if denominator.abs() < 1e-10 {
return n_f64 * (n_f64 + 1.0) / 2.0 * x.signum();
}
numerator / denominator
}
#[allow(dead_code)]
pub fn legendre_associated_second_kind(n: i32, m: i32, x: f64) -> SpecialResult<(f64, f64)> {
if n < 0 || m.abs() > n {
return Err(SpecialError::DomainError(format!(
"Invalid parameters: n={n}, m={m}, must have n≥0 and |m|≤n"
)));
}
if x.abs() <= 1.0 {
return Err(SpecialError::DomainError(format!(
"Argument x={x} must satisfy |x| > 1"
)));
}
if n == 0 && m == 0 {
let q_00 = 0.5 * ((x + 1.0) / (x - 1.0)).ln();
let q_00_prime = -1.0 / (x * x - 1.0);
return Ok((q_00, q_00_prime));
}
if m == 0 {
legendre_second_kind_m0(n, x)
} else {
legendre_second_kind_general(n, m, x)
}
}
#[allow(dead_code)]
fn legendre_second_kind_m0(n: i32, x: f64) -> SpecialResult<(f64, f64)> {
if n == 0 {
let q_0 = 0.5 * ((x + 1.0) / (x - 1.0)).ln();
let q_0_prime = -1.0 / (x * x - 1.0);
return Ok((q_0, q_0_prime));
}
if n == 1 {
let q_0 = 0.5 * ((x + 1.0) / (x - 1.0)).ln();
let q_1 = x * q_0 - 1.0;
let q_1_prime = q_0 + x / (x * x - 1.0);
return Ok((q_1, q_1_prime));
}
let mut q_prev = 0.5 * ((x + 1.0) / (x - 1.0)).ln(); let mut q_curr = x * q_prev - 1.0;
for k in 2..=n {
let k_f = k as f64;
let q_next = ((2.0 * k_f - 1.0) * x * q_curr - (k_f - 1.0) * q_prev) / k_f;
q_prev = q_curr;
q_curr = q_next;
}
let q_n_prime = if n == 1 {
let q_0 = 0.5 * ((x + 1.0) / (x - 1.0)).ln();
q_0 + x / (x * x - 1.0)
} else {
let n_f = n as f64;
n_f * (x * q_curr - q_prev) / (x * x - 1.0)
};
Ok((q_curr, q_n_prime))
}
#[allow(dead_code)]
fn legendre_second_kind_general(n: i32, m: i32, x: f64) -> SpecialResult<(f64, f64)> {
let m_abs = m.abs();
if m_abs == 0 {
return legendre_second_kind_m0(n, x);
}
let (mut q_nm, mut q_nm_prime) = legendre_second_kind_m0(n, x)?;
let sqrt_x2minus_1 = (x * x - 1.0).sqrt();
for k in 1..=m_abs {
let k_f = k as f64;
let x2minus_1 = x * x - 1.0;
let new_q_nm = sqrt_x2minus_1 * (k_f * x / x2minus_1 * q_nm + q_nm_prime);
let new_q_nm_prime = x / sqrt_x2minus_1 * (k_f * x / x2minus_1 * q_nm + q_nm_prime)
+ sqrt_x2minus_1
* (
k_f / x2minus_1 * q_nm + k_f * x / x2minus_1 * q_nm_prime
- 2.0 * k_f * x * x / (x2minus_1 * x2minus_1) * q_nm
+ q_nm_prime
);
q_nm = new_q_nm;
q_nm_prime = new_q_nm_prime;
}
if m < 0 {
let sign = if m_abs % 2 == 0 { 1.0 } else { -1.0 };
use crate::combinatorial::factorial;
let factor = sign * factorial((n - m_abs) as u32).unwrap_or(1.0)
/ factorial((n + m_abs) as u32).unwrap_or(1.0);
q_nm *= factor;
q_nm_prime *= factor;
}
Ok((q_nm, q_nm_prime))
}
#[allow(dead_code)]
pub fn solve_spheroidal_eigenvalue_improved(m: i32, n: i32, c: f64) -> SpecialResult<f64> {
if c.abs() < 0.1 {
return solve_eigenvalue_perturbation(m, n, c);
}
let matrixsize = (4 * n.abs() + 20).min(100) as usize;
solve_eigenvalue_matrix_method(m, n, c, matrixsize)
}
#[allow(dead_code)]
fn solve_eigenvalue_perturbation(m: i32, n: i32, c: f64) -> SpecialResult<f64> {
let _m_f = m as f64;
let n_f = n as f64;
let lambda_0 = n_f * (n_f + 1.0);
let lambda_1 = if n == 0 && m == 0 {
c * c / 2.0
} else {
let factor = if n % 2 == m % 2 { 1.0 } else { -1.0 };
factor * c * c / (2.0 * (2.0 * n_f + 1.0))
};
let lambda_2 =
-c.powi(4) / (8.0 * (2.0 * n_f + 1.0) * (2.0 * n_f + 3.0) * (2.0 * n_f - 1.0).max(1.0));
Ok(lambda_0 + lambda_1 + lambda_2)
}
#[allow(dead_code)]
fn solve_eigenvalue_matrix_method(m: i32, n: i32, c: f64, matrixsize: usize) -> SpecialResult<f64> {
let m_f = m as f64;
let c2 = c * c;
let mut main_diag = vec![0.0; matrixsize];
let mut upper_diag = vec![0.0; matrixsize - 1];
let mut lower_diag = vec![0.0; matrixsize - 1];
for i in 0..matrixsize {
let r = (i as i32 + m - n).abs();
let r_f = r as f64;
main_diag[i] = (r_f + m_f) * (r_f + m_f + 1.0);
if i < matrixsize - 1 {
let beta = c2 / (4.0 * (2.0 * r_f + 1.0) * (2.0 * r_f + 3.0));
upper_diag[i] = beta;
}
if i > 0 {
let r_prev = ((i - 1) as i32 + m - n).abs() as f64;
let beta = c2 / (4.0 * (2.0 * r_prev + 1.0) * (2.0 * r_prev + 3.0));
lower_diag[i - 1] = beta;
}
}
solve_tridiagonal_eigenvalue(&main_diag, &upper_diag, n)
}
fn solve_tridiagonal_eigenvalue(
main_diag: &[f64],
off_diag: &[f64],
target_n: i32,
) -> SpecialResult<f64> {
let n = main_diag.len();
if n == 0 {
return Err(SpecialError::ComputationError("Empty matrix".to_string()));
}
let target_f = target_n as f64;
let mut sigma = target_f * (target_f + 1.0);
let max_iter = 100;
let tolerance = 1e-12;
let mut v = vec![1.0; n];
let mut norm = v.iter().map(|x| x * x).sum::<f64>().sqrt();
for vi in v.iter_mut() {
*vi /= norm;
}
for _ in 0..max_iter {
let w = solve_shifted_tridiagonal(main_diag, off_diag, &v, sigma)?;
norm = w.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm < 1e-100 {
return Err(SpecialError::ComputationError(
"Eigenvalue iteration produced zero vector".to_string(),
));
}
for i in 0..n {
v[i] = w[i] / norm;
}
let mut lambda = 0.0;
for i in 0..n {
lambda += v[i] * main_diag[i] * v[i];
if i < n - 1 {
lambda += 2.0 * v[i] * off_diag[i] * v[i + 1];
}
}
if (lambda - sigma).abs() < tolerance {
return Ok(lambda);
}
sigma = lambda;
}
Ok(sigma)
}
fn solve_shifted_tridiagonal(
main_diag: &[f64],
off_diag: &[f64],
rhs: &[f64],
sigma: f64,
) -> SpecialResult<Vec<f64>> {
let n = main_diag.len();
if n == 0 {
return Ok(Vec::new());
}
let mut a = vec![0.0; n]; let mut b = vec![0.0; n]; let mut c = vec![0.0; n];
for i in 0..n {
b[i] = main_diag[i] - sigma;
if i < n - 1 {
c[i] = off_diag[i];
a[i + 1] = off_diag[i];
}
}
let mut c_prime = vec![0.0; n];
let mut d_prime = vec![0.0; n];
c_prime[0] = c[0] / b[0];
d_prime[0] = rhs[0] / b[0];
for i in 1..n {
let denom = b[i] - a[i] * c_prime[i - 1];
if denom.abs() < 1e-100 {
let reg_denom = denom + 1e-10 * denom.signum();
c_prime[i] = if i < n - 1 { c[i] / reg_denom } else { 0.0 };
d_prime[i] = (rhs[i] - a[i] * d_prime[i - 1]) / reg_denom;
} else {
c_prime[i] = if i < n - 1 { c[i] / denom } else { 0.0 };
d_prime[i] = (rhs[i] - a[i] * d_prime[i - 1]) / denom;
}
}
let mut x = vec![0.0; n];
x[n - 1] = d_prime[n - 1];
for i in (0..n - 1).rev() {
x[i] = d_prime[i] - c_prime[i] * x[i + 1];
}
Ok(x)
}