use pounce_common::types::{Index, Number};
const FLOOR_BAND: Number = 10.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct InfPrFloor {
floor: Number,
iters_at_floor: Index,
samples: Index,
}
impl Default for InfPrFloor {
fn default() -> Self {
Self {
floor: Number::INFINITY,
iters_at_floor: 0,
samples: 0,
}
}
}
impl InfPrFloor {
pub fn observe(&mut self, inf_pr: Number) {
self.samples += 1;
if !inf_pr.is_finite() {
return;
}
if inf_pr * FLOOR_BAND < self.floor {
self.floor = inf_pr;
self.iters_at_floor = 1;
} else if inf_pr <= self.floor * FLOOR_BAND {
self.iters_at_floor += 1;
}
}
pub fn iters_at_floor(&self) -> Index {
self.iters_at_floor
}
pub fn floor(&self) -> Number {
self.floor
}
pub fn samples(&self) -> Index {
self.samples
}
}
#[cfg(test)]
mod tests {
use super::*;
fn feed(values: &[Number]) -> InfPrFloor {
let mut f = InfPrFloor::default();
for &v in values {
f.observe(v);
}
f
}
#[test]
fn an_unobserved_solve_demonstrates_nothing() {
let f = InfPrFloor::default();
assert_eq!(f.iters_at_floor(), 0);
assert_eq!(f.samples(), 0);
assert!(f.floor().is_infinite());
}
#[test]
fn a_floor_returned_to_across_excursions_still_accumulates() {
let mut vals = vec![2.65e-1, 6.89e-2, 2.01e-2, 1.00e-2];
for _ in 0..500 {
vals.push(1.04e-2);
vals.push(9.56e1); }
let f = feed(&vals);
assert_eq!(f.floor(), 2.01e-2);
assert_eq!(f.iters_at_floor(), 502);
assert_eq!(f.samples(), 1004);
}
#[test]
fn a_short_solve_cannot_accumulate_a_long_floor() {
let vals: Vec<Number> = (0..20).map(|k| 2.72e-1 * (1.0 + k as Number)).collect();
assert!(feed(&vals).iters_at_floor() <= 20);
}
#[test]
fn a_slow_steady_grind_downwards_is_not_a_floor() {
let vals: Vec<Number> = (0..2000).map(|k| 1.0e3 * 0.9_f64.powi(k)).collect();
let f = feed(&vals);
assert_eq!(f.samples(), 2000);
assert!(
f.iters_at_floor() <= 25,
"a solve still descending must not accumulate a floor: {}",
f.iters_at_floor()
);
}
#[test]
fn monotone_divergence_accumulates_nothing_however_long_it_runs() {
let vals: Vec<Number> = (0..5000).map(|k| 1.0e-3 * 100.0_f64.powi(k)).collect();
let f = feed(&vals);
assert_eq!(f.samples(), 5000);
assert_eq!(f.iters_at_floor(), 1);
}
#[test]
fn the_band_holds_wander_and_a_real_drop_restarts_the_count() {
let f = feed(&[1.0e6, 9.0e5, 3.0e6, 8.0e5]);
assert_eq!(f.floor(), 1.0e6);
assert_eq!(f.iters_at_floor(), 4);
let g = feed(&[1.0e6, 1.0e6, 1.0e6, 1.0e1]);
assert_eq!(g.floor(), 1.0e1);
assert_eq!(g.iters_at_floor(), 1);
}
#[test]
fn non_finite_iterates_neither_count_nor_corrupt_the_floor() {
let f = feed(&[5.0, Number::NAN, Number::INFINITY, 5.0]);
assert_eq!(f.floor(), 5.0);
assert_eq!(f.iters_at_floor(), 2);
assert_eq!(f.samples(), 4);
}
}