use super::{fast_ln, fast_ln_gamma, ln_gamma_increment, ln_gamma_increments, LnTable};
use special::Gamma;
fn grid(lo: f64, hi: f64, ratio: f64) -> Vec<f64> {
let mut xs = Vec::new();
let mut x = lo;
while x <= hi {
xs.push(x);
x *= ratio;
}
xs
}
fn close(got: f64, want: f64) -> bool {
(got - want).abs() <= 1e-2 * want.abs() + 0.03
}
#[test]
fn fast_ln_is_near_the_library() {
for x in grid(1e-30, 1e30, 1.37) {
let got = f64::from(fast_ln(x as f32));
assert!(close(got, x.ln()), "ln({x}): {got} against {}", x.ln());
}
}
#[test]
fn fast_ln_gamma_is_near_the_library() {
for x in grid(1e-3, 1e12, 1.13) {
let want = Gamma::ln_gamma(x).0;
let got = f64::from(fast_ln_gamma(x as f32));
assert!(close(got, want), "ln Γ({x}): {got} against {want}");
}
}
#[test]
fn increments_hold_at_large_counts() {
for x in grid(1.0, 1e9, 3.1) {
for y in [1usize, 2, 7, 100, 10_000] {
let want: f64 = (0..y).map(|i| (x + i as f64).ln()).sum();
let got = f64::from(ln_gamma_increment(x as f32, y as f32));
assert!(
close(got, want),
"ln Γ({x} + {y}) − ln Γ({x}): {got} against {want}"
);
}
}
}
#[test]
fn the_error_of_an_increment_scales_with_its_count() {
for x in grid(1e-3, 1e7, 1.31) {
for y in grid(1e-3, 1e4, 1.9) {
let want = Gamma::ln_gamma(x + y).0 - Gamma::ln_gamma(x).0;
let got = f64::from(ln_gamma_increment(x as f32, y as f32));
let resolution = 1e-14 * Gamma::ln_gamma(x + y).0.abs();
assert!(
(got - want).abs() <= 1e-2 * y + 2e-4 + resolution,
"ln Γ({x} + {y}) − ln Γ({x}): {got} against {want}"
);
}
}
}
#[test]
fn small_counts_do_not_pile_up_a_bias_on_empty_features() {
for x in grid(0.25, 20.0, 1.01) {
let y = 0.02;
let want = Gamma::ln_gamma(x + y).0 - Gamma::ln_gamma(x).0;
let got = f64::from(ln_gamma_increment(x as f32, y as f32));
assert!(
5000.0 * (got - want).abs() < 1.0,
"x = {x}: {got} against {want}"
);
}
}
fn jump_error(below: f64, above: f64, y: f64) -> f64 {
let approx = |x: f64| f64::from(ln_gamma_increment(x as f32, y as f32));
let exact = |x: f64| Gamma::ln_gamma(x + y).0 - Gamma::ln_gamma(x).0;
((approx(above) - approx(below)) - (exact(above) - exact(below))).abs()
}
#[test]
fn increments_barely_jump_where_the_forms_meet() {
for y in [1e2f64, 1e3, 1e4, 1e5, 1e6] {
let error = jump_error(y * 0.999, y * 1.001, y);
assert!(error <= 2e-4 * y, "y = {y}: a jump off by {error}");
}
}
#[test]
fn increments_barely_jump_where_the_cached_form_ends() {
for y in [
1e-1f64, 0.5, 1.0, 1.05, 1.2, 1.35, 2.0, 3.0, 5.0, 1e1, 1e2, 1e3, 1e4, 1e5,
] {
let seam = 8.0 * y.max(1.0);
let error = jump_error(seam * 0.999, seam * 1.001, y);
assert!(error <= 2e-4 * y + 2e-4, "y = {y}: a jump off by {error}");
}
}
#[test]
fn increments_stay_within_what_fast_ln_resolves_around_the_seam() {
for y in [1.0f64, 1.2, 2.0, 4.0, 8.0, 1e1, 1e2, 1e3, 1e4] {
for x in grid(4.0 * y.max(2.0), 64.0 * y, 1.03) {
let want = Gamma::ln_gamma(x + y).0 - Gamma::ln_gamma(x).0;
let got = f64::from(ln_gamma_increment(x as f32, y as f32));
assert!(
(got - want).abs() <= 1.6e-4 * y + 1e-4,
"ln Γ({x} + {y}) − ln Γ({x}): {got} against {want}, {:.2e} of y",
(got - want).abs() / y
);
}
}
}
#[test]
fn increments_survive_very_large_counts() {
for y in [1e8f64, 1e9, 1e10, 1e11, 1e12] {
for x in grid(y, 1e4 * y, 3.1) {
let want = Gamma::ln_gamma(x + y).0 - Gamma::ln_gamma(x).0;
let got = f64::from(ln_gamma_increment(x as f32, y as f32));
assert!(
(got - want).abs() <= 1e-2 * y,
"ln Γ({x} + {y}) − ln Γ({x}): {got} against {want}"
);
}
}
}
#[test]
fn a_run_of_increments_is_the_scalar_bit_for_bit() {
let xs: Vec<f32> = grid(1e-3, 1e13, 1.7).iter().map(|&x| x as f32).collect();
let mut table = LnTable::new(xs.len());
for (i, &x) in xs.iter().enumerate() {
table.set(i, x);
}
let mut out = vec![0.0; xs.len()];
for y in grid(1e-3, 1e12, 3.3) {
let y = y as f32;
for start in [0, 7] {
let out = &mut out[start..];
ln_gamma_increments(table.run(start..xs.len()), y, out);
for (&x, &got) in xs[start..].iter().zip(out.iter()) {
assert_eq!(
got.to_bits(),
ln_gamma_increment(x, y).to_bits(),
"{x}, {y}"
);
}
}
}
}
#[test]
fn a_cell_never_set_gives_no_plausible_increment() {
let table = LnTable::new(1);
let mut out = [0.0];
ln_gamma_increments(table.run(0..1), 2.0, &mut out);
assert!(out[0].is_nan(), "{}", out[0]);
}