use tru::{Fx, FocusingResult};
use bbg::Particle;
pub struct Domain {
particles: Vec<Particle>,
focus: Vec<Fx>, }
impl Domain {
pub fn epsilon_support(result: &FocusingResult, node_ids: &[Particle], epsilon: Fx) -> Domain {
let mut particles = Vec::new();
let mut focus = Vec::new();
for (i, id) in node_ids.iter().enumerate() {
if result.focus[i] >= epsilon {
particles.push(*id);
focus.push(result.focus[i]);
}
}
Domain { particles, focus }
}
pub fn from_focus(particles: Vec<Particle>, focus: Vec<Fx>) -> Domain {
debug_assert_eq!(particles.len(), focus.len());
Domain { particles, focus }
}
pub fn len(&self) -> usize {
self.particles.len()
}
pub fn is_empty(&self) -> bool {
self.particles.is_empty()
}
pub fn mean(&self) -> Fx {
if self.focus.is_empty() {
return Fx::ZERO;
}
let mut sum = Fx::ZERO;
for f in &self.focus {
sum = sum + *f;
}
sum.div(Fx::from_int(self.focus.len() as i64))
}
pub fn variance(&self) -> Fx {
if self.focus.is_empty() {
return Fx::ZERO;
}
let mu = self.mean();
let mut sq = Fx::ZERO;
for f in &self.focus {
sq = sq + (*f * *f);
}
let mean_sq = sq.div(Fx::from_int(self.focus.len() as i64));
let mu_sq = mu * mu;
if mean_sq >= mu_sq {
mean_sq - mu_sq
} else {
Fx::ZERO
}
}
pub fn focus_of(&self, particle: &Particle) -> Option<Fx> {
self.particles
.iter()
.position(|p| p == particle)
.map(|i| self.focus[i])
}
}
pub fn crosses_threshold(phi_i: Fx, domain: &Domain, kappa_prime: Fx) -> bool {
let mu = domain.mean();
if phi_i <= mu {
return false;
}
let excess = phi_i - mu;
let lhs = excess * excess; let rhs = (kappa_prime * kappa_prime) * domain.variance(); lhs > rhs
}
pub fn certified(uncert_mass: Fx, gap: Fx, kappa_d: Fx, c: Fx, kappa_prime: Fx) -> bool {
let one = Fx::ONE;
let numer = (one - kappa_d) * gap; let denom = c * (one + kappa_prime); uncert_mass * denom < numer
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Finality {
Final,
Pending,
}
#[allow(clippy::too_many_arguments)]
pub fn finalizes(
phi_i: Fx,
domain: &Domain,
uncert_mass: Fx,
gap: Fx,
kappa_d: Fx,
c: Fx,
kappa_prime: Fx,
) -> Finality {
if crosses_threshold(phi_i, domain, kappa_prime)
&& certified(uncert_mass, gap, kappa_d, c, kappa_prime)
{
Finality::Final
} else {
Finality::Pending
}
}
#[cfg(test)]
mod tests {
use super::*;
fn p(b: u8) -> Particle {
[b; 32]
}
fn fx(n: i64, d: i64) -> Fx {
Fx::from_int(n).div(Fx::from_int(d))
}
#[test]
fn epsilon_support_is_the_superlevel_set() {
let result = FocusingResult {
focus: vec![fx(1, 2), fx(1, 100), fx(3, 10)],
syntropy: Fx::ZERO,
diffusion: vec![],
springs: vec![],
heat: vec![],
};
let ids = vec![p(1), p(2), p(3)];
let d = Domain::epsilon_support(&result, &ids, fx(1, 20));
assert_eq!(d.len(), 2);
assert!(d.focus_of(&p(1)).is_some());
assert!(d.focus_of(&p(2)).is_none());
assert!(d.focus_of(&p(3)).is_some());
}
#[test]
fn mean_and_variance_match_hand_computation() {
let d = Domain::from_focus(vec![p(1), p(2), p(3)], vec![fx(1, 4), fx(1, 4), fx(1, 2)]);
assert!((d.mean().to_f64() - 1.0 / 3.0).abs() < 1e-6);
assert!((d.variance().to_f64() - 0.013888).abs() < 1e-4);
}
#[test]
fn uniform_domain_has_zero_variance_and_nothing_crosses() {
let d = Domain::from_focus(vec![p(1), p(2), p(3)], vec![fx(1, 3), fx(1, 3), fx(1, 3)]);
assert!(d.variance().to_f64() < 1e-9);
assert!(!crosses_threshold(fx(1, 3), &d, fx(3, 2)));
}
#[test]
fn a_spike_crosses_the_threshold() {
let d = Domain::from_focus(
vec![p(1), p(2), p(3), p(4)],
vec![fx(85, 100), fx(5, 100), fx(5, 100), fx(5, 100)],
);
assert!(crosses_threshold(fx(85, 100), &d, fx(3, 2)), "the spike should finalize");
assert!(!crosses_threshold(fx(5, 100), &d, fx(3, 2)), "background should not");
}
#[test]
fn certification_gate_bounds_uncertified_mass() {
let kappa_d = fx(74, 100);
let gap = fx(85, 1000);
let c = fx(225, 100);
let kp = fx(15, 10);
assert!(certified(fx(3, 1000), gap, kappa_d, c, kp));
assert!(!certified(fx(10, 1000), gap, kappa_d, c, kp));
}
#[test]
fn finalizes_requires_both_conditions() {
let d = Domain::from_focus(
vec![p(1), p(2), p(3), p(4)],
vec![fx(85, 100), fx(5, 100), fx(5, 100), fx(5, 100)],
);
let (kappa_d, gap, c, kp) = (fx(74, 100), fx(85, 1000), fx(225, 100), fx(15, 10));
assert_eq!(
finalizes(fx(85, 100), &d, fx(3, 1000), gap, kappa_d, c, kp),
Finality::Final
);
assert_eq!(
finalizes(fx(85, 100), &d, fx(50, 1000), gap, kappa_d, c, kp),
Finality::Pending
);
assert_eq!(
finalizes(fx(5, 100), &d, fx(3, 1000), gap, kappa_d, c, kp),
Finality::Pending
);
}
}