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 m_f64 = m as f64;
let c2 = c.powi(2); let parity = (n - m) % 2;
let parity_f64 = parity as f64;
let matrixsize = 25.max(2 * (n as usize) + 15);
let mut diag = vec![0.0_f64; matrixsize];
let mut off = vec![0.0_f64; matrixsize - 1];
for k in 0..matrixsize {
let l = m_f64 + 2.0 * k as f64 + parity_f64;
diag[k] = l * (l + 1.0);
}
for k in 0..(matrixsize - 1) {
let l = m_f64 + 2.0 * k as f64 + parity_f64;
off[k] = -c2 / (4.0 * (2.0 * l + 1.0) * (2.0 * l + 3.0));
}
let tiny = 1e-300_f64;
let mut d_prev = 1.0_f64;
let mut d_curr = diag[0] - lambda;
let mut dp_prev = 0.0_f64;
let mut dp_curr = -1.0_f64;
let mut log_scale = 0.0_f64;
let mut sign = if d_curr >= 0.0 { 1.0 } else { -1.0 };
for k in 1..matrixsize {
let dk = diag[k] - lambda;
let bk_sq = off[k - 1] * off[k - 1];
let dp_next = -d_prev + dk * dp_curr - bk_sq * dp_prev;
let d_next = dk * d_curr - bk_sq * d_prev;
dp_prev = dp_curr;
d_prev = d_curr;
dp_curr = dp_next;
d_curr = d_next;
let abs_d = d_curr.abs();
if abs_d > 1e100 {
let scale = 1.0 / abs_d;
d_curr *= scale;
d_prev *= scale;
dp_curr *= scale;
dp_prev *= scale;
log_scale += abs_d.ln();
sign = if d_curr >= 0.0 { 1.0 } else { -1.0 };
} else if abs_d < tiny && abs_d > 0.0 {
let scale = 1.0 / abs_d.max(tiny);
d_curr *= scale;
d_prev *= scale;
dp_curr *= scale;
dp_prev *= scale;
log_scale -= abs_d.ln().abs();
}
}
let _ = log_scale;
let _ = sign;
Ok((d_curr, dp_curr))
}
#[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))
}