use pounce_common::types::Number;
pub const UNBOUNDED: i8 = -1;
pub const INACTIVE: i8 = 0;
pub const WEAKLY_ACTIVE: i8 = 1;
pub const STRONGLY_ACTIVE: i8 = 2;
pub const AMBIGUOUS: i8 = 3;
pub const UNIDENTIFIED: i8 = 4;
pub const FIXED: i8 = 5;
pub const EQUALITY: i8 = 6;
pub fn classify(r: Number, mu: Number) -> i8 {
if mu > 1e-4 {
if r < 1e-1 {
INACTIVE
} else if r > 1e1 {
STRONGLY_ACTIVE
} else {
AMBIGUOUS
}
} else if r < mu.sqrt() {
INACTIVE
} else if r > 1.0 / mu.sqrt() {
STRONGLY_ACTIVE
} else if (1e-1..=1e1).contains(&r) {
WEAKLY_ACTIVE
} else {
AMBIGUOUS
}
}
pub fn sign_of(x: Number) -> i8 {
if x > 0.0 {
1
} else if x < 0.0 {
-1
} else {
0
}
}
pub fn off_path(s: Number, z: Number, mu: Number) -> bool {
let comp = s * z;
comp > 10.0 * mu || comp < 0.1 * mu
}
pub fn contaminated(status: i8, r: Number, mu: Number) -> bool {
status == INACTIVE && r > 100.0 * mu
}
#[derive(Clone, Copy, Debug)]
pub struct Entry {
pub status: i8,
pub ratio: Number,
pub q_sign: i8,
pub off_path: bool,
pub contaminated: bool,
pub sigma: Number,
}
pub const NOT_CLASSIFIED: Entry = Entry {
status: UNBOUNDED,
ratio: Number::NAN,
q_sign: 0,
off_path: false,
contaminated: false,
sigma: 0.0,
};
pub fn zero_gradient_row(sigma: Number, floor: Number) -> Entry {
Entry {
status: UNIDENTIFIED,
ratio: sigma / floor,
q_sign: 0,
off_path: false,
contaminated: false,
sigma,
}
}
pub fn classify_entry(sigma: Number, q_signed: Number, floor: Number, mu: Number) -> Entry {
let q_sign = sign_of(q_signed);
let q = q_signed.abs();
if q < floor {
return Entry {
status: UNIDENTIFIED,
ratio: sigma / floor,
q_sign,
off_path: false,
contaminated: false,
sigma,
};
}
let r = sigma / q;
let status = classify(r, mu);
Entry {
status,
ratio: r,
q_sign,
off_path: false,
contaminated: contaminated(status, r, mu),
sigma,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tight_mu_walks_all_five_regions() {
let mu = 1e-10; assert_eq!(classify(0.9e-5, mu), INACTIVE);
assert_eq!(classify(1.1e-5, mu), AMBIGUOUS); assert_eq!(classify(0.5, mu), WEAKLY_ACTIVE);
assert_eq!(classify(50.0, mu), AMBIGUOUS); assert_eq!(classify(2e5, mu), STRONGLY_ACTIVE);
}
#[test]
fn band_edges_are_inclusive_and_mu_edges_separate() {
let mu = 1e-10;
assert_eq!(classify(1e-1, mu), WEAKLY_ACTIVE);
assert_eq!(classify(1e1, mu), WEAKLY_ACTIVE);
assert_eq!(classify(0.99e-5, mu), INACTIVE);
assert_eq!(classify(1.01e-5, mu), AMBIGUOUS);
assert_eq!(classify(0.99e5, mu), AMBIGUOUS);
assert_eq!(classify(1.01e5, mu), STRONGLY_ACTIVE);
}
#[test]
fn loose_mu_refuses_the_weak_call() {
for mu in [1e-3, 1e-2, 1e-1] {
assert_eq!(classify(0.05, mu), INACTIVE);
assert_eq!(classify(1.0, mu), AMBIGUOUS);
assert_eq!(classify(50.0, mu), STRONGLY_ACTIVE);
}
assert_eq!(classify(1.0, 1e-4), WEAKLY_ACTIVE);
}
#[test]
fn off_path_is_a_factor_of_ten_both_ways() {
let mu = 1e-2;
assert!(!off_path(1.0, 1e-2, mu)); assert!(!off_path(0.5, 1e-2, mu)); assert!(off_path(1.0, 0.2, mu)); assert!(off_path(1.0, 5e-4, mu)); }
#[test]
fn contamination_is_mu_relative_and_inactive_only() {
let mu = 1e-10; assert!(contaminated(INACTIVE, 1e-6, mu));
assert!(!contaminated(INACTIVE, 5e-9, mu));
assert!(!contaminated(WEAKLY_ACTIVE, 1.0, mu));
assert!(!contaminated(STRONGLY_ACTIVE, 1e5, mu));
assert!(100.0 * mu < mu.sqrt());
}
#[test]
fn below_floor_reports_unidentified_with_the_sign() {
let e = classify_entry(0.5, 1e-12, 1e-8, 1e-10);
assert_eq!(e.status, UNIDENTIFIED);
assert_eq!(e.q_sign, 1);
let e = classify_entry(0.5, -1e-12, 1e-8, 1e-10);
assert_eq!(e.status, UNIDENTIFIED);
assert_eq!(e.q_sign, -1);
let e = classify_entry(1.0, -2.0, 1e-8, 1e-10);
assert_eq!(e.status, WEAKLY_ACTIVE);
assert_eq!(e.q_sign, -1);
assert_eq!(e.ratio, 0.5);
}
}