use std::f64::consts::{PI, TAU};
use super::Cdf;
use super::studentt::StudentT;
use crate::errors::QlResult;
use crate::math::comparison::sign;
use crate::require;
use crate::types::Real;
const EPSILON: Real = 1.0e-8;
#[derive(Clone, Copy, Debug)]
pub struct BivariateCumulativeStudentDistribution {
n: u32,
rho: Real,
}
impl BivariateCumulativeStudentDistribution {
pub fn new(n: u32, rho: Real) -> QlResult<Self> {
require!(n >= 1, "degrees of freedom must be >= 1, got {n}");
require!(
(-1.0..=1.0).contains(&rho),
"correlation rho must be in [-1, 1], got {rho}"
);
Ok(BivariateCumulativeStudentDistribution { n, rho })
}
pub fn value(&self, x: Real, y: Real) -> Real {
if x.is_nan() || y.is_nan() {
return Real::NAN;
}
if (x.is_infinite() && x < 0.0) || (y.is_infinite() && y < 0.0) {
return 0.0;
}
if x.is_infinite() || y.is_infinite() {
let t = StudentT::new(Real::from(self.n)).expect("n >= 1 is a valid Student-t df");
return if x.is_infinite() { t.cdf(y) } else { t.cdf(x) };
}
p_n(x, y, self.n, self.rho)
}
}
fn arctan(x: Real, y: Real) -> Real {
let res = x.atan2(y);
if res >= 0.0 { res } else { res + TAU }
}
fn f_x(m: Real, h: Real, k: Real, rho: Real) -> Real {
let un_cor = 1.0 - rho * rho;
let sub = (h - rho * k).powi(2);
let denom = sub + un_cor * (m + k * k);
if denom < EPSILON {
return 0.0;
}
sub / denom
}
fn even_sum(x_term: Real, div_term: Real, sgn: Real, n: u32) -> Real {
let dg_m = 2.0 * (1.0 - x_term);
let gj_m = sgn * 2.0 / PI;
let mut f_j = (PI / div_term).sqrt();
let mut g_j = 1.0 + gj_m * arctan(x_term.sqrt(), (1.0 - x_term).sqrt());
let mut sum = f_j * g_j;
if n >= 4 {
f_j *= 0.5 / div_term;
let mut dgj = gj_m * (x_term * (1.0 - x_term)).sqrt();
g_j += dgj;
sum += f_j * g_j;
for j in 3..=n / 2 {
let jf = Real::from(j);
f_j *= (jf - 1.5) / (jf - 1.0) / div_term;
dgj *= (jf - 2.0) / (2.0 * jf - 3.0) * dg_m;
g_j += dgj;
sum += f_j * g_j;
}
}
sum
}
fn odd_sum(x_term: Real, div_term: Real, sgn: Real, n: u32) -> Real {
let mult = (1.0 - x_term) / 2.0;
let mut f_j = 2.0 / PI.sqrt() / div_term;
let mut dgj = sgn * x_term.sqrt();
let mut g_j = 1.0 + dgj;
let mut sum = f_j * g_j;
for j in 2..=(n - 1) / 2 {
let jf = Real::from(j);
f_j *= (jf - 1.0) / (jf - 0.5) / div_term;
dgj *= (2.0 * jf - 3.0) / (jf - 1.0) * mult;
g_j += dgj;
sum += f_j * g_j;
}
sum
}
fn p_n(h: Real, k: Real, n: u32, rho: Real) -> Real {
let un_cor = 1.0 - rho * rho;
let nf = Real::from(n);
let div = 4.0 * (nf * PI).sqrt();
let x_hk = f_x(nf, h, k, rho);
let x_kh = f_x(nf, k, h, rho);
let div_h = 1.0 + h * h / nf;
let div_k = 1.0 + k * k / nf;
let sgn_hk = sign(h - rho * k);
let sgn_kh = sign(k - rho * h);
if n.is_multiple_of(2) {
let mut res = arctan(un_cor.sqrt(), -rho) / TAU;
res += k / div * even_sum(x_hk, div_k, sgn_hk, n);
res += h / div * even_sum(x_kh, div_h, sgn_kh, n);
res
} else {
let hk = h * k;
let hkcn = hk + rho * nf;
let sqrt_expr = (h * h - 2.0 * rho * hk + k * k + nf * un_cor).sqrt();
let mut res = arctan(
nf.sqrt() * (-(h + k) * hkcn - (hk - nf) * sqrt_expr),
(hk - nf) * hkcn - nf * (h + k) * sqrt_expr,
) / TAU;
if n > 1 {
res += k / div * odd_sum(x_hk, div_k, sgn_hk, n);
res += h / div * odd_sum(x_kh, div_h, sgn_kh, n);
}
res
}
}
#[cfg(test)]
mod tests {
use super::*;
type Biv = BivariateCumulativeStudentDistribution;
#[rustfmt::skip]
const XS: [Real; 14] = [
0.00, 0.50, 1.00, 1.50, 2.00, 2.50, 3.00,
4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00,
];
const NS: [u32; 20] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 60, 90, 120, 150, 300, 600,
];
#[rustfmt::skip]
const EXPECTED_POS: [Real; 280] = [
0.33333, 0.50000, 0.63497, 0.72338, 0.78063, 0.81943, 0.84704,
0.88332, 0.90590, 0.92124, 0.93231, 0.94066, 0.94719, 0.95243,
0.33333, 0.52017, 0.68114, 0.78925, 0.85607, 0.89754, 0.92417,
0.95433, 0.96978, 0.97862, 0.98411, 0.98774, 0.99026, 0.99208,
0.33333, 0.52818, 0.70018, 0.81702, 0.88720, 0.92812, 0.95238,
0.97667, 0.98712, 0.99222, 0.99497, 0.99657, 0.99756, 0.99821,
0.33333, 0.53245, 0.71052, 0.83231, 0.90402, 0.94394, 0.96612,
0.98616, 0.99353, 0.99664, 0.99810, 0.99885, 0.99927, 0.99951,
0.33333, 0.53510, 0.71701, 0.84196, 0.91449, 0.95344, 0.97397,
0.99095, 0.99637, 0.99836, 0.99918, 0.99956, 0.99975, 0.99985,
0.33333, 0.53689, 0.72146, 0.84862, 0.92163, 0.95972, 0.97893,
0.99365, 0.99779, 0.99913, 0.99962, 0.99982, 0.99990, 0.99995,
0.33333, 0.53819, 0.72470, 0.85348, 0.92679, 0.96415, 0.98230,
0.99531, 0.99857, 0.99950, 0.99981, 0.99992, 0.99996, 0.99998,
0.33333, 0.53917, 0.72716, 0.85719, 0.93070, 0.96743, 0.98470,
0.99639, 0.99903, 0.99970, 0.99990, 0.99996, 0.99998, 0.99999,
0.33333, 0.53994, 0.72909, 0.86011, 0.93375, 0.96995, 0.98650,
0.99713, 0.99931, 0.99981, 0.99994, 0.99998, 0.99999, 1.00000,
0.33333, 0.54056, 0.73065, 0.86247, 0.93621, 0.97194, 0.98788,
0.99766, 0.99950, 0.99988, 0.99996, 0.99999, 1.00000, 1.00000,
0.33333, 0.54243, 0.73540, 0.86968, 0.94362, 0.97774, 0.99168,
0.99890, 0.99985, 0.99998, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54338, 0.73781, 0.87336, 0.94735, 0.98053, 0.99337,
0.99932, 0.99993, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54395, 0.73927, 0.87560, 0.94959, 0.98216, 0.99430,
0.99952, 0.99996, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54433, 0.74025, 0.87709, 0.95108, 0.98322, 0.99489,
0.99963, 0.99998, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54528, 0.74271, 0.88087, 0.95482, 0.98580, 0.99623,
0.99983, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54560, 0.74354, 0.88215, 0.95607, 0.98663, 0.99664,
0.99987, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54576, 0.74396, 0.88279, 0.95669, 0.98704, 0.99683,
0.99989, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54586, 0.74420, 0.88317, 0.95706, 0.98729, 0.99695,
0.99990, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54605, 0.74470, 0.88394, 0.95781, 0.98777, 0.99717,
0.99992, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.33333, 0.54615, 0.74495, 0.88432, 0.95818, 0.98801, 0.99728,
0.99993, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
];
#[rustfmt::skip]
const EXPECTED_NEG: [Real; 280] = [
0.16667, 0.36554, 0.54022, 0.65333, 0.72582, 0.77465, 0.80928,
0.85466, 0.88284, 0.90196, 0.91575, 0.92616, 0.93429, 0.94081,
0.16667, 0.38889, 0.59968, 0.73892, 0.82320, 0.87479, 0.90763,
0.94458, 0.96339, 0.97412, 0.98078, 0.98518, 0.98823, 0.99044,
0.16667, 0.39817, 0.62478, 0.77566, 0.86365, 0.91391, 0.94330,
0.97241, 0.98483, 0.99086, 0.99410, 0.99598, 0.99714, 0.99790,
0.16667, 0.40313, 0.63863, 0.79605, 0.88547, 0.93396, 0.96043,
0.98400, 0.99256, 0.99614, 0.99782, 0.99868, 0.99916, 0.99944,
0.16667, 0.40620, 0.64740, 0.80900, 0.89902, 0.94588, 0.97007,
0.98972, 0.99591, 0.99816, 0.99909, 0.99951, 0.99972, 0.99983,
0.16667, 0.40829, 0.65345, 0.81794, 0.90820, 0.95368, 0.97607,
0.99290, 0.99755, 0.99904, 0.99958, 0.99980, 0.99989, 0.99994,
0.16667, 0.40980, 0.65788, 0.82449, 0.91482, 0.95914, 0.98010,
0.99482, 0.99844, 0.99946, 0.99979, 0.99991, 0.99996, 0.99998,
0.16667, 0.41095, 0.66126, 0.82948, 0.91981, 0.96314, 0.98295,
0.99605, 0.99895, 0.99968, 0.99989, 0.99996, 0.99998, 0.99999,
0.16667, 0.41185, 0.66393, 0.83342, 0.92369, 0.96619, 0.98506,
0.99689, 0.99926, 0.99980, 0.99994, 0.99998, 0.99999, 1.00000,
0.16667, 0.41257, 0.66608, 0.83661, 0.92681, 0.96859, 0.98667,
0.99748, 0.99946, 0.99987, 0.99996, 0.99999, 1.00000, 1.00000,
0.16667, 0.41476, 0.67268, 0.84633, 0.93614, 0.97550, 0.99103,
0.99884, 0.99984, 0.99998, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41586, 0.67605, 0.85129, 0.94078, 0.97877, 0.99292,
0.99930, 0.99993, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41653, 0.67810, 0.85430, 0.94356, 0.98066, 0.99396,
0.99950, 0.99996, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41698, 0.67947, 0.85632, 0.94540, 0.98189, 0.99461,
0.99962, 0.99998, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41810, 0.68294, 0.86141, 0.94998, 0.98483, 0.99607,
0.99982, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41847, 0.68411, 0.86312, 0.95149, 0.98577, 0.99651,
0.99987, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41866, 0.68470, 0.86398, 0.95225, 0.98623, 0.99672,
0.99989, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41877, 0.68505, 0.86449, 0.95270, 0.98650, 0.99684,
0.99990, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41900, 0.68576, 0.86552, 0.95360, 0.98705, 0.99707,
0.99992, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
0.16667, 0.41911, 0.68612, 0.86604, 0.95405, 0.98731, 0.99719,
0.99993, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000,
];
#[rustfmt::skip]
const CASES: [(u32, Real, Real, Real, Real); 66] = [
(2, -1.0, 5.0, 8.0, 0.973491), (2, 1.0, -2.0, 8.0, 0.091752),
(2, 1.0, 5.25, -9.5, 0.005450), (3, -0.5, -5.0, -5.0, 0.000220),
(4, -1.0, -8.0, 7.5, 0.0), (4, 0.5, -5.5, 10.0, 0.002655),
(4, 1.0, -5.0, 6.0, 0.003745), (4, 1.0, 6.0, 5.5, 0.997336),
(5, -0.5, -7.0, -6.25, 0.000004), (5, -0.5, 3.75, -7.25, 0.000166),
(5, -0.5, 7.75, -1.25, 0.133073), (6, 0.0, 7.5, 3.25, 0.991149),
(7, -0.5, -1.0, -8.5, 0.000001), (7, -1.0, -4.25, -4.0, 0.0),
(7, 0.0, 0.5, -2.25, 0.018819), (8, -1.0, 8.25, 1.75, 0.940866),
(8, 0.0, 2.25, 4.75, 0.972105), (9, -0.5, -4.0, 8.25, 0.001550),
(9, -1.0, -1.25, -8.75, 0.0), (9, -1.0, 5.75, -6.0, 0.0),
(9, 0.5, -6.5, -9.5, 0.000001), (9, 1.0, -2.0, 9.25, 0.038276),
(10, -1.0, -0.5, 6.0, 0.313881), (10, 0.5, 0.0, 9.25, 0.5),
(10, 0.5, 6.75, -2.25, 0.024090), (10, 1.0, -1.75, -1.0, 0.055341),
(15, 0.0, -1.25, -4.75, 0.000029), (15, 0.0, -2.0, -1.5, 0.003411),
(15, 0.5, 3.0, -3.25, 0.002691), (20, -0.5, 2.0, -1.25, 0.098333),
(20, -1.0, 3.0, 8.0, 0.996462), (20, 0.0, -7.5, 1.5, 0.0),
(20, 0.5, 1.25, 9.75, 0.887136), (25, -1.0, -4.25, 5.0, 0.000111),
(25, 0.5, 9.5, -1.5, 0.073069), (25, 1.0, -6.5, -3.25, 0.0),
(30, -1.0, -7.75, 10.0, 0.0), (30, 1.0, 0.5, 9.5, 0.689638),
(60, -1.0, -3.5, -8.25, 0.0), (60, -1.0, 4.25, 0.75, 0.771869),
(60, -1.0, 5.75, 3.75, 0.9998), (60, 0.5, -4.5, 8.25, 0.000016),
(60, 1.0, 6.5, -4.0, 0.000088), (90, -0.5, -3.75, -2.75, 0.0),
(90, 0.5, 8.75, -7.0, 0.0), (120, 0.0, -3.5, -9.25, 0.0),
(120, 0.0, -8.25, 5.0, 0.0), (120, 1.0, -0.75, 3.75, 0.227361),
(120, 1.0, -3.5, -8.0, 0.0), (150, 0.0, 10.0, -1.75, 0.041082),
(300, -0.5, -6.0, 3.75, 0.0), (300, -0.5, 3.5, -4.5, 0.000004),
(300, 0.0, 6.5, -5.0, 0.0), (600, -0.5, 9.25, 1.5, 0.93293),
(600, -1.0, -9.25, 1.5, 0.0), (600, 0.5, -5.0, 8.0, 0.0),
(600, 1.0, -2.75, -9.0, 0.0), (1000, -0.5, -2.5, 0.25, 0.000589),
(1000, -0.5, 3.0, 1.0, 0.839842), (2000, -1.0, 9.0, -4.75, 0.000001),
(2000, 0.5, 9.75, 7.25, 1.0), (2000, 1.0, 0.75, -9.0, 0.0),
(5000, -0.5, 9.75, 5.5, 1.0), (5000, -1.0, 6.0, 1.0, 0.841321),
(5000, 1.0, 4.0, -7.75, 0.0), (10000, 0.5, 1.5, 6.0, 0.933177),
];
#[test]
fn matches_reference_tables() {
for (i, &n) in NS.iter().enumerate() {
let f1 = Biv::new(n, 0.5).unwrap();
let f2 = Biv::new(n, -0.5).unwrap();
for (j, &x) in XS.iter().enumerate() {
let idx = i * XS.len() + j;
let g1 = f1.value(x, x);
let g2 = f2.value(x, x);
assert!(
(g1 - EXPECTED_POS[idx]).abs() < 1e-5,
"n={n} x={x} +: {g1} vs {}",
EXPECTED_POS[idx]
);
assert!(
(g2 - EXPECTED_NEG[idx]).abs() < 1e-5,
"n={n} x={x} -: {g2} vs {}",
EXPECTED_NEG[idx]
);
}
}
}
#[test]
fn matches_reference_cases() {
for (n, rho, x, y, expected) in CASES {
let got = Biv::new(n, rho).unwrap().value(x, y);
assert!(
(got - expected).abs() < 1e-6,
"n={n} rho={rho} ({x},{y}): {got} vs {expected}"
);
}
}
#[test]
fn boundary_and_construction() {
let f = Biv::new(5, 0.5).unwrap();
assert!(f.value(Real::NAN, 0.0).is_nan());
let t = StudentT::new(5.0).unwrap();
assert_eq!(f.value(Real::INFINITY, 1.0), t.cdf(1.0));
assert_eq!(f.value(1.0, Real::INFINITY), t.cdf(1.0));
assert_eq!(f.value(Real::NEG_INFINITY, 0.0), 0.0);
assert_eq!(f.value(0.0, Real::NEG_INFINITY), 0.0);
assert!(Biv::new(0, 0.5).is_err());
assert!(Biv::new(5, 1.5).is_err());
assert!(Biv::new(5, Real::NAN).is_err());
}
}