use std::f64::consts::{PI, SQRT_2};
use crate::error::{SpecialError, SpecialResult};
use crate::gamma::{gamma, gammaln};
#[allow(dead_code)]
pub fn pbdv(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if v.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError("NaN input to pbdv".to_string()));
}
if v == 0.0 {
return pbdv_0(x);
} else if v == 1.0 {
return pbdv_1(x);
} else if v == 2.0 {
return pbdv_2(x);
} else if v == -1.0 {
return pbdv_m1(x);
} else if v == -2.0 {
return pbdv_m2(x);
}
if v.floor() == v && (-20.0..=20.0).contains(&v) {
pbdv_integer(v as i32, x)
} else {
pbdv_general(v, x)
}
}
#[allow(dead_code)]
fn pbdv_0(x: f64) -> SpecialResult<(f64, f64)> {
let x2 = x * x;
let d = (-x2 / 4.0).exp();
let dp = -x / 2.0 * d;
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_1(x: f64) -> SpecialResult<(f64, f64)> {
let x2 = x * x;
let exp_term = (-x2 / 4.0).exp();
let d = x * exp_term;
let dp = (1.0 - x2 / 2.0) * exp_term;
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_2(x: f64) -> SpecialResult<(f64, f64)> {
let x2 = x * x;
let exp_term = (-x2 / 4.0).exp();
let d = (x2 - 2.0) * exp_term;
let dp = x * (x2 - 4.0) * exp_term / 2.0;
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_m1(x: f64) -> SpecialResult<(f64, f64)> {
let x2 = x * x;
let sqrt_pi_over_2 = (PI / 2.0).sqrt();
let exp_pos = (x2 / 4.0).exp();
let exp_neg = (-x2 / 4.0).exp();
let erf_val = erf_approx(x / SQRT_2);
let d = sqrt_pi_over_2 * exp_pos * (1.0 - erf_val) - x * exp_neg / 2.0;
let dp = x * d / 2.0 + exp_neg;
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_m2(x: f64) -> SpecialResult<(f64, f64)> {
let (d_m1, _) = pbdv_m1(x)?;
let (d_0, _) = pbdv_0(x)?;
let d = x * d_m1 - d_0;
let dp = d_m1 - x * d / 2.0;
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_integer(v: i32, x: f64) -> SpecialResult<(f64, f64)> {
if v >= 0 {
let (d0, d0p) = pbdv_0(x)?;
let (d1, d1p) = pbdv_1(x)?;
let mut d_prev = d0;
let mut d = d1;
let mut dp_prev = d0p;
let mut dp = d1p;
for k in 1..v {
let d_next = x * d - k as f64 * d_prev;
let dp_next = x * dp - k as f64 * dp_prev - d;
d_prev = d;
d = d_next;
dp_prev = dp;
dp = dp_next;
}
Ok((d, dp))
} else {
let (d0, d0p) = pbdv_0(x)?;
let (dm1, dm1p) = pbdv_m1(x)?;
let mut d_prev = dm1;
let mut d = d0;
let mut dp_prev = dm1p;
let mut dp = d0p;
for k in 0..(-v - 1) {
let d_next = (x * d + d_prev) / (k as f64 + 1.0);
let dp_next = (x * dp + dp_prev - d) / (k as f64 + 1.0);
d_prev = d;
d = d_next;
dp_prev = dp;
dp = dp_next;
}
Ok((d, dp))
}
}
#[allow(dead_code)]
fn pbdv_general(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x.abs() < 5.0 {
return pbdv_series(v, x);
}
if x > 0.0 && x.abs() > (2.0 * v.abs()).max(20.0) {
return pbdv_asymptotic_pos(v, x);
}
if x < 0.0 && x.abs() > (2.0 * v.abs()).max(20.0) {
return pbdv_asymptotic_neg(v, x);
}
pbdv_series(v, x)
}
#[allow(dead_code)]
fn pbdv_series(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x == 0.0 {
let gamma_1_plus_v_half = gamma((1.0 + v) / 2.0);
let gamma_1 = 1.0; let d_at_zero = 2_f64.powf(-v / 2.0) * gamma_1_plus_v_half / gamma_1;
let dp_at_zero = if v > 0.0 { 0.0 } else { f64::INFINITY };
return Ok((d_at_zero, dp_at_zero));
}
if x.abs() > 100.0 {
return if x > 0.0 {
pbdv_asymptotic_pos(v, x)
} else {
pbdv_asymptotic_neg(v, x)
};
}
let x2 = x * x;
let log_exp_term = -x2 / 4.0;
let exp_term = if log_exp_term < -700.0 {
0.0 } else {
log_exp_term.exp()
};
let mut d_sum; let mut dp_sum = 0.0;
let mut prev_d_sum = 0.0;
let mut prev_dp_sum = 0.0;
let mut stagnation_count = 0;
let v_half = v / 2.0;
let log_first_term = -v_half * 2.0_f64.ln() + gammaln(1.0 + v_half) - gammaln(1.0);
let first_term = log_first_term.exp();
let mut term = first_term;
let mut dp_term = 0.0;
d_sum = term;
for k in 1..100 {
if k % 2 == 0 {
let k_f64 = k as f64;
let log_coef = (k / 2) as f64 * (-(k_f64) / 2.0).ln() - log_factorial(k);
let coef = log_coef.exp();
let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
let log_x_pow_k = k_f64 * x.abs().ln();
let x_pow_k = sign_correction * log_x_pow_k.exp();
let new_term = coef * x_pow_k;
let x_pow_kminus_1 = if k > 0 { x_pow_k / x } else { 1.0 };
let new_dp_term = coef * x_pow_kminus_1 * k_f64;
if new_term.is_finite() {
term = new_term;
d_sum += term;
}
if new_dp_term.is_finite() {
dp_term = new_dp_term;
dp_sum += dp_term;
}
} else {
let k_f64 = k as f64;
let log_coef = ((k - 1) / 2) as f64 * (-(k_f64) / 2.0).ln() - log_factorial(k);
let coef = log_coef.exp();
let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
let log_x_pow_k = k_f64 * x.abs().ln();
let x_pow_k = sign_correction * log_x_pow_k.exp();
let new_term = coef * x_pow_k;
if new_term.is_finite() {
term = new_term;
d_sum += term;
dp_term = term * k_f64 / x;
dp_sum += dp_term;
}
}
let abs_tol = 1e-15;
let d_rel_tol = 1e-15 * d_sum.abs().max(1e-300);
let dp_rel_tol = 1e-15 * dp_sum.abs().max(1e-300);
if (term.abs() < abs_tol || term.abs() < d_rel_tol)
&& (dp_term.abs() < abs_tol || dp_term.abs() < dp_rel_tol)
{
break;
}
if (d_sum - prev_d_sum).abs() < d_rel_tol && (dp_sum - prev_dp_sum).abs() < dp_rel_tol {
stagnation_count += 1;
if stagnation_count >= 3 {
break;
}
} else {
stagnation_count = 0;
}
if k > 50 && term.abs() < 1e-30 && dp_term.abs() < 1e-30 {
break;
}
prev_d_sum = d_sum;
prev_dp_sum = dp_sum;
}
let d = d_sum * exp_term;
let dp = dp_sum * exp_term - x / 2.0 * d;
if !d.is_finite() || !dp.is_finite() {
if v.floor() == v && (-5.0..=5.0).contains(&v) {
return pbdv_integer(v as i32, x);
}
let d_approx = if v < 0.0 && x.abs() > 10.0 {
if x > 0.0 {
0.0 } else {
f64::INFINITY.copysign(if v.floor() as i32 % 2 == 0 { 1.0 } else { -1.0 })
}
} else if v > 0.0 && x.abs() > 10.0 {
0.0
} else {
let gamma_val = gamma((1.0 + v) / 2.0);
2_f64.powf(-v / 2.0) * gamma_val * (-x2 / 4.0).exp()
};
let dp_approx = -x / 2.0 * d_approx;
return Ok((d_approx, dp_approx));
}
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_asymptotic_pos(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x > 100.0 && v > 0.0 {
return Ok((0.0, 0.0));
}
let z = x / SQRT_2;
let v2 = v * v;
let log_pre_factor = -z * z / 2.0 - (v + 0.5) * z.abs().ln() - (v / 2.0) * 2.0_f64.ln();
let mut sum = 1.0;
let mut term = 1.0;
let mut prev_sum = 0.0;
let mut stagnation_count = 0;
let mut deriv_term = 0.0;
for k in 1..30 {
let numerator = v2 - (2 * k - 1) as f64 * (2 * k - 1) as f64;
let denominator = 2.0 * k as f64 * z * z;
if denominator.abs() < 1e-300 {
break;
}
let term_factor = numerator / denominator;
let new_term = term * term_factor;
if !new_term.is_finite() {
break;
}
term = new_term;
sum += term;
deriv_term = term;
let abs_tol = 1e-15;
let rel_tol = 1e-15 * sum.abs().max(1e-300);
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
if (sum - prev_sum).abs() < rel_tol {
stagnation_count += 1;
if stagnation_count >= 3 {
break;
}
} else {
stagnation_count = 0;
}
if k > 5 && term.abs() > prev_sum.abs() {
sum = prev_sum;
break;
}
prev_sum = sum;
}
let d = if log_pre_factor < -700.0 {
0.0 } else if log_pre_factor > 700.0 {
f64::INFINITY.copysign(if v.floor() as i32 % 2 == 0 { 1.0 } else { -1.0 })
} else {
log_pre_factor.exp() * sum
};
let dp = if d.abs() < 1e-300 {
0.0
} else {
let correction = deriv_term * z / sum;
-d * (z + (v + 0.5) / z - correction)
};
if !d.is_finite() || !dp.is_finite() {
let d_approx = 0.0; let dp_approx = 0.0; return Ok((d_approx, dp_approx));
}
Ok((d, dp))
}
#[allow(dead_code)]
fn pbdv_asymptotic_neg(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
let (v_val, vp_val) = pbvv(v, -x)?;
let pi_v = PI * v;
let sin_pi_v = pi_v.sin();
let sin_pi_v_plus_1_half = (PI * (v + 1.0) / 2.0).sin();
let cos_pi_v_half = (PI * v / 2.0).cos();
let (v_val_prev, _) = pbvv(v - 1.0, -x)?;
let d = (sin_pi_v * v_val + v_val_prev / cos_pi_v_half) / sin_pi_v_plus_1_half;
let dp = -vp_val;
Ok((d, dp))
}
#[allow(dead_code)]
pub fn pbvv(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if v.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError("NaN input to pbvv".to_string()));
}
if v.floor() == v && (-20.0..=20.0).contains(&v) {
pbvv_integer(v as i32, x)
} else {
pbvv_general(v, x)
}
}
#[allow(dead_code)]
fn pbvv_integer(v: i32, x: f64) -> SpecialResult<(f64, f64)> {
let (d_v, d_v_prime) = pbdv(v as f64, x)?;
if v >= 0 {
let (d_neg_vminus_1, d_neg_vminus_1_prime) = pbdv(-(v as f64) - 1.0, x)?;
let gamma_arg1 = v as f64 + 1.0;
let gamma_v_plus_1 = gamma(gamma_arg1);
let v_val =
d_v * (PI * v as f64).cos() - d_neg_vminus_1 * (2.0 / PI).sqrt() * gamma_v_plus_1;
let vp_val = d_v_prime * (PI * v as f64).cos()
- d_neg_vminus_1_prime * (2.0 / PI).sqrt() * gamma_v_plus_1;
Ok((v_val, vp_val))
} else {
let (d_neg_vminus_1, d_neg_vminus_1_prime) = pbdv(-(v as f64) - 1.0, x)?;
let gamma_arg1 = -(v as f64);
let _gamma_neg_v = gamma(gamma_arg1);
let v_val = d_neg_vminus_1;
let vp_val = d_neg_vminus_1_prime;
Ok((v_val, vp_val))
}
}
#[allow(dead_code)]
fn pbvv_general(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x.abs() < 5.0 {
return pbvv_series(v, x);
}
if x.abs() > (2.0 * v.abs()).max(20.0) {
return pbvv_asymptotic(v, x);
}
pbvv_series(v, x)
}
#[allow(dead_code)]
fn pbvv_series(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x == 0.0 {
let sqrt_2_pi = (2.0 / PI).sqrt();
let gamma_term = gamma(v + 0.5);
let v_at_zero = sqrt_2_pi / gamma_term;
let vp_at_zero = if v.floor() == v && v >= 0.0 {
if (v as i32) % 2 == 0 {
0.0 } else {
let mut val = sqrt_2_pi;
for k in 1..((v as i32 + 1) / 2) {
val *= (2 * k - 1) as f64 / 2.0;
}
val
}
} else {
0.0
};
return Ok((v_at_zero, vp_at_zero));
}
if x.abs() > 100.0 {
return pbvv_asymptotic(v, x);
}
let x2 = x * x;
let log_exp_term = x2 / 4.0;
let exp_term = if log_exp_term > 700.0 {
f64::INFINITY
} else {
log_exp_term.exp()
};
let sqrt_2_pi = (2.0 / PI).sqrt();
let gamma_term = if v + 0.5 > 170.0 {
gammaln(v + 0.5).exp()
} else {
gamma(v + 0.5)
};
let leading = if gamma_term.abs() < 1e-300 {
f64::INFINITY
} else if gamma_term.is_infinite() {
0.0
} else {
sqrt_2_pi * exp_term / gamma_term
};
let mut v_sum; let mut vp_sum = 0.0;
let mut prev_v_sum = 0.0;
let mut prev_vp_sum = 0.0;
let mut stagnation_count = 0;
let mut term = 1.0;
let mut vp_term = 0.0;
v_sum = term;
for k in 1..80 {
let k_f64 = k as f64;
if k <= 20 {
let coef = (k_f64 / 2.0).powi(k / 2) / factorial(k as usize);
let x_pow_k = x.powi(k);
let new_term = coef * x_pow_k;
let x_pow_kminus_1 = if k > 0 { x_pow_k / x } else { 1.0 };
let new_vp_term = coef * x_pow_kminus_1 * k_f64;
if new_term.is_finite() {
term = new_term;
v_sum += term;
}
if new_vp_term.is_finite() {
vp_term = new_vp_term;
vp_sum += vp_term;
}
} else {
let log_coef = (k / 2) as f64 * (k_f64 / 2.0).ln() - log_factorial(k as usize);
let coef = log_coef.exp();
let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
let log_x_pow_k = k_f64 * x.abs().ln();
let x_pow_k = sign_correction * log_x_pow_k.exp();
let new_term = coef * x_pow_k;
let sign_correctionminus_1 = if x < 0.0 && (k - 1) % 2 == 1 {
-1.0
} else {
1.0
};
let log_x_pow_kminus_1 = (k_f64 - 1.0) * x.abs().ln();
let x_pow_kminus_1 = sign_correctionminus_1 * log_x_pow_kminus_1.exp();
let new_vp_term = coef * x_pow_kminus_1 * k_f64;
if new_term.is_finite() {
term = new_term;
v_sum += term;
}
if new_vp_term.is_finite() {
vp_term = new_vp_term;
vp_sum += vp_term;
}
}
let abs_tol = 1e-15;
let v_rel_tol = 1e-15 * v_sum.abs().max(1e-300);
let vp_rel_tol = 1e-15 * vp_sum.abs().max(1e-300);
if (term.abs() < abs_tol || term.abs() < v_rel_tol)
&& (vp_term.abs() < abs_tol || vp_term.abs() < vp_rel_tol)
{
break;
}
if (v_sum - prev_v_sum).abs() < v_rel_tol && (vp_sum - prev_vp_sum).abs() < vp_rel_tol {
stagnation_count += 1;
if stagnation_count >= 3 {
break;
}
} else {
stagnation_count = 0;
}
if k > 50 && term.abs() < 1e-30 && vp_term.abs() < 1e-30 {
break;
}
prev_v_sum = v_sum;
prev_vp_sum = vp_sum;
}
let v_val = leading * v_sum;
let vp_val = leading * (vp_sum + v_sum * x / 2.0);
if !v_val.is_finite() || !vp_val.is_finite() {
if x.abs() > 20.0 {
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
let v_approx = sign * f64::INFINITY;
let vp_approx = sign * f64::INFINITY;
return Ok((v_approx, vp_approx));
} else if v > 100.0 {
let v_approx = 0.0;
let vp_approx = 0.0;
return Ok((v_approx, vp_approx));
}
}
Ok((v_val, vp_val))
}
#[allow(dead_code)]
fn pbvv_asymptotic(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
if x.abs() > 100.0 {
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
if x.abs() > 700.0 {
return Ok((sign * f64::INFINITY, sign * f64::INFINITY));
}
}
let z = x.abs() / SQRT_2;
let v2 = v * v;
let sign = if x >= 0.0 { 1.0 } else { -1.0 };
let mut sum = 1.0;
let mut term = 1.0;
let mut prev_sum = 0.0;
let mut stagnation_count = 0;
let mut deriv_term = 0.0;
for k in 1..30 {
let numerator = v2 - (2 * k - 1) as f64 * (2 * k - 1) as f64;
let denominator = 2.0 * k as f64 * z * z;
if denominator.abs() < 1e-300 {
break;
}
let term_factor = numerator / denominator;
let new_term = term * term_factor;
if !new_term.is_finite() {
break;
}
term = new_term;
sum += term;
deriv_term = term;
let abs_tol = 1e-15;
let rel_tol = 1e-15 * sum.abs().max(1e-300);
if term.abs() < abs_tol || term.abs() < rel_tol {
break;
}
if (sum - prev_sum).abs() < rel_tol {
stagnation_count += 1;
if stagnation_count >= 3 {
break;
}
} else {
stagnation_count = 0;
}
if k > 5 && term.abs() > prev_sum.abs() {
sum = prev_sum;
break;
}
prev_sum = sum;
}
let _sqrt_2_pi = (2.0 / PI).sqrt();
let gamma_term = if v + 0.5 > 170.0 {
gammaln(v + 0.5).exp()
} else {
gamma(v + 0.5)
};
let log_sqrt_2_pi = (2.0 / PI).sqrt().ln();
let log_exp_term = z * z / 2.0;
let log_z_term = (v + 0.5) * z.ln();
let log_sum = sum.ln();
let log_gamma = gamma_term.ln();
let log_v_val = log_sqrt_2_pi + log_exp_term - log_z_term + log_sum - log_gamma;
let v_val = if log_v_val > 700.0 {
sign * f64::INFINITY
} else if log_v_val < -700.0 {
0.0
} else {
sign * log_v_val.exp()
};
let vp_val = if !v_val.is_finite() {
v_val
} else if v_val.abs() < 1e-300 {
0.0
} else {
let correction = if sum.abs() < 1e-300 {
0.0 } else {
deriv_term * z / sum
};
v_val * (sign * z + (v + 0.5) / z - correction)
};
if !v_val.is_finite() && x.abs() < 100.0 {
let asymptotic_sign = if x >= 0.0 { 1.0 } else { -1.0 };
let v_approx = asymptotic_sign * f64::MAX * 0.1; let vp_approx = v_approx;
return Ok((v_approx, vp_approx));
}
Ok((v_val, vp_val))
}
#[allow(dead_code)]
pub fn pbdv_seq(vmax: usize, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to pbdv_seq".to_string(),
));
}
if vmax == 0 {
let (d0, d0p) = pbdv(0.0, x)?;
return Ok((vec![d0], vec![d0p]));
}
let mut d_values = vec![0.0; vmax + 1];
let mut dp_values = vec![0.0; vmax + 1];
let (d0, d0p) = pbdv(0.0, x)?;
let (d1, d1p) = pbdv(1.0, x)?;
d_values[0] = d0;
dp_values[0] = d0p;
if vmax >= 1 {
d_values[1] = d1;
dp_values[1] = d1p;
}
for v in 2..=vmax {
d_values[v] = x * d_values[v - 1] - (v as f64 - 1.0) * d_values[v - 2];
dp_values[v] = x * dp_values[v - 1] - (v as f64 - 1.0) * dp_values[v - 2] + d_values[v - 1];
}
Ok((d_values, dp_values))
}
#[allow(dead_code)]
pub fn pbvv_seq(vmax: usize, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
if x.is_nan() {
return Err(SpecialError::DomainError(
"NaN input to pbvv_seq".to_string(),
));
}
let mut v_values = vec![0.0; vmax + 1];
let mut vp_values = vec![0.0; vmax + 1];
for v in 0..=vmax {
let (v_val, vp_val) = pbvv(v as f64, x)?;
v_values[v] = v_val;
vp_values[v] = vp_val;
}
Ok((v_values, vp_values))
}
#[allow(dead_code)]
pub fn pbwa(a: f64, x: f64) -> SpecialResult<(f64, f64)> {
if a.is_nan() || x.is_nan() {
return Err(SpecialError::DomainError("NaN input to pbwa".to_string()));
}
let v = -a - 0.5;
pbdv(v, x)
}
#[allow(dead_code)]
fn factorial(n: usize) -> f64 {
if n <= 1 {
return 1.0;
}
if n <= 20 {
let mut result = 1.0;
for i in 2..=n {
result *= i as f64;
}
return result;
}
let result = gammaln(n as f64 + 1.0).exp();
if result.is_finite() {
result
} else {
let n_f64 = n as f64;
(2.0 * PI * n_f64).sqrt() * (n_f64 / std::f64::consts::E).powf(n_f64)
}
}
#[allow(dead_code)]
fn erf_approx(x: f64) -> f64 {
if x == 0.0 {
return 0.0;
}
if !x.is_finite() {
return if x.is_sign_positive() { 1.0 } else { -1.0 };
}
if x.abs() > 6.0 {
return if x > 0.0 { 1.0 } else { -1.0 };
}
if x.abs() <= 0.5 {
let x2 = x * x;
let x4 = x2 * x2;
let x6 = x4 * x2;
let x8 = x4 * x4;
let two_over_sqrt_pi = 2.0 / PI.sqrt();
x * two_over_sqrt_pi * (1.0 - x2 / 3.0 + x4 / 10.0 - x6 / 42.0 + x8 / 216.0)
} else {
let z = x.abs();
let t = 1.0 / (1.0 + 0.5 * z);
let tau = t
* (-z * z - 1.26551223
+ t * (1.00002368
+ t * (0.37409196
+ t * (0.09678418
+ t * (-0.18628806
+ t * (0.27886807
+ t * (-1.13520398
+ t * (1.48851587
+ t * (-0.82215223 + t * 0.17087277)))))))));
let result = if x >= 0.0 { 1.0 - tau } else { tau - 1.0 };
if result.abs() < 1e-16 {
if x >= 0.0 {
1.0
} else {
-1.0
}
} else {
result
}
}
}
#[allow(dead_code)]
fn log_factorial(n: usize) -> f64 {
if n <= 1 {
return 0.0; }
gammaln(n as f64 + 1.0)
}