use super::common::{random_left_bounded_gamma, upper_incomplete_gamma};
use super::float::Float;
use numeric_literals::replace_float_literals;
use rand::Rng;
use rand_distr::{Distribution, Exp1, StandardNormal, StandardUniform};
#[replace_float_literals(T::from(literal).unwrap())]
pub fn sample_polyagamma_saddlepoint<T: Float, R: Rng>(rng: &mut R, h: T, z: T) -> T
where
StandardNormal: Distribution<T>,
StandardUniform: Distribution<T>,
Exp1: Distribution<T>,
{
let mut pr = Parameters::new(h, 0.5 * z.abs());
let sqrt_rho = (-2.0 * pr.left_tangent_slope).sqrt();
let sqrt_rho_inv = sqrt_rho.recip();
let p = (h * (0.5 / pr.xc + pr.left_tangent_intercept - sqrt_rho)
+ invgauss_logcdf(pr.xc, sqrt_rho_inv, h))
.exp()
+ pr.sqrt_alpha;
assert!(p.is_finite());
let hrho = -h * pr.right_tangent_slope;
let q = ((upper_incomplete_gamma(h, hrho * pr.xc, false) * pr.right_kernel_coef).ln()
+ (h * (pr.right_tangent_intercept - hrho.ln())))
.exp();
assert!(q.is_finite());
let proposal_probability = p / (p + q);
let mu2 = sqrt_rho_inv * sqrt_rho_inv;
loop {
let u = T::from(rng.random::<f32>()).unwrap();
if u < proposal_probability {
loop {
let y = rng.sample::<T, StandardNormal>(StandardNormal);
let w = sqrt_rho_inv + 0.5 * mu2 * y * y / h;
pr.x = w - (w * w - mu2).abs().sqrt();
if rng.sample::<T, StandardUniform>(StandardUniform) * (1.0 + pr.x * sqrt_rho) > 1.0
{
pr.x = mu2 / pr.x;
}
if pr.x < pr.xc {
break;
}
}
} else {
pr.x = random_left_bounded_gamma(rng, h, hrho, pr.xc);
}
if T::from(rng.random::<f32>()).unwrap() * bounding_kernel(&pr) <= saddle_point(&pr) {
break;
}
}
0.25 * h * pr.x
}
struct Parameters<T: Float> {
right_tangent_intercept: T,
left_tangent_intercept: T,
right_tangent_slope: T,
left_tangent_slope: T,
right_kernel_coef: T,
left_kernel_coef: T,
sqrt_alpha: T,
log_cosh_z: T,
sqrt_h2pi: T,
half_z2: T,
logxc: T,
xc: T,
h: T,
x: T,
}
impl<T: Float> Parameters<T> {
#[replace_float_literals(T::from(literal).unwrap())]
fn new(h: T, z: T) -> Self {
let log275 = 1.0116009116784799;
let log3 = 1.0986122886681098;
let xl;
let logxl;
let half_z2;
let log_cosh_z;
if z > 0. {
xl = tanh_x(z);
logxl = xl.ln();
half_z2 = 0.5 * (z * z);
log_cosh_z = z.cosh().ln();
} else {
xl = 1.;
logxl = 0.;
half_z2 = 0.;
log_cosh_z = 0.;
}
let xc = 2.75 * xl;
let xr = 3. * xl;
let xc_inv = xc.recip();
let xl_inv = xl.recip();
let ul = -half_z2;
let (ur, _) = newton_raphson(xr, select_starting_guess(xr));
let (_, rv) = newton_raphson(xc, select_starting_guess(xc));
let tr = ur + half_z2;
let left_tangent_slope = -0.5 * (xl_inv * xl_inv);
let left_tangent_intercept = cumulant(ul, log_cosh_z) - 0.5 * xc_inv + xl_inv;
assert!(left_tangent_intercept.is_finite());
let logxc = log275 + logxl;
let right_tangent_slope = -tr - 1. / xr;
let right_tangent_intercept = cumulant(ur, log_cosh_z) + 1.0 - log3 - logxl + logxc;
assert!(right_tangent_intercept.is_finite());
let alpha_r = rv.fprime * (xc_inv * xc_inv); let alpha_l = xc_inv * alpha_r;
let sqrt_alpha = 1.0 / alpha_l.sqrt();
let sqrt_h2pi = (h / T::TAU).sqrt();
let left_kernel_coef = sqrt_h2pi * sqrt_alpha;
let right_kernel_coef = sqrt_h2pi / alpha_r.sqrt();
Parameters {
right_tangent_intercept,
left_tangent_intercept,
right_tangent_slope,
left_tangent_slope,
right_kernel_coef,
left_kernel_coef,
sqrt_alpha,
log_cosh_z,
sqrt_h2pi,
half_z2,
logxc,
xc,
h,
x: 0.0,
}
}
}
#[replace_float_literals(T::from(literal).unwrap())]
fn tanh_x<T: Float>(x: T) -> T {
if x > 4.95 {
return 1. / x;
}
let p0 = -1.613_411_902_399_622_7e3;
let p1 = -9.922_592_967_223_608e1;
let p2 = -9.643_749_277_722_548e-1;
let q0 = 4.840_235_707_198_869e3;
let q1 = 2.233_772_071_896_231_4e3;
let q2 = 1.127_447_438_053_494_9e2;
let x2 = x * x;
1. + x2 * ((p2 * x2 + p1) * x2 + p0) / (((x2 + q2) * x2 + q1) * x2 + q0)
}
fn tan_x<T: Float>(x: T) -> T {
x.tan() / x
}
struct FuncReturnValue<T: Float> {
f: T,
fprime: T,
}
#[replace_float_literals(T::from(literal).unwrap())]
fn cumulant<T: Float>(u: T, log_cosh_z: T) -> T {
if u < 0.0 {
log_cosh_z - (-2.0 * u).sqrt().cosh().ln()
} else if u > 0.0 {
log_cosh_z - (2.0 * u).sqrt().cos().ln()
} else {
log_cosh_z
}
}
#[replace_float_literals(T::from(literal).unwrap())]
fn cumulant_prime<T: Float>(u: T) -> FuncReturnValue<T> {
let s = 2.0 * u;
let f = if s < 0.0 {
tanh_x((-s).sqrt())
} else if s > 0.0 {
tan_x(s.sqrt())
} else {
1.0
};
let fprime = f * f + (1.0 - f) / s;
FuncReturnValue { f, fprime }
}
fn isclose<T: Float>(a: T, b: T, atol: T, rtol: T) -> bool {
(a - b).abs() <= atol.max(rtol * a.abs().max(b.abs()))
}
#[replace_float_literals(T::from(literal).unwrap())]
fn select_starting_guess<T: Float>(x: T) -> T {
if x <= 0.25 {
-9.0
} else if x <= 0.5 {
-1.78
} else if x <= 1.0 {
-0.147
} else if x <= 1.5 {
0.345
} else if x <= 2.5 {
0.72
} else if x <= 4.0 {
0.95
} else {
1.15
}
}
const PGM_MAX_ITER: usize = 25;
#[replace_float_literals(T::from(literal).unwrap())]
fn newton_raphson<T: Float>(arg: T, mut x0: T) -> (T, FuncReturnValue<T>) {
let atol = 1e-05;
let rtol = 1e-05;
let mut n = 0;
let mut x = x0;
let mut value;
loop {
x0 = x;
value = cumulant_prime(x0);
let fval = value.f - arg;
if fval.abs() <= atol || value.fprime <= atol {
return (x0, value);
}
x = x0 - fval / value.fprime;
n += 1;
if isclose(x, x0, atol, rtol) || n >= PGM_MAX_ITER {
break;
}
}
(x, value)
}
fn saddle_point<T: Float>(pr: &Parameters<T>) -> T {
let (u, rv) = newton_raphson(pr.x, select_starting_guess(pr.x));
let t = u + pr.half_z2;
(pr.h * (cumulant(u, pr.log_cosh_z) - t * pr.x)).exp() * pr.sqrt_h2pi / rv.fprime.sqrt()
}
#[replace_float_literals(T::from(literal).unwrap())]
fn bounding_kernel<T: Float>(pr: &Parameters<T>) -> T {
if pr.x > pr.xc {
let point = pr.right_tangent_slope * pr.x + pr.right_tangent_intercept;
(pr.h * (pr.logxc + point) + (pr.h - 1.0) * pr.x.ln()).exp() * pr.right_kernel_coef
} else {
let point = pr.left_tangent_slope * pr.x + pr.left_tangent_intercept;
(0.5 * pr.h * (1.0 / pr.xc - 1.0 / pr.x) + pr.h * point - 1.5 * pr.x.ln()).exp()
* pr.left_kernel_coef
}
}
#[replace_float_literals(T::from(literal).unwrap())]
fn log_norm_cdf<T: Float>(x: T) -> T {
(-0.5 * (x / T::SQRT_2).erfc_()).ln_1p()
}
#[replace_float_literals(T::from(literal).unwrap())]
fn invgauss_logcdf<T: Float>(x: T, mu: T, lambda: T) -> T {
let qm = x / mu;
let tm = mu / lambda;
let r = (x / lambda).sqrt();
let a = log_norm_cdf((qm - 1.) / r);
let b = 2. / tm + log_norm_cdf(-(qm + 1.) / r);
a + (b - a).exp().ln_1p()
}