use crate::error::{SpecialError, SpecialResult};
use crate::q_analogs::{q_pochhammer, q_pochhammer_inf};
const MAX_TERMS: usize = 500;
const TOL: f64 = 1e-14;
fn q_poch_nu1(nu: f64, q: f64, n: usize) -> SpecialResult<f64> {
let a = q.powf(nu + 1.0);
q_pochhammer(a, q, n)
}
pub fn jackson_j_nu(nu: f64, x: f64, q: f64) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"jackson_j_nu: q must satisfy 0 < q < 1, got q = {q}"
)));
}
if x < 0.0 {
return Err(SpecialError::DomainError(format!(
"jackson_j_nu: x must be non-negative, got x = {x}"
)));
}
let a_nu = q.powf(nu + 1.0);
let poch_nu = q_pochhammer_inf(a_nu, q)?;
let poch_q = q_pochhammer_inf(q, q)?;
if poch_q.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"jackson_j_nu: (q;q)_∞ is too close to zero".to_string(),
));
}
let prefactor = poch_nu / poch_q;
let half_x = x / 2.0;
let mut sum = 0.0f64;
let mut q_kk1_2 = 1.0f64; let mut q_pow = 1.0f64; let mut poch_q_k = 1.0f64; let mut poch_nu_k = 1.0f64; let mut half_x_pow = half_x.powf(nu);
for k in 0..MAX_TERMS {
if k > 0 {
q_kk1_2 *= q.powi(k as i32); q_pow *= q;
poch_q_k *= 1.0 - q_pow;
poch_nu_k *= 1.0 - a_nu * q.powi((k - 1) as i32);
half_x_pow *= half_x * half_x;
}
let denom = poch_q_k * poch_nu_k;
if denom.abs() < 1e-300 {
break;
}
let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
let term = sign * q_kk1_2 * half_x_pow / denom;
sum += term;
if term.abs() < TOL * sum.abs().max(1e-300) && k > 2 {
break;
}
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"jackson_j_nu: series diverged".to_string(),
));
}
}
let result = prefactor * sum;
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"jackson_j_nu: result is not finite".to_string(),
));
}
Ok(result)
}
pub fn big_q_bessel(nu: f64, x: f64, q: f64) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"big_q_bessel: q must satisfy 0 < q < 1, got q = {q}"
)));
}
if x < 0.0 {
return Err(SpecialError::DomainError(format!(
"big_q_bessel: x must be non-negative, got x = {x}"
)));
}
let a_nu = q.powf(nu + 1.0);
let poch_nu = q_pochhammer_inf(a_nu, q)?;
let poch_q = q_pochhammer_inf(q, q)?;
if poch_q.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"big_q_bessel: (q;q)_∞ is too close to zero".to_string(),
));
}
let half_x = x / 2.0;
let half_x_nu = half_x.powf(nu);
let prefactor = poch_nu / poch_q * half_x_nu;
let mut sum = 0.0f64;
let mut poch_q_k = 1.0f64;
let mut poch_nu_k = 1.0f64;
let mut half_x2_pow = 1.0f64; let half_x2 = half_x * half_x;
let mut q_k_nu_k = 1.0f64; let mut q_pow_k = 1.0f64; let mut q_pow_nu = a_nu;
for k in 0..MAX_TERMS {
if k > 0 {
q_pow_k *= q;
q_k_nu_k *= q.powf(nu + (2 * k - 1) as f64);
poch_q_k *= 1.0 - q_pow_k;
poch_nu_k *= 1.0 - q_pow_nu * q.powi((k - 1) as i32);
half_x2_pow *= half_x2;
}
let denom = poch_q_k * poch_nu_k;
if denom.abs() < 1e-300 {
break;
}
let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
let term = sign * half_x2_pow * q_k_nu_k / denom;
sum += term;
if term.abs() < TOL * sum.abs().max(1e-300) && k > 2 {
break;
}
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"big_q_bessel: series diverged".to_string(),
));
}
}
let result = prefactor * sum;
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"big_q_bessel: result is not finite".to_string(),
));
}
Ok(result)
}
pub fn q_bessel_series(nu: f64, x: f64, q: f64, n_terms: usize) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_bessel_series: q must satisfy 0 < q < 1, got q = {q}"
)));
}
if x < 0.0 {
return Err(SpecialError::DomainError(format!(
"q_bessel_series: x must be non-negative, got x = {x}"
)));
}
if n_terms == 0 {
return Ok(0.0);
}
let half_x = x / 2.0;
let mut sum = 0.0f64;
let mut half_x_pow = half_x.powf(nu); let half_x2 = half_x * half_x;
let mut q_pow = 1.0f64; let a_nu = q.powf(nu + 1.0);
for k in 0..n_terms {
let poch_q_k = q_pochhammer(q, q, k)?;
let poch_nu_k = q_poch_nu1(nu, q, k)?;
let denom = poch_q_k * poch_nu_k;
if denom.abs() < 1e-300 {
break;
}
let sign = if k % 2 == 0 { 1.0 } else { -1.0 };
let term = sign * half_x_pow / denom;
sum += term;
half_x_pow *= half_x2;
q_pow *= q;
let _ = (a_nu, q_pow);
if term.abs() < TOL * sum.abs().max(1e-300) {
break;
}
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"q_bessel_series: series diverged".to_string(),
));
}
}
Ok(sum)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jackson_j_nu_at_zero_order_zero() {
let val = jackson_j_nu(0.0, 0.0, 0.5).expect("jackson j_0(0)");
assert!((val - 1.0).abs() < 1e-8, "val = {val}");
}
#[test]
fn test_jackson_j_nu_small_x() {
let val = jackson_j_nu(0.0, 0.01, 0.5).expect("jackson j_0 small x");
assert!((val - 1.0).abs() < 0.01, "val = {val}");
}
#[test]
fn test_jackson_j_nu_order_one_at_zero() {
let val = jackson_j_nu(1.0, 0.0, 0.5).expect("jackson j_1(0)");
assert!(val.abs() < 1e-14, "val = {val}");
}
#[test]
fn test_big_q_bessel_at_zero_order_zero() {
let val = big_q_bessel(0.0, 0.0, 0.5).expect("big_q_bessel J_0(0)");
assert!((val - 1.0).abs() < 1e-8, "val = {val}");
}
#[test]
fn test_big_q_bessel_order_one_at_zero() {
let val = big_q_bessel(1.0, 0.0, 0.5).expect("big_q_bessel J_1(0)");
assert!(val.abs() < 1e-14, "val = {val}");
}
#[test]
fn test_q_bessel_series_small() {
let val = q_bessel_series(0.0, 0.1, 0.5, 20).expect("q_bessel_series");
assert!(val.is_finite());
assert!((val - 1.0).abs() < 0.1, "val = {val}");
}
#[test]
fn test_q_bessel_series_n1_equals_first_term() {
let nu = 0.5f64;
let x = 0.4f64;
let q = 0.7f64;
let val = q_bessel_series(nu, x, q, 1).expect("q_bessel_series n=1");
let expected = (x / 2.0).powf(nu);
assert!((val - expected).abs() < 1e-12, "val = {val}, expected = {expected}");
}
#[test]
fn test_jackson_domain_errors() {
assert!(jackson_j_nu(0.0, 0.5, 1.5).is_err()); assert!(jackson_j_nu(0.0, -0.5, 0.5).is_err()); assert!(jackson_j_nu(0.0, 0.5, 0.0).is_err()); }
}