use crate::elementary::Power;
use crate::special::gamma::Gamma;
use crate::{algebra::abstr::Real, special::gamma};
pub trait Hypergeometric {
fn f21(self, b: Self, c: Self, z: Self) -> Self;
}
macro_rules! impl_hypergeometric {
($t: ty, $norm_name: ident, $tolerance: expr, $infinity: expr) => {
fn $norm_name(a: $t, b: $t, c: $t, z: $t) -> $t {
let mut c_i: $t = 1.0;
let mut s_i: $t = c_i;
let mut s_i_p: $t = s_i; let mut j: $t = 0.0;
while c_i.abs() / s_i_p.abs() > $tolerance {
let k: $t = (a + j) * (b + j) / (c + j);
let l: $t = z / (j + 1.0);
c_i = c_i * k * l;
s_i_p = s_i;
s_i += c_i;
j += 1.0;
}
s_i
}
impl Hypergeometric for $t {
fn f21(self, b: Self, c: Self, z: Self) -> Self {
if self <= 0.0 || b <= 0.0 || c <= 0.0 || z > 1.0 {
panic!();
}
if z == 1.0 {
if c - self - b < 0.0 {
return c.gamma() * (self + b - c).gamma() / (self.gamma() * b.gamma());
} else {
if c - self - b == 0.0 {
return gamma::gamma(c) / (gamma::gamma(self) * gamma::gamma(b));
} else {
return gamma::gamma(c) * gamma::gamma(c - self - b)
/ (gamma::gamma(c - self) * gamma::gamma(c - b));
}
}
}
let f: Self;
if -$infinity < z && z < -1.0 {
let l1: Self = (1.0 - z).pow(-self) * gamma::gamma(c) * gamma::gamma(b - self)
/ (gamma::gamma(b) * gamma::gamma(c - self));
let l2: Self = (1.0 - z).pow(-b) * gamma::gamma(c) * gamma::gamma(self - b)
/ (gamma::gamma(self) * gamma::gamma(c - b));
let f1: Self = $norm_name(self, c - b, self - b + 1.0, 1.0 / (1.0 - z));
let f2: Self = $norm_name(b, c - self, b - self + 1.0, 1.0 / (1.0 - z));
f = l1 * f1 + l2 * f2;
} else {
if (-1.0..0.0).contains(&z) {
f = $norm_name(self, c - b, c, z / (z - 1.0)) * (1.0 - z).pow(-self);
} else {
if (0.0..=0.5).contains(&z) {
f = $norm_name(self, b, c, z);
} else {
if (0.5..=1.0).contains(&z) {
let l1: Self = gamma::gamma(c) * gamma::gamma(c - self - b)
/ (gamma::gamma(c - self) * gamma::gamma(c - b));
let l2: Self = (1.0 - z).pow(c - self - b)
* gamma::gamma(c)
* gamma::gamma(self + b - c)
/ (gamma::gamma(self) * gamma::gamma(b));
let f1: Self = $norm_name(self, b, self + b - c + 1.0, 1.0 - z);
let f2: Self =
$norm_name(c - self, c - b, c - self - b + 1.0, 1.0 - z);
f = l1 * f1 + l2 * f2;
} else {
if 1.0 < z && z <= 2.0 {
unimplemented!();
}
else {
unimplemented!();
}
}
}
}
}
return f;
}
}
};
}
impl_hypergeometric!(f64, f21_f64_norm, 0.0000000000000002, f64::INFINITY);
impl_hypergeometric!(f32, f21_f32_norm, 0.0000000000000002, f32::INFINITY);
pub fn f21<T>(a: T, b: T, c: T, z: T) -> T
where
T: Real + Hypergeometric,
{
a.f21(b, c, z)
}