use crate::error::{SpecialError, SpecialResult};
use crate::gamma::gamma;
pub fn meixner_pollaczek(n: usize, lambda: f64, phi: f64, x: f64) -> SpecialResult<f64> {
validate_mp_params(lambda, phi)?;
Ok(meixner_pollaczek_recur(n, lambda, phi, x))
}
pub fn meixner_pollaczek_array(
n_max: usize,
lambda: f64,
phi: f64,
x: f64,
) -> SpecialResult<Vec<f64>> {
validate_mp_params(lambda, phi)?;
let mut result = Vec::with_capacity(n_max + 1);
let cos_phi = phi.cos();
let sin_phi = phi.sin();
let p0 = 1.0_f64;
result.push(p0);
if n_max == 0 {
return Ok(result);
}
let p1 = 2.0 * (lambda * cos_phi + x * sin_phi);
result.push(p1);
if n_max == 1 {
return Ok(result);
}
let mut p_prev = p0;
let mut p_curr = p1;
for k in 1..n_max {
let n_f = k as f64;
let coeff_curr = 2.0 * (x * sin_phi + (n_f + lambda) * cos_phi);
let coeff_prev = n_f + 2.0 * lambda - 1.0;
let p_next = (coeff_curr * p_curr - coeff_prev * p_prev) / (n_f + 1.0);
result.push(p_next);
p_prev = p_curr;
p_curr = p_next;
}
Ok(result)
}
pub fn meixner_pollaczek_eval(
n: usize,
lambda: f64,
phi: f64,
x_vals: &[f64],
) -> SpecialResult<Vec<f64>> {
validate_mp_params(lambda, phi)?;
let result = x_vals
.iter()
.map(|&x| meixner_pollaczek_recur(n, lambda, phi, x))
.collect();
Ok(result)
}
pub fn meixner_pollaczek_weight(x: f64, lambda: f64, phi: f64) -> SpecialResult<f64> {
validate_mp_params(lambda, phi)?;
let log_abs_gamma_sq = 2.0 * log_abs_gamma_complex(lambda, x)?;
let exp_factor = (2.0 * phi - std::f64::consts::PI) * x;
let result = ((exp_factor + log_abs_gamma_sq) - (2.0 * std::f64::consts::PI).ln()).exp();
Ok(result)
}
pub fn charlier_polynomial(n: usize, a: f64, x: f64) -> SpecialResult<f64> {
if a <= 0.0 {
return Err(SpecialError::DomainError(format!(
"Charlier polynomial requires a > 0, got a = {a}"
)));
}
match n {
0 => Ok(1.0),
1 => Ok(1.0 - x / a),
_ => {
let mut c_prev = 1.0_f64; let mut c_curr = 1.0 - x / a;
for k in 1..n {
let k_f = k as f64;
let c_next = ((a - k_f - x) * c_curr - k_f * c_prev) / a;
c_prev = c_curr;
c_curr = c_next;
}
Ok(c_curr)
}
}
}
pub fn charlier_polynomial_array(n_max: usize, a: f64, x: f64) -> SpecialResult<Vec<f64>> {
if a <= 0.0 {
return Err(SpecialError::DomainError(format!(
"Charlier polynomial requires a > 0, got a = {a}"
)));
}
let mut result = Vec::with_capacity(n_max + 1);
let c0 = 1.0_f64;
result.push(c0);
if n_max == 0 {
return Ok(result);
}
let c1 = 1.0 - x / a;
result.push(c1);
let mut c_prev = c0;
let mut c_curr = c1;
for k in 1..n_max {
let k_f = k as f64;
let c_next = ((a - k_f - x) * c_curr - k_f * c_prev) / a;
result.push(c_next);
c_prev = c_curr;
c_curr = c_next;
}
Ok(result)
}
pub fn meixner_polynomial(n: usize, beta: f64, c: f64, x: f64) -> SpecialResult<f64> {
if beta <= 0.0 {
return Err(SpecialError::DomainError(format!(
"Meixner polynomial requires beta > 0, got {beta}"
)));
}
if c <= 0.0 || c >= 1.0 {
return Err(SpecialError::DomainError(format!(
"Meixner polynomial requires 0 < c < 1, got c = {c}"
)));
}
let z = 1.0 - 1.0 / c;
let mut term = 1.0_f64;
let mut sum = 1.0_f64;
for k in 1..=n {
let k_f = k as f64;
let numer = (-( (n as f64) ) + (k_f - 1.0)) * (-(x) + (k_f - 1.0));
let denom = (beta + k_f - 1.0) * k_f;
if denom.abs() < 1e-300 {
break;
}
term *= numer / denom * z;
sum += term;
}
Ok(sum)
}
fn validate_mp_params(lambda: f64, phi: f64) -> SpecialResult<()> {
if lambda <= 0.0 {
return Err(SpecialError::DomainError(format!(
"Meixner-Pollaczek polynomial requires lambda > 0, got lambda = {lambda}"
)));
}
if phi <= 0.0 || phi >= std::f64::consts::PI {
return Err(SpecialError::DomainError(format!(
"Meixner-Pollaczek polynomial requires 0 < phi < pi, got phi = {phi}"
)));
}
Ok(())
}
fn meixner_pollaczek_recur(n: usize, lambda: f64, phi: f64, x: f64) -> f64 {
let cos_phi = phi.cos();
let sin_phi = phi.sin();
match n {
0 => 1.0,
1 => 2.0 * (lambda * cos_phi + x * sin_phi),
_ => {
let mut p_prev = 1.0_f64;
let mut p_curr = 2.0 * (lambda * cos_phi + x * sin_phi);
for k in 1..n {
let n_f = k as f64;
let coeff_curr = 2.0 * (x * sin_phi + (n_f + lambda) * cos_phi);
let coeff_prev = n_f + 2.0 * lambda - 1.0;
let p_next = (coeff_curr * p_curr - coeff_prev * p_prev) / (n_f + 1.0);
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
}
}
fn log_abs_gamma_complex(a: f64, b: f64) -> SpecialResult<f64> {
if b == 0.0 {
let g = gamma(a);
if g <= 0.0 || !g.is_finite() {
return Err(SpecialError::DomainError(format!(
"log_abs_gamma_complex: Gamma({a}) = {g} is not positive finite"
)));
}
return Ok(g.ln());
}
let mut a_shifted = a;
let mut correction = 0.0_f64;
while a_shifted.hypot(b) < 8.0 {
correction += (a_shifted.hypot(b)).ln();
a_shifted += 1.0;
}
let r2 = a_shifted * a_shifted + b * b; let r = r2.sqrt();
let log_r = r.ln();
let theta = b.atan2(a_shifted);
let re_stirling = (a_shifted - 0.5) * log_r
- b * theta
- a_shifted
+ 0.5 * (2.0 * std::f64::consts::PI).ln();
let stirling_corr1 = a_shifted / (12.0 * r2);
let re_z3 = a_shifted * a_shifted * a_shifted - 3.0 * a_shifted * b * b;
let stirling_corr2 = -re_z3 / (360.0 * r2 * r2 * r2);
let log_abs_gamma_shifted = re_stirling + stirling_corr1 + stirling_corr2;
Ok(log_abs_gamma_shifted - correction)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mp_degree_zero() {
let phi = std::f64::consts::PI / 3.0;
for &x in &[-2.0, 0.0, 1.5, 100.0] {
let val = meixner_pollaczek(0, 1.0, phi, x).expect("ok");
assert!((val - 1.0).abs() < 1e-14, "P_0 should be 1, got {val}");
}
}
#[test]
fn test_mp_degree_one_at_half_pi() {
let phi = std::f64::consts::FRAC_PI_2;
for &x in &[0.0, 1.0, -1.5, 3.7] {
let val = meixner_pollaczek(1, 2.0, phi, x).expect("ok");
let expected = 2.0 * x;
assert!(
(val - expected).abs() < 1e-12,
"P_1 at phi=pi/2 should be {expected}, got {val}"
);
}
}
#[test]
fn test_mp_array_consistency() {
let lambda = 0.5;
let phi = std::f64::consts::PI / 4.0;
let x = 1.3;
let n_max = 6;
let arr = meixner_pollaczek_array(n_max, lambda, phi, x).expect("ok");
assert_eq!(arr.len(), n_max + 1);
for n in 0..=n_max {
let scalar = meixner_pollaczek(n, lambda, phi, x).expect("ok");
assert!(
(arr[n] - scalar).abs() < 1e-12,
"degree {n}: array={} scalar={scalar}",
arr[n]
);
}
}
#[test]
fn test_mp_domain_errors() {
assert!(meixner_pollaczek(2, 0.0, 1.0, 0.0).is_err());
assert!(meixner_pollaczek(2, -1.0, 1.0, 0.0).is_err());
assert!(meixner_pollaczek(2, 1.0, 0.0, 0.0).is_err());
assert!(meixner_pollaczek(2, 1.0, std::f64::consts::PI, 0.0).is_err());
assert!(meixner_pollaczek(2, 1.0, -0.1, 0.0).is_err());
assert!(meixner_pollaczek(2, 1.0, 4.0, 0.0).is_err());
}
#[test]
fn test_mp_recurrence_consistency() {
let lambda = 1.5;
let phi = std::f64::consts::PI / 3.0;
let x = 0.8;
let cos_phi = phi.cos();
let sin_phi = phi.sin();
let p0 = meixner_pollaczek(0, lambda, phi, x).expect("ok");
let p1 = meixner_pollaczek(1, lambda, phi, x).expect("ok");
let p2 = meixner_pollaczek(2, lambda, phi, x).expect("ok");
let rhs = (2.0 * (x * sin_phi + (1.0 + lambda) * cos_phi) * p1
- (1.0 + 2.0 * lambda - 1.0) * p0)
/ 2.0;
assert!(
(p2 - rhs).abs() < 1e-12,
"Recurrence check: P_2 = {p2}, rhs = {rhs}"
);
}
#[test]
fn test_mp_weight_positive() {
let lambda = 1.0;
let phi = std::f64::consts::PI / 3.0;
for &x in &[-3.0, -1.0, 0.0, 1.0, 2.5] {
let w = meixner_pollaczek_weight(x, lambda, phi).expect("ok");
assert!(w > 0.0, "Weight should be positive at x={x}, got {w}");
}
}
#[test]
fn test_charlier_degree_zero() {
for &x in &[0.0, 1.0, 5.0] {
let val = charlier_polynomial(0, 2.0, x).expect("ok");
assert!((val - 1.0).abs() < 1e-14);
}
}
#[test]
fn test_charlier_degree_one() {
let a = 3.0;
for &x in &[0.0, 1.0, 3.0, 6.0] {
let val = charlier_polynomial(1, a, x).expect("ok");
let expected = 1.0 - x / a;
assert!(
(val - expected).abs() < 1e-14,
"C_1({x}; {a}) should be {expected}, got {val}"
);
}
}
#[test]
fn test_charlier_recurrence() {
let a = 2.0;
let x = 1.5;
let c0 = charlier_polynomial(0, a, x).expect("ok");
let c1 = charlier_polynomial(1, a, x).expect("ok");
let c2 = charlier_polynomial(2, a, x).expect("ok");
let rhs = ((a - 1.0 - x) * c1 - c0) / a;
assert!(
(c2 - rhs).abs() < 1e-12,
"Charlier recurrence: C_2={c2}, rhs={rhs}"
);
}
#[test]
fn test_charlier_array_consistency() {
let a = 2.5;
let x = 2.0;
let n_max = 5;
let arr = charlier_polynomial_array(n_max, a, x).expect("ok");
assert_eq!(arr.len(), n_max + 1);
for n in 0..=n_max {
let scalar = charlier_polynomial(n, a, x).expect("ok");
assert!(
(arr[n] - scalar).abs() < 1e-12,
"Charlier degree {n}: array={} scalar={scalar}",
arr[n]
);
}
}
#[test]
fn test_charlier_domain_error() {
assert!(charlier_polynomial(2, 0.0, 1.0).is_err());
assert!(charlier_polynomial(2, -1.0, 1.0).is_err());
}
#[test]
fn test_meixner_polynomial_n0() {
let m0 = meixner_polynomial(0, 1.0, 0.5, 2.0).expect("ok");
assert!((m0 - 1.0).abs() < 1e-14);
}
#[test]
fn test_meixner_polynomial_n1() {
let beta = 2.0;
let c = 0.5;
let x = 3.0;
let m1 = meixner_polynomial(1, beta, c, x).expect("ok");
let expected = 1.0 + x * (1.0 - 1.0 / c) / beta;
assert!(
(m1 - expected).abs() < 1e-12,
"M_1 should be {expected}, got {m1}"
);
}
#[test]
fn test_meixner_polynomial_domain_error() {
assert!(meixner_polynomial(2, 0.0, 0.5, 1.0).is_err());
assert!(meixner_polynomial(2, 1.0, 0.0, 1.0).is_err());
assert!(meixner_polynomial(2, 1.0, 1.0, 1.0).is_err());
assert!(meixner_polynomial(2, 1.0, -0.1, 1.0).is_err());
}
#[test]
fn test_log_abs_gamma_complex_real_case() {
use crate::gamma::gammaln;
let a = 3.5;
let result = log_abs_gamma_complex(a, 0.0).expect("ok");
let expected = gammaln(a);
assert!(
(result - expected).abs() < 1e-10,
"log|Gamma({a})| should match lgamma: {result} vs {expected}"
);
}
}