#[derive(Debug, Clone, Copy)]
pub struct TheoremOneBound {
pub initial_gap: f64,
pub drift_rate: f64,
pub envelope_expansion_rate: f64,
pub exit_bound_cycles: u32,
pub observed_transition_cycle: Option<u32>,
pub drift_onset_cycle: Option<u32>,
pub bound_satisfied: bool,
}
impl TheoremOneBound {
#[must_use]
pub fn compute(
initial_gap: f64,
drift_rate: f64,
envelope_expansion_rate: f64,
observed_transition: Option<u32>,
drift_onset: Option<u32>,
) -> Self {
let net_rate = drift_rate - envelope_expansion_rate;
let exit_bound = if net_rate > 1e-15 {
let raw = initial_gap / net_rate;
ceil_nonnegative_to_u32(raw)
} else {
u32::MAX };
let bound_satisfied = match (observed_transition, drift_onset) {
(Some(obs), Some(onset)) if obs >= onset => {
(obs - onset) <= exit_bound
}
_ => false,
};
Self {
initial_gap,
drift_rate,
envelope_expansion_rate,
exit_bound_cycles: exit_bound,
observed_transition_cycle: observed_transition,
drift_onset_cycle: drift_onset,
bound_satisfied,
}
}
}
#[must_use]
fn ceil_nonnegative_to_u32(x: f64) -> u32 {
if !x.is_finite() {
return u32::MAX;
}
if x <= 0.0 {
return 0;
}
let truncated = x as u32;
if x > truncated as f64 {
truncated.saturating_add(1)
} else {
truncated
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_theorem_bound() {
let bound = TheoremOneBound::compute(
10.0, 0.5, 0.0, Some(25), Some(5), );
assert_eq!(bound.exit_bound_cycles, 20);
assert!(bound.bound_satisfied);
}
#[test]
fn test_theorem_bound_not_satisfied() {
let bound = TheoremOneBound::compute(
5.0,
0.1,
0.0,
Some(100),
Some(5),
);
assert!(!bound.bound_satisfied);
}
}