use crate::error::{SpecialError, SpecialResult};
const MAX_SERIES_TERMS: usize = 500;
const CONVERGENCE_TOL: f64 = 1e-15;
pub fn q_pochhammer(a: f64, q: f64, n: usize) -> SpecialResult<f64> {
if n == 0 {
return Ok(1.0);
}
let mut result = 1.0f64;
let mut q_k = 1.0f64;
for _ in 0..n {
let factor = 1.0 - a * q_k;
result *= factor;
q_k *= q;
}
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"q_pochhammer: result overflowed or underflowed".to_string(),
));
}
Ok(result)
}
pub fn q_pochhammer_inf(a: f64, q: f64) -> SpecialResult<f64> {
if q.abs() >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_pochhammer_inf: |q| must be < 1, got q = {q}"
)));
}
let mut result = 1.0f64;
let mut q_k = 1.0f64;
for _ in 0..MAX_SERIES_TERMS {
let factor = 1.0 - a * q_k;
let prev = result;
result *= factor;
q_k *= q;
if (result - prev).abs() < CONVERGENCE_TOL * result.abs() {
return Ok(result);
}
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"q_pochhammer_inf: partial product overflowed".to_string(),
));
}
}
if result.is_finite() {
Ok(result)
} else {
Err(SpecialError::ConvergenceError(
"q_pochhammer_inf: infinite product failed to converge".to_string(),
))
}
}
fn q_number(n: u64, q: f64) -> f64 {
if (q - 1.0).abs() < 1e-14 {
n as f64
} else {
(1.0 - q.powi(n as i32)) / (1.0 - q)
}
}
pub fn q_factorial(n: usize, q: f64) -> SpecialResult<f64> {
if n == 0 {
return Ok(1.0);
}
let mut result = 1.0f64;
if (q - 1.0).abs() < 1e-14 {
for k in 1..=(n as u64) {
result *= k as f64;
}
} else {
for k in 1..=(n as u64) {
let q_k = q_number(k, q);
result *= q_k;
if !result.is_finite() {
return Err(SpecialError::OverflowError(format!(
"q_factorial: overflow at k = {k}"
)));
}
}
}
Ok(result)
}
pub fn q_binomial(n: usize, k: usize, q: f64) -> SpecialResult<f64> {
if k > n {
return Err(SpecialError::DomainError(format!(
"q_binomial: k = {k} must be ≤ n = {n}"
)));
}
if k == 0 || k == n {
return Ok(1.0);
}
let k_eff = k.min(n - k);
let mut result = 1.0f64;
if (q - 1.0).abs() < 1e-14 {
for j in 0..k_eff {
result *= (n - j) as f64 / (j + 1) as f64;
}
} else {
for j in 0..k_eff {
let num = 1.0 - q.powi((n - j) as i32);
let den = 1.0 - q.powi((j + 1) as i32);
if den.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"q_binomial: denominator factor vanished".to_string(),
));
}
result *= num / den;
if !result.is_finite() {
return Err(SpecialError::OverflowError(format!(
"q_binomial: overflow at j = {j}"
)));
}
}
}
Ok(result)
}
pub fn q_gamma(x: f64, q: f64) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_gamma: q must satisfy 0 < q < 1, got q = {q}"
)));
}
if x <= 0.0 {
return Err(SpecialError::DomainError(format!(
"q_gamma: x must be positive, got x = {x}"
)));
}
let qx = q.powf(x);
let poch_q = q_pochhammer_inf(q, q)?;
let poch_qx = q_pochhammer_inf(qx, q)?;
if poch_qx.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"q_gamma: (q^x; q)_∞ is too close to zero".to_string(),
));
}
let result = poch_q / poch_qx * (1.0 - q).powf(1.0 - x);
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"q_gamma: result is not finite".to_string(),
));
}
Ok(result)
}
pub fn q_beta(a: f64, b: f64, q: f64) -> SpecialResult<f64> {
if q <= 0.0 || q >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_beta: q must satisfy 0 < q < 1, got q = {q}"
)));
}
if a <= 0.0 || b <= 0.0 {
return Err(SpecialError::DomainError(format!(
"q_beta: a and b must be positive, got a = {a}, b = {b}"
)));
}
let gamma_a = q_gamma(a, q)?;
let gamma_b = q_gamma(b, q)?;
let gamma_ab = q_gamma(a + b, q)?;
if gamma_ab.abs() < 1e-300 {
return Err(SpecialError::ComputationError(
"q_beta: Γ_q(a+b) is too close to zero".to_string(),
));
}
let result = gamma_a * gamma_b / gamma_ab;
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"q_beta: result is not finite".to_string(),
));
}
Ok(result)
}
pub fn q_exponential(x: f64, q: f64) -> SpecialResult<f64> {
if q.abs() >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_exponential: |q| must be < 1, got q = {q}"
)));
}
let mut sum = 0.0f64;
let mut x_pow_n = 1.0f64; let mut q_fact = 1.0f64; let mut q_pow_n = 1.0f64;
for n in 0..MAX_SERIES_TERMS {
if n > 0 {
x_pow_n *= x;
let q_n = if (q - 1.0).abs() < 1e-14 {
n as f64
} else {
(1.0 - q_pow_n) / (1.0 - q)
};
q_fact *= q_n;
if q_fact.abs() < 1e-300 {
break;
}
}
let term = x_pow_n / q_fact;
sum += term;
if term.abs() < CONVERGENCE_TOL * sum.abs() && n > 5 {
return Ok(sum);
}
q_pow_n *= q;
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"q_exponential: series diverged".to_string(),
));
}
}
if sum.is_finite() {
Ok(sum)
} else {
Err(SpecialError::ConvergenceError(
"q_exponential: series did not converge".to_string(),
))
}
}
pub fn q_exponential_big(x: f64, q: f64) -> SpecialResult<f64> {
if q.abs() >= 1.0 {
return Err(SpecialError::DomainError(format!(
"q_exponential_big: |q| must be < 1, got q = {q}"
)));
}
let a = -x * (1.0 - q);
q_pochhammer_inf(a, q)
}
pub fn q_logarithm(x: f64, q: f64) -> SpecialResult<f64> {
if x <= 0.0 {
return Err(SpecialError::DomainError(format!(
"q_logarithm: x must be positive, got x = {x}"
)));
}
if (q - 1.0).abs() < 1e-14 {
return Ok(x.ln());
}
let exponent = 1.0 - q;
let x_pow = x.powf(exponent);
let result = (x_pow - 1.0) / exponent;
if !result.is_finite() {
return Err(SpecialError::OverflowError(
"q_logarithm: result is not finite".to_string(),
));
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_q_pochhammer_empty_product() {
let val = q_pochhammer(0.5, 0.5, 0).expect("q_pochhammer(0.5, 0.5, 0)");
assert!((val - 1.0).abs() < 1e-14);
}
#[test]
fn test_q_pochhammer_single_factor() {
let val = q_pochhammer(0.3, 0.5, 1).expect("q_pochhammer single factor");
assert!((val - 0.7).abs() < 1e-14);
}
#[test]
fn test_q_pochhammer_three_factors() {
let expected = 0.5 * 0.75 * 0.875;
let val = q_pochhammer(0.5, 0.5, 3).expect("q_pochhammer three factors");
assert!((val - expected).abs() < 1e-12);
}
#[test]
fn test_q_pochhammer_inf_zero_base() {
let val = q_pochhammer_inf(0.0, 0.5).expect("q_pochhammer_inf zero base");
assert!((val - 1.0).abs() < 1e-12);
}
#[test]
fn test_q_factorial_zero() {
let val = q_factorial(0, 0.5).expect("q_factorial(0)");
assert!((val - 1.0).abs() < 1e-14);
}
#[test]
fn test_q_factorial_classical_limit() {
let q = 1.0 - 1e-9;
for n in 0..=7usize {
let qf = q_factorial(n, q).expect("q_factorial classical limit");
let classical: f64 = (1..=(n as u64)).product::<u64>() as f64;
let expected = if n == 0 { 1.0 } else { classical };
assert!(
(qf - expected).abs() < 1e-3 * expected.max(1.0),
"n={n}: q_factorial={qf}, expected={expected}"
);
}
}
#[test]
fn test_q_binomial_boundary() {
for n in 1..=5usize {
let v0 = q_binomial(n, 0, 0.5).expect("q_binomial k=0");
let vn = q_binomial(n, n, 0.5).expect("q_binomial k=n");
assert!((v0 - 1.0).abs() < 1e-12);
assert!((vn - 1.0).abs() < 1e-12);
}
}
#[test]
fn test_q_binomial_classical_limit() {
let val = q_binomial(4, 2, 1.0 - 1e-9).expect("q_binomial classical");
assert!((val - 6.0).abs() < 0.01, "val = {val}");
}
#[test]
fn test_q_binomial_q2() {
let val = q_binomial(4, 2, 2.0).expect("q_binomial q=2");
assert!((val - 35.0).abs() < 1e-6, "val = {val}");
}
#[test]
fn test_q_gamma_at_one() {
let val = q_gamma(1.0, 0.5).expect("q_gamma(1)");
assert!((val - 1.0).abs() < 1e-8, "val = {val}");
}
#[test]
fn test_q_gamma_functional_eq() {
let q = 0.7f64;
let x = 1.5f64;
let gx1 = q_gamma(x + 1.0, q).expect("q_gamma(x+1)");
let gx = q_gamma(x, q).expect("q_gamma(x)");
let q_x = (1.0 - q.powf(x)) / (1.0 - q);
assert!(
(gx1 - q_x * gx).abs() < 1e-8,
"functional eq: lhs={gx1}, rhs={}, diff={}",
q_x * gx,
(gx1 - q_x * gx).abs()
);
}
#[test]
fn test_q_beta_symmetry() {
let q = 0.6f64;
let a = 1.5f64;
let b = 2.0f64;
let v1 = q_beta(a, b, q).expect("q_beta(a,b)");
let v2 = q_beta(b, a, q).expect("q_beta(b,a)");
assert!((v1 - v2).abs() < 1e-8, "symmetry: {v1} vs {v2}");
}
#[test]
fn test_q_exponential_near_classical() {
let val = q_exponential(1.0, 0.999).expect("q_exponential near classical");
assert!(
(val - std::f64::consts::E).abs() < 0.01,
"val = {val}, e = {}",
std::f64::consts::E
);
}
#[test]
fn test_q_logarithm_at_one() {
for q in &[0.1, 0.5, 0.9, 2.0] {
let val = q_logarithm(1.0, *q).expect("q_logarithm(1)");
assert!(val.abs() < 1e-14, "q={q}: val = {val}");
}
}
#[test]
fn test_q_logarithm_classical_limit() {
let val = q_logarithm(std::f64::consts::E, 1.0 - 1e-9).expect("q_log classical");
assert!((val - 1.0).abs() < 1e-5, "val = {val}");
}
}
pub fn basic_hypergeometric_2phi1(a: f64, b: f64, c: f64, q: f64, z: f64) -> SpecialResult<f64> {
if q.abs() >= 1.0 {
return Err(SpecialError::DomainError(format!(
"basic_hypergeometric_2phi1: |q| must be < 1, got q = {q}"
)));
}
if z.abs() >= 1.0 {
return Err(SpecialError::DomainError(format!(
"basic_hypergeometric_2phi1: |z| must be < 1 for convergence, got z = {z}"
)));
}
let mut poch_a = 1.0f64; let mut poch_b = 1.0f64; let mut poch_c = 1.0f64; let mut poch_qq = 1.0f64; let mut z_pow = 1.0f64; let mut q_pow = 1.0f64;
let mut sum = 0.0f64;
for n in 0..MAX_SERIES_TERMS {
let denom = poch_c * poch_qq;
if denom.abs() < 1e-300 {
return Err(SpecialError::ComputationError(format!(
"basic_hypergeometric_2phi1: denominator vanished at n = {n} \
(c may be a non-positive power of q)"
)));
}
let term = poch_a * poch_b / denom * z_pow;
sum += term;
if n >= 5 && term.abs() < CONVERGENCE_TOL * sum.abs().max(1e-300) {
return Ok(sum);
}
if !sum.is_finite() {
return Err(SpecialError::OverflowError(
"basic_hypergeometric_2phi1: partial sum overflowed".to_string(),
));
}
let qn = q_pow; poch_a *= 1.0 - a * qn;
poch_b *= 1.0 - b * qn;
poch_c *= 1.0 - c * qn;
poch_qq *= 1.0 - q * qn;
z_pow *= z;
q_pow *= q;
if poch_a.abs() < 1e-300 || poch_b.abs() < 1e-300 {
return Ok(sum);
}
}
if sum.is_finite() {
Ok(sum)
} else {
Err(SpecialError::ConvergenceError(
"basic_hypergeometric_2phi1: series did not converge".to_string(),
))
}
}
#[cfg(test)]
mod advanced_q_tests {
use super::*;
#[test]
fn test_2phi1_unit_term() {
let val = basic_hypergeometric_2phi1(0.0, 0.5, 0.5, 0.3, 0.0).expect("2phi1 z=0");
assert!((val - 1.0).abs() < 1e-14, "val = {val}");
}
#[test]
fn test_2phi1_q_geometric_series() {
let q = 0.5f64;
let val = basic_hypergeometric_2phi1(0.1, 0.2, 0.7, q, 0.3).expect("2phi1 general");
assert!(val.is_finite(), "val not finite: {val}");
assert!(val > 0.0, "val should be positive for these params");
}
#[test]
fn test_2phi1_terminating() {
let q = 0.5f64;
let n_terms = 3usize;
let a = q.powi(-(n_terms as i32)); let b = 0.5f64;
let c = 0.75f64;
let z = 0.4f64;
let val = basic_hypergeometric_2phi1(a, b, c, q, z).expect("2phi1 terminating");
assert!(val.is_finite(), "terminating series should be finite: {val}");
}
#[test]
fn test_2phi1_domain_error_q_too_large() {
let result = basic_hypergeometric_2phi1(0.5, 0.5, 0.5, 1.5, 0.3);
assert!(result.is_err());
}
#[test]
fn test_2phi1_domain_error_z_too_large() {
let result = basic_hypergeometric_2phi1(0.5, 0.5, 0.5, 0.5, 1.5);
assert!(result.is_err());
}
#[test]
fn test_2phi1_symmetry_in_a_b() {
let (a, b, c, q, z) = (0.3, 0.6, 0.8, 0.4, 0.2);
let v1 = basic_hypergeometric_2phi1(a, b, c, q, z).expect("v1");
let v2 = basic_hypergeometric_2phi1(b, a, c, q, z).expect("v2");
assert!((v1 - v2).abs() < 1e-12, "symmetry: {v1} vs {v2}");
}
}