use crate::error::{SpecialError, SpecialResult};
fn obl_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_oblate_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(
"Oblate continued fraction iteration diverged".to_string(),
));
}
}
Ok(lambda)
}
#[allow(dead_code)]
fn compute_oblate_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 obl_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 obl_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 c.is_nan() {
return Ok(f64::NAN);
}
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 {
return obl_cv_continued_fraction(m, n, c);
}
obl_cv_asymptotic(m, n, c)
}
#[allow(dead_code)]
pub fn obl_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 obl_cv(m, degree, c) {
Ok(val) => result.push(val),
Err(e) => return Err(e),
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn obl_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 {
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x);
let correction = -c.powi(2) * x * p_mn / (4.0 * (n as f64 * (n as f64 + 1.0)));
let perturbed_value = p_mn + correction;
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_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus);
let correction_plus = -c.powi(2) * x_plus * p_plus / (4.0 * (n as f64 * (n as f64 + 1.0)));
let correctionminus = -c.powi(2) * xminus * pminus / (4.0 * (n as f64 * (n as f64 + 1.0)));
let perturbed_derivative =
((p_plus + correction_plus) - (pminus + correctionminus)) / (2.0 * h);
return Ok((perturbed_value, perturbed_derivative));
}
let lambda = obl_cv(m, n, c)?;
let scaling_factor = (lambda / (n as f64 * (n as f64 + 1.0))).sqrt().abs();
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, x) * scaling_factor;
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_plus = crate::orthogonal::legendre_assoc(n as usize, m, x_plus) * scaling_factor;
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, xminus) * scaling_factor;
let p_mn_prime = (p_plus - pminus) / (2.0 * h);
Ok((p_mn, p_mn_prime))
}
#[allow(dead_code)]
pub fn obl_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 < 0.0 {
return Err(SpecialError::DomainError(
"Radial coordinate x must be ≥ 0.0".to_string(),
));
}
if c.is_nan() || x.is_nan() {
return Ok((f64::NAN, f64::NAN));
}
if c == 0.0 {
let legendre_arg = if x > 1.0 { 1.0 / x } else { x };
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, legendre_arg);
let transformation_factor = if x > 1.0 { x.powi(-{ n }) } else { 1.0 };
let radial_value = p_mn * transformation_factor;
let h = 1e-8;
let x_plus = x + h;
let xminus = if x - h >= 0.0 { x - h } else { x + h };
let legendre_plus = if x_plus > 1.0 { 1.0 / x_plus } else { x_plus };
let legendreminus = if xminus > 1.0 { 1.0 / xminus } else { xminus };
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, legendre_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, legendreminus);
let trans_plus = if x_plus > 1.0 {
x_plus.powi(-{ n })
} else {
1.0
};
let transminus = if xminus > 1.0 {
xminus.powi(-{ n })
} else {
1.0
};
let radial_derivative = ((p_plus * trans_plus) - (pminus * transminus)) / (2.0 * h);
return Ok((radial_value, radial_derivative));
}
if c.abs() < 1.0 {
let legendre_arg = if x > 1.0 { 1.0 / x } else { x };
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, legendre_arg);
let transformation_factor = if x > 1.0 { x.powi(-{ n }) } else { 1.0 };
let base_value = p_mn * transformation_factor;
let lambda = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let correction = -c.powi(2) * eta * base_value / (4.0 * lambda.abs().sqrt());
let perturbed_value = base_value + correction;
let h = 1e-8;
let x_plus = x + h;
let xminus = if x - h >= 0.0 { x - h } else { x + h };
let legendre_plus = if x_plus > 1.0 { 1.0 / x_plus } else { x_plus };
let legendreminus = if xminus > 1.0 { 1.0 / xminus } else { xminus };
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, legendre_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, legendreminus);
let trans_plus = if x_plus > 1.0 {
x_plus.powi(-{ n })
} else {
1.0
};
let transminus = if xminus > 1.0 {
xminus.powi(-{ n })
} else {
1.0
};
let base_plus = p_plus * trans_plus;
let baseminus = pminus * transminus;
let eta_plus = if x_plus >= 1.0 {
(x_plus.powi(2) - 1.0).sqrt()
} else {
(1.0 - x_plus.powi(2)).sqrt()
};
let etaminus = if xminus >= 1.0 {
(xminus.powi(2) - 1.0).sqrt()
} else {
(1.0 - xminus.powi(2)).sqrt()
};
let correction_plus = -c.powi(2) * eta_plus * base_plus / (4.0 * lambda.abs().sqrt());
let correctionminus = -c.powi(2) * etaminus * baseminus / (4.0 * lambda.abs().sqrt());
let perturbed_derivative =
((base_plus + correction_plus) - (baseminus + correctionminus)) / (2.0 * h);
return Ok((perturbed_value, perturbed_derivative));
}
if c.abs() < 10.0 {
let lambda = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let normalization = (2.0 / (std::f64::consts::PI * lambda.abs())).sqrt();
let argument = c * eta;
let (exponential_part, power_part) = if x >= 1.0 {
(argument.exp(), eta.powf(-0.5))
} else {
((-argument.abs()).exp(), eta.powf(-0.5))
};
let leading_coefficient = if m == 0 { 1.0 } else { eta.powi(m) };
let radial_value = normalization * exponential_part * power_part * leading_coefficient;
let h = 1e-8;
let x_plus = x + h;
let _xminus = if x - h >= 0.0 { x - h } else { x + h };
let eta_plus = if x_plus >= 1.0 {
(x_plus.powi(2) - 1.0).sqrt()
} else {
(1.0 - x_plus.powi(2)).sqrt()
};
let arg_plus = c * eta_plus;
let (exp_plus, power_plus) = if x_plus >= 1.0 {
(arg_plus.exp(), eta_plus.powf(-0.5))
} else {
((-arg_plus.abs()).exp(), eta_plus.powf(-0.5))
};
let coeff_plus = if m == 0 { 1.0 } else { eta_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 = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let phase = c * eta + lambda * eta.ln();
let amplitude = (2.0 / (std::f64::consts::PI * c * eta)).sqrt();
let radial_value = if x >= 1.0 {
amplitude * phase.cos()
} else {
amplitude * (-phase.abs()).exp()
};
let phase_derivative = c + lambda / eta;
let amplitude_derivative = -amplitude / (2.0 * eta);
let radial_derivative = if x >= 1.0 {
amplitude_derivative * phase.cos() - amplitude * phase.sin() * phase_derivative
} else {
amplitude_derivative * (-phase.abs()).exp()
- amplitude * (-phase.abs()).exp() * phase_derivative
};
Ok((radial_value, radial_derivative))
}
#[allow(dead_code)]
pub fn obl_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 < 0.0 {
return Err(SpecialError::DomainError(
"Radial coordinate x must be ≥ 0.0".to_string(),
));
}
if c.is_nan() || x.is_nan() {
return Ok((f64::NAN, f64::NAN));
}
if c == 0.0 {
let legendre_arg = if x > 1.0 { 1.0 / x } else { x };
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, legendre_arg);
let q_mn = p_mn * (legendre_arg + 1.0).ln() / (legendre_arg - 1.0).ln().abs();
let transformation_factor = if x > 1.0 { x.powi(-(n + 1)) } else { 1.0 };
let radial_value = q_mn * transformation_factor;
let h = 1e-8;
let x_plus = x + h;
let xminus = if x - h >= 0.0 { x - h } else { x + h };
let legendre_plus = if x_plus > 1.0 { 1.0 / x_plus } else { x_plus };
let legendreminus = if xminus > 1.0 { 1.0 / xminus } else { xminus };
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, legendre_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, legendreminus);
let q_plus = p_plus * (legendre_plus + 1.0).ln() / (legendre_plus - 1.0).ln().abs();
let qminus = pminus * (legendreminus + 1.0).ln() / (legendreminus - 1.0).ln().abs();
let trans_plus = if x_plus > 1.0 {
x_plus.powi(-(n + 1))
} else {
1.0
};
let transminus = if xminus > 1.0 {
xminus.powi(-(n + 1))
} else {
1.0
};
let radial_derivative = ((q_plus * trans_plus) - (qminus * transminus)) / (2.0 * h);
return Ok((radial_value, radial_derivative));
}
if c.abs() < 1.0 {
let legendre_arg = if x > 1.0 { 1.0 / x } else { x };
let p_mn = crate::orthogonal::legendre_assoc(n as usize, m, legendre_arg);
let q_mn = p_mn * (legendre_arg + 1.0).ln() / (legendre_arg - 1.0).ln().abs();
let transformation_factor = if x > 1.0 { x.powi(-(n + 1)) } else { 1.0 };
let base_value = q_mn * transformation_factor;
let lambda = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let correction = -c.powi(2) * eta * base_value / (4.0 * lambda.abs().sqrt());
let perturbed_value = base_value + correction;
let h = 1e-8;
let x_plus = x + h;
let xminus = if x - h >= 0.0 { x - h } else { x + h };
let legendre_plus = if x_plus > 1.0 { 1.0 / x_plus } else { x_plus };
let legendreminus = if xminus > 1.0 { 1.0 / xminus } else { xminus };
let p_plus = crate::orthogonal::legendre_assoc(n as usize, m, legendre_plus);
let pminus = crate::orthogonal::legendre_assoc(n as usize, m, legendreminus);
let q_plus = p_plus * (legendre_plus + 1.0).ln() / (legendre_plus - 1.0).ln().abs();
let qminus = pminus * (legendreminus + 1.0).ln() / (legendreminus - 1.0).ln().abs();
let trans_plus = if x_plus > 1.0 {
x_plus.powi(-(n + 1))
} else {
1.0
};
let transminus = if xminus > 1.0 {
xminus.powi(-(n + 1))
} else {
1.0
};
let base_plus = q_plus * trans_plus;
let baseminus = qminus * transminus;
let eta_plus = if x_plus >= 1.0 {
(x_plus.powi(2) - 1.0).sqrt()
} else {
(1.0 - x_plus.powi(2)).sqrt()
};
let etaminus = if xminus >= 1.0 {
(xminus.powi(2) - 1.0).sqrt()
} else {
(1.0 - xminus.powi(2)).sqrt()
};
let correction_plus = -c.powi(2) * eta_plus * base_plus / (4.0 * lambda.abs().sqrt());
let correctionminus = -c.powi(2) * etaminus * baseminus / (4.0 * lambda.abs().sqrt());
let perturbed_derivative =
((base_plus + correction_plus) - (baseminus + correctionminus)) / (2.0 * h);
return Ok((perturbed_value, perturbed_derivative));
}
if c.abs() < 10.0 {
let lambda = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let normalization = (2.0 / (std::f64::consts::PI * lambda.abs())).sqrt();
let argument = c * eta;
let (exponential_part, power_part) = if x >= 1.0 {
((-argument).exp(), eta.powf(-0.5))
} else {
(argument.sin(), eta.powf(-0.5))
};
let leading_coefficient = if m == 0 { 1.0 } else { eta.powi(m) };
let radial_value = normalization * exponential_part * power_part * leading_coefficient;
let h = 1e-8;
let x_plus = x + h;
let _xminus = if x - h >= 0.0 { x - h } else { x + h };
let eta_plus = if x_plus >= 1.0 {
(x_plus.powi(2) - 1.0).sqrt()
} else {
(1.0 - x_plus.powi(2)).sqrt()
};
let arg_plus = c * eta_plus;
let (exp_plus, power_plus) = if x_plus >= 1.0 {
((-arg_plus).exp(), eta_plus.powf(-0.5))
} else {
(arg_plus.sin(), eta_plus.powf(-0.5))
};
let coeff_plus = if m == 0 { 1.0 } else { eta_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 = obl_cv(m, n, c)?;
let eta = if x >= 1.0 {
(x.powi(2) - 1.0).sqrt()
} else {
(1.0 - x.powi(2)).sqrt()
};
let phase = -c * eta + lambda * eta.ln();
let amplitude = (2.0 / (std::f64::consts::PI * c * eta)).sqrt();
let radial_value = if x >= 1.0 {
amplitude * phase.sin() } else {
amplitude * phase.cos() };
let phase_derivative = -c + lambda / eta;
let amplitude_derivative = -amplitude / (2.0 * eta);
let radial_derivative = if x >= 1.0 {
amplitude_derivative * phase.sin() + amplitude * phase.cos() * phase_derivative
} else {
amplitude_derivative * phase.cos() - amplitude * phase.sin() * phase_derivative
};
Ok((radial_value, radial_derivative))
}