use std::collections::{BTreeMap, BTreeSet};
use super::compile::{
AnchorMap, Bound, ClassId, ConstraintSet, QuarantinePolicy, RowUid, ValueBounds, compile,
};
use super::wire::{DOMAIN_VALUE, FRAME_EQUAL_EFFORT, Judgement, RaterKind, Response, RowForm};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Reachability {
fwd: BTreeMap<ClassId, BTreeSet<ClassId>>,
}
impl Reachability {
pub(crate) fn build(cs: &ConstraintSet) -> Self {
let mut adj: BTreeMap<ClassId, BTreeSet<ClassId>> = BTreeMap::new();
for (winner, loser) in cs.edges.keys() {
adj.entry(winner.clone()).or_default().insert(loser.clone());
}
let mut fwd: BTreeMap<ClassId, BTreeSet<ClassId>> = BTreeMap::new();
for src in adj.keys() {
let mut seen: BTreeSet<ClassId> = BTreeSet::new();
let mut stack: Vec<&ClassId> = vec![src];
while let Some(node) = stack.pop() {
if let Some(next) = adj.get(node) {
for m in next {
if seen.insert(m.clone()) {
stack.push(m);
}
}
}
}
fwd.insert(src.clone(), seen);
}
Reachability { fwd }
}
pub(crate) fn reaches(&self, from: &ClassId, to: &ClassId) -> bool {
self.fwd.get(from).is_some_and(|set| set.contains(to))
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PairSide {
pub class: ClassId,
pub eff_weight: f64,
pub bounds: ValueBounds,
pub anchor: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SignRange {
NegativeOnly,
ZeroOnly,
PositiveOnly,
Mixed,
}
impl SignRange {
pub(crate) fn is_determined(self) -> bool {
!matches!(self, SignRange::Mixed)
}
}
fn is_zero(w: f64) -> bool {
w.abs().total_cmp(&0.0).is_eq()
}
#[derive(Debug, Clone, Copy)]
struct Interval {
lo: Option<f64>,
hi: Option<f64>,
}
fn side_interval(side: &PairSide) -> Interval {
if let Some(v) = side.anchor {
return Interval {
lo: Some(v),
hi: Some(v),
};
}
let take = |b: Bound| match b {
Bound::Unbounded => None,
Bound::Open(v) | Bound::Closed(v) => Some(v),
};
Interval {
lo: take(side.bounds.lower),
hi: take(side.bounds.upper),
}
}
#[derive(Debug, Clone, Copy)]
enum Coupling {
AGreater,
BGreater,
Free,
}
pub(crate) fn determined(reach: &Reachability, a: &PairSide, b: &PairSide) -> SignRange {
let (wa, wb) = (a.eff_weight, b.eff_weight);
if is_zero(wa) && is_zero(wb) {
return SignRange::ZeroOnly;
}
let ia = side_interval(a);
let ib = side_interval(b);
if a.class == b.class {
return same_class_range(wa - wb, ia);
}
let coupling = if reach.reaches(&a.class, &b.class) {
Coupling::AGreater
} else if reach.reaches(&b.class, &a.class) {
Coupling::BGreater
} else {
Coupling::Free
};
joint_range(wa, wb, ia, ib, coupling)
}
fn same_class_range(c: f64, i: Interval) -> SignRange {
if is_zero(c) {
return SignRange::ZeroOnly;
}
if let (Some(lo), Some(hi)) = (i.lo, i.hi)
&& lo.total_cmp(&hi).is_eq()
{
return sign_of(c * lo);
}
let mut vals: Vec<f64> = Vec::new();
let mut sup_unbounded = false;
let mut inf_unbounded = false;
match i.lo {
Some(lo) => vals.push(c * lo),
None => {
if c > 0.0 {
inf_unbounded = true;
} else {
sup_unbounded = true;
}
}
}
match i.hi {
Some(hi) => vals.push(c * hi),
None => {
if c > 0.0 {
sup_unbounded = true;
} else {
inf_unbounded = true;
}
}
}
range_verdict(&vals, sup_unbounded, inf_unbounded)
}
fn sign_of(v: f64) -> SignRange {
if v > 0.0 {
SignRange::PositiveOnly
} else if v < 0.0 {
SignRange::NegativeOnly
} else {
SignRange::ZeroOnly
}
}
fn joint_range(wa: f64, wb: f64, ia: Interval, ib: Interval, coupling: Coupling) -> SignRange {
let f = |x: f64, y: f64| wa * x - wb * y;
let point_ok = |x: f64, y: f64| match coupling {
Coupling::AGreater => x >= y,
Coupling::BGreater => x <= y,
Coupling::Free => true,
};
let mut vals: Vec<f64> = Vec::new();
let finite_or_rep = |i: Interval| -> Vec<f64> {
let fin: Vec<f64> = [i.lo, i.hi].into_iter().flatten().collect();
if fin.is_empty() { vec![0.0] } else { fin }
};
for &x in &finite_or_rep(ia) {
for &y in &finite_or_rep(ib) {
if point_ok(x, y) {
vals.push(f(x, y));
}
}
}
if !matches!(coupling, Coupling::Free) {
let t0 = match (ia.lo, ib.lo) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
};
let t1 = match (ia.hi, ib.hi) {
(Some(a), Some(b)) => Some(a.min(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
};
let nonempty = match (t0, t1) {
(Some(a), Some(b)) => a <= b,
_ => true,
};
if nonempty {
if let Some(t) = t0 {
vals.push(f(t, t));
}
if let Some(t) = t1 {
vals.push(f(t, t));
}
if t0.is_none() && t1.is_none() {
vals.push(f(0.0, 0.0));
}
}
}
let ray_ok = |dx: f64, dy: f64| match coupling {
Coupling::AGreater => dx >= dy,
Coupling::BGreater => dx <= dy,
Coupling::Free => true,
};
let mut rays: Vec<(f64, f64)> = Vec::new();
if ia.hi.is_none() {
rays.push((1.0, 0.0));
}
if ia.lo.is_none() {
rays.push((-1.0, 0.0));
}
if ib.hi.is_none() {
rays.push((0.0, 1.0));
}
if ib.lo.is_none() {
rays.push((0.0, -1.0));
}
if ia.hi.is_none() && ib.hi.is_none() {
rays.push((1.0, 1.0));
}
if ia.lo.is_none() && ib.lo.is_none() {
rays.push((-1.0, -1.0));
}
let mut sup_unbounded = false;
let mut inf_unbounded = false;
for (dx, dy) in rays {
if !ray_ok(dx, dy) {
continue;
}
let growth = wa * dx - wb * dy;
if growth > 0.0 {
sup_unbounded = true;
} else if growth < 0.0 {
inf_unbounded = true;
}
}
range_verdict(&vals, sup_unbounded, inf_unbounded)
}
fn range_verdict(vertex_values: &[f64], sup_unbounded: bool, inf_unbounded: bool) -> SignRange {
let mut inf = f64::INFINITY;
let mut sup = f64::NEG_INFINITY;
for &v in vertex_values {
inf = inf.min(v);
sup = sup.max(v);
}
if inf_unbounded {
inf = f64::NEG_INFINITY;
}
if sup_unbounded {
sup = f64::INFINITY;
}
debug_assert!(
inf <= sup,
"empty pair joint region from a satisfiable ConstraintSet"
);
if inf.total_cmp(&sup).is_eq() {
return sign_of(inf);
}
if inf < 0.0 && sup > 0.0 {
SignRange::Mixed
} else if inf >= 0.0 {
SignRange::PositiveOnly
} else {
SignRange::NegativeOnly
}
}
pub(crate) const SYNTHETIC_SESSION_UID: &str = "synthetic-hypothetical";
pub(crate) const SYNTHETIC_ROW_DATE: &str = "0000-01-01";
pub(crate) fn synthetic_answer_row(a: &str, b: &str, response: Response) -> Judgement {
Judgement {
uid: format!("{SYNTHETIC_SESSION_UID}:{a}:{b}"),
seq: 0,
a: a.to_string(),
b: Some(b.to_string()),
response: Some(response),
domain: DOMAIN_VALUE.to_string(),
frame: FRAME_EQUAL_EFFORT.to_string(),
form: RowForm::Order,
magnitude: None,
est_lower: None,
est_upper: None,
supersedes: None,
lens: None,
rater: RaterKind::Agent,
by: None,
note: None,
date: Some(SYNTHETIC_ROW_DATE.to_string()),
observed_at: None,
basis: None,
admission: None,
}
}
#[derive(Debug)]
pub(crate) enum Hypothetical<'a> {
Answer(Box<Judgement>),
AnchorRemoved(&'a str),
RowsRetired(&'a BTreeSet<RowUid>),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct HypotheticalOutcome {
pub newly_determined: Vec<(ClassId, ClassId)>,
pub no_longer_determined: Vec<(ClassId, ClassId)>,
}
impl HypotheticalOutcome {
pub(crate) fn yield_delta(&self) -> i64 {
let gained = i64::try_from(self.newly_determined.len()).unwrap_or(i64::MAX);
let lost = i64::try_from(self.no_longer_determined.len()).unwrap_or(i64::MAX);
gained - lost
}
}
pub(crate) fn hypothetical_outcome(
baseline_reach: &Reachability,
active: &[&Judgement],
anchors: &AnchorMap,
hypo: &Hypothetical<'_>,
relevant: &[(PairSide, PairSide)],
) -> HypotheticalOutcome {
let mut anchors2: AnchorMap = anchors.clone();
let active2: Vec<&Judgement> = match hypo {
Hypothetical::Answer(row) => {
let mut v = active.to_vec();
v.push(row.as_ref());
v
}
Hypothetical::AnchorRemoved(entity) => {
anchors2.remove(*entity);
active.to_vec()
}
Hypothetical::RowsRetired(uids) => active
.iter()
.copied()
.filter(|j| !uids.contains(&j.uid))
.collect(),
};
let cs2 = compile(&active2, &anchors2, QuarantinePolicy::Symmetric);
let reach2 = Reachability::build(&cs2);
let remap = |side: &PairSide| -> Option<PairSide> {
let class = cs2.classes.get(&side.class)?.clone();
let bounds = *cs2.bounds.get(&class)?;
let anchor = cs2.anchors.get(&class).copied();
Some(PairSide {
class,
eff_weight: side.eff_weight,
bounds,
anchor,
})
};
let mut out = HypotheticalOutcome::default();
for (a, b) in relevant {
let before = determined(baseline_reach, a, b).is_determined();
let after = match (remap(a), remap(b)) {
(Some(a2), Some(b2)) => determined(&reach2, &a2, &b2).is_determined(),
_ => false,
};
match (before, after) {
(false, true) => out
.newly_determined
.push((a.class.clone(), b.class.clone())),
(true, false) => out
.no_longer_determined
.push((a.class.clone(), b.class.clone())),
_ => {}
}
}
out
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "signed-count convenience wrapper; elicit uses hypothetical_outcome directly (SL-217 PHASE-02)"
)
)]
pub(crate) fn hypothetical_yield(
baseline_reach: &Reachability,
active: &[&Judgement],
anchors: &AnchorMap,
hypo: &Hypothetical<'_>,
relevant: &[(PairSide, PairSide)],
) -> i64 {
hypothetical_outcome(baseline_reach, active, anchors, hypo, relevant).yield_delta()
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "fixed-weight pool helper; elicit pairs with per-pair weights (SL-217 PHASE-02)"
)
)]
pub(crate) fn indeterminate_pairs(
reach: &Reachability,
pool: &[PairSide],
) -> Vec<(ClassId, ClassId)> {
let mut out: Vec<(ClassId, ClassId)> = Vec::new();
for (i, a) in pool.iter().enumerate() {
for b in pool.iter().skip(i + 1) {
if !determined(reach, a, b).is_determined() {
out.push((a.class.clone(), b.class.clone()));
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::comparison::wire::{DOMAIN_VALUE, FRAME_EQUAL_EFFORT};
fn row(uid: &str, a: &str, b: &str, response: Response) -> Judgement {
Judgement {
uid: uid.to_string(),
seq: 0,
a: a.to_string(),
b: Some(b.to_string()),
response: Some(response),
domain: DOMAIN_VALUE.to_string(),
frame: FRAME_EQUAL_EFFORT.to_string(),
form: RowForm::Order,
magnitude: None,
est_lower: None,
est_upper: None,
supersedes: None,
lens: None,
rater: RaterKind::Human,
by: None,
note: None,
date: Some("2026-07-12".to_string()),
observed_at: None,
basis: None,
admission: None,
}
}
fn win(uid: &str, winner: &str, loser: &str) -> Judgement {
row(uid, winner, loser, Response::PreferA)
}
fn eq_row(uid: &str, a: &str, b: &str) -> Judgement {
row(uid, a, b, Response::Equal)
}
fn amap(anchors: &[(&str, f64)]) -> AnchorMap {
anchors.iter().map(|&(e, v)| (e.to_string(), v)).collect()
}
fn compiled(rows: &[Judgement], anchors: &[(&str, f64)]) -> (ConstraintSet, Reachability) {
let refs: Vec<&Judgement> = rows.iter().collect();
let cs = compile(&refs, &amap(anchors), QuarantinePolicy::Symmetric);
let reach = Reachability::build(&cs);
(cs, reach)
}
fn side(cs: &ConstraintSet, entity: &str, w: f64) -> PairSide {
let class = cs.classes[entity].clone();
PairSide {
class: class.clone(),
eff_weight: w,
bounds: cs.bounds[&class],
anchor: cs.anchors.get(&class).copied(),
}
}
#[test]
fn chain_ordered_equal_weights_determined() {
let rows = [win("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[]);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "B", 1.0));
assert_eq!(verdict, SignRange::PositiveOnly);
}
#[test]
fn chain_ordered_cheaper_winner_determined_positive_intervals() {
let rows = [win("j0", "A", "B"), win("j1", "B", "C")];
let (cs, reach) = compiled(&rows, &[("C", 0.0)]);
let verdict = determined(&reach, &side(&cs, "A", 2.0), &side(&cs, "B", 1.0));
assert_eq!(verdict, SignRange::PositiveOnly);
}
#[test]
fn chain_ordered_costlier_winner_indeterminate() {
let rows = [win("j0", "A", "B"), win("j1", "B", "C")];
let (cs, reach) = compiled(&rows, &[("C", 0.0)]);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "B", 2.0));
assert_eq!(verdict, SignRange::Mixed);
}
#[test]
fn costlier_winner_determined_under_anchor_squeeze() {
let rows = [win("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[("A", 10.0), ("B", 1.0)]);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "B", 2.0));
assert_eq!(verdict, SignRange::PositiveOnly);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "B", 20.0));
assert_eq!(verdict, SignRange::NegativeOnly);
}
#[test]
fn box_overlap_but_chain_coupled_determined() {
let rows = [
win("j0", "T", "A"),
win("j1", "A", "B"),
win("j2", "B", "Z"),
];
let (cs, reach) = compiled(&rows, &[("T", 10.0), ("Z", 0.0)]);
let a = side(&cs, "A", 1.0);
let b = side(&cs, "B", 1.0);
assert_eq!(a.bounds, b.bounds, "boxes genuinely overlap");
assert_eq!(determined(&reach, &a, &b), SignRange::PositiveOnly);
}
#[test]
fn same_class_differing_weights_spanning_zero_indeterminate() {
let rows = [
eq_row("j0", "A", "B"),
win("j1", "T", "A"),
win("j2", "A", "L"),
];
let (cs, reach) = compiled(&rows, &[("T", 5.0), ("L", -5.0)]);
let verdict = determined(&reach, &side(&cs, "A", 2.0), &side(&cs, "B", 1.0));
assert_eq!(verdict, SignRange::Mixed);
}
#[test]
fn same_class_equal_weights_zero_only() {
let rows = [eq_row("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[]);
let verdict = determined(&reach, &side(&cs, "A", 1.5), &side(&cs, "B", 1.5));
assert_eq!(verdict, SignRange::ZeroOnly);
}
#[test]
fn same_class_anchored_point_reads_sign() {
let rows = [eq_row("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[("A", 3.0)]);
let verdict = determined(&reach, &side(&cs, "A", 2.0), &side(&cs, "B", 1.0));
assert_eq!(verdict, SignRange::PositiveOnly);
}
#[test]
fn both_weights_zero_zero_only() {
let rows = [win("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[]);
let verdict = determined(&reach, &side(&cs, "A", 0.0), &side(&cs, "B", 0.0));
assert_eq!(verdict, SignRange::ZeroOnly);
}
#[test]
fn unbounded_side_differing_weights_mixed() {
let rows = [win("j0", "A", "B"), win("j1", "C", "D")];
let (cs, reach) = compiled(&rows, &[]);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "C", 2.0));
assert_eq!(verdict, SignRange::Mixed);
}
#[test]
fn one_zero_weight_over_negative_spanning_interval_mixed() {
let rows = [
win("j0", "T", "A"),
win("j1", "A", "L"),
win("j2", "C", "D"),
];
let (cs, reach) = compiled(&rows, &[("T", 5.0), ("L", -5.0)]);
let verdict = determined(&reach, &side(&cs, "A", 1.0), &side(&cs, "C", 0.0));
assert_eq!(verdict, SignRange::Mixed);
let (cs2, reach2) = compiled(&rows, &[("T", 5.0), ("L", 1.0)]);
let verdict = determined(&reach2, &side(&cs2, "A", 1.0), &side(&cs2, "C", 0.0));
assert_eq!(verdict, SignRange::PositiveOnly);
}
#[test]
fn coupling_boundary_infimum_golden() {
let rows = [
win("j0", "U", "A"),
win("j1", "A", "L2"),
win("j2", "A", "B"),
win("j3", "M", "B"),
win("j4", "B", "L"),
];
let (cs, reach) = compiled(&rows, &[("U", 10.0), ("L2", 4.0), ("M", 6.0), ("L", 0.0)]);
let a = side(&cs, "A", 0.7);
let b = side(&cs, "B", 1.0);
assert_eq!(a.bounds.lower, Bound::Open(4.0));
assert_eq!(a.bounds.upper, Bound::Open(10.0));
assert_eq!(b.bounds.lower, Bound::Open(0.0));
assert_eq!(b.bounds.upper, Bound::Open(6.0));
assert_eq!(determined(&reach, &a, &b), SignRange::Mixed);
}
#[test]
fn indeterminate_pairs_filters_pool() {
let rows = [
win("j0", "A", "B"),
row("j1", "C", "A", Response::Incomparable),
];
let (cs, reach) = compiled(&rows, &[]);
let pool = [
side(&cs, "A", 1.0),
side(&cs, "B", 1.0),
side(&cs, "C", 1.0),
];
let open = indeterminate_pairs(&reach, &pool);
assert_eq!(open.len(), 2);
assert!(open.contains(&(cs.classes["A"].clone(), cs.classes["C"].clone())));
assert!(open.contains(&(cs.classes["B"].clone(), cs.classes["C"].clone())));
}
fn oracle_extends(cs: &ConstraintSet, pin_a: (&ClassId, f64), pin_b: (&ClassId, f64)) -> bool {
let mut pinned: BTreeMap<ClassId, f64> = cs.anchors.clone();
for (class, v) in [pin_a, pin_b] {
if let Some(&existing) = pinned.get(class) {
if existing.total_cmp(&v).is_ne() {
return false;
}
}
pinned.insert(class.clone(), v);
}
let all: BTreeSet<ClassId> = cs.classes.values().cloned().collect();
let free: Vec<ClassId> = all
.iter()
.filter(|c| !pinned.contains_key(*c))
.cloned()
.collect();
let mut pins: Vec<f64> = pinned.values().copied().collect();
pins.sort_by(f64::total_cmp);
pins.dedup_by(|a, b| a.total_cmp(b).is_eq());
let n = all.len();
let mut grid: Vec<f64> = Vec::new();
if let (Some(&first), Some(&last)) = (pins.first(), pins.last()) {
for i in 1..=n {
grid.push(first - i as f64);
grid.push(last + i as f64);
}
for w in pins.windows(2) {
let (lo, hi) = (w[0], w[1]);
for i in 1..=n {
grid.push(lo + (hi - lo) * (i as f64) / ((n + 1) as f64));
}
}
}
grid.extend(pins.iter().copied());
fn consistent(
edges: &super::super::compile::EdgeMap,
vals: &BTreeMap<ClassId, f64>,
) -> bool {
edges.keys().all(|(w, l)| match (vals.get(w), vals.get(l)) {
(Some(wv), Some(lv)) => wv > lv,
_ => true,
})
}
fn bt(
free: &[ClassId],
vals: &mut BTreeMap<ClassId, f64>,
grid: &[f64],
edges: &super::super::compile::EdgeMap,
) -> bool {
let Some((head, rest)) = free.split_first() else {
return consistent(edges, vals);
};
for &v in grid {
vals.insert(head.clone(), v);
if consistent(edges, vals) && bt(rest, vals, grid, edges) {
return true;
}
}
vals.remove(head);
false
}
let mut vals = pinned.clone();
if !consistent(&cs.edges, &vals) {
return false;
}
bt(&free, &mut vals, &grid, &cs.edges)
}
fn interval_samples(b: &ValueBounds, anchor: Option<f64>) -> Vec<(f64, bool)> {
if let Some(v) = anchor {
return vec![(v, true), (v + 1.0, false), (v - 1.0, false)];
}
let mut out: Vec<(f64, bool)> = Vec::new();
match (b.lower, b.upper) {
(Bound::Open(l), Bound::Open(u)) => {
out.push((l + (u - l) * 0.25, true));
out.push(((l + u) / 2.0, true));
out.push((l + (u - l) * 0.75, true));
out.push((l, false)); out.push((u, false));
out.push((l - 1.0, false));
out.push((u + 1.0, false));
}
(Bound::Open(l), Bound::Unbounded) => {
out.push((l + 0.5, true));
out.push((l + 10.0, true));
out.push((l, false));
out.push((l - 1.0, false));
}
(Bound::Unbounded, Bound::Open(u)) => {
out.push((u - 0.5, true));
out.push((u - 10.0, true));
out.push((u, false));
out.push((u + 1.0, false));
}
(Bound::Unbounded, Bound::Unbounded) => {
out.push((-7.0, true));
out.push((0.0, true));
out.push((7.0, true));
}
_ => {}
}
out
}
#[test]
fn extension_oracle_confirms_joint_region_exactness() {
struct Scenario {
name: &'static str,
rows: Vec<Judgement>,
anchors: Vec<(&'static str, f64)>,
pair: (&'static str, &'static str),
}
let scenarios = vec![
Scenario {
name: "bare chain, unbounded sides",
rows: vec![win("j0", "A", "B")],
anchors: vec![],
pair: ("A", "B"),
},
Scenario {
name: "chain with bottom anchor (open + unbounded)",
rows: vec![win("j0", "A", "B"), win("j1", "B", "C")],
anchors: vec![("C", 0.0)],
pair: ("A", "B"),
},
Scenario {
name: "shared ANCHORED intermediate A ⇝ Z ⇝ B (reviewer case)",
rows: vec![win("j0", "A", "Z"), win("j1", "Z", "B")],
anchors: vec![("Z", 5.0)],
pair: ("A", "B"),
},
Scenario {
name: "shared UNANCHORED intermediate, outer anchors",
rows: vec![
win("j0", "T", "A"),
win("j1", "A", "Z"),
win("j2", "Z", "B"),
win("j3", "B", "L"),
],
anchors: vec![("T", 10.0), ("L", 0.0)],
pair: ("A", "B"),
},
Scenario {
name: "diamond: multiple anchored ancestors and descendants",
rows: vec![
win("j0", "T1", "A"),
win("j1", "T2", "A"),
win("j2", "B", "L1"),
win("j3", "B", "L2"),
win("j4", "A", "B"),
],
anchors: vec![("T1", 8.0), ("T2", 9.0), ("L1", 1.0), ("L2", 2.0)],
pair: ("A", "B"),
},
Scenario {
name: "equality merge inside the chain",
rows: vec![
eq_row("j0", "A", "A2"),
win("j1", "A2", "B"),
win("j2", "B", "L"),
],
anchors: vec![("L", 0.0)],
pair: ("A", "B"),
},
Scenario {
name: "order-incomparable pair, one side anchored chain",
rows: vec![win("j0", "A", "B"), win("j1", "C", "D")],
anchors: vec![("B", 3.0)],
pair: ("A", "C"),
},
Scenario {
name: "negative domain (no positivity assumption)",
rows: vec![win("j0", "A", "B"), win("j1", "B", "C")],
anchors: vec![("A", -1.0), ("C", -9.0)],
pair: ("A", "B"),
},
Scenario {
name: "coupling-boundary golden graph",
rows: vec![
win("j0", "U", "A"),
win("j1", "A", "L2"),
win("j2", "A", "B"),
win("j3", "M", "B"),
win("j4", "B", "L"),
],
anchors: vec![("U", 10.0), ("L2", 4.0), ("M", 6.0), ("L", 0.0)],
pair: ("A", "B"),
},
];
for s in &scenarios {
let (cs, reach) = compiled(&s.rows, &s.anchors);
assert!(
cs.quarantined.is_empty(),
"{}: scenario must compile clean",
s.name
);
let (ea, eb) = s.pair;
let ca = cs.classes[ea].clone();
let cb = cs.classes[eb].clone();
let ba = cs.bounds[&ca];
let bb = cs.bounds[&cb];
let aa = cs.anchors.get(&ca).copied();
let ab = cs.anchors.get(&cb).copied();
let a_greater = reach.reaches(&ca, &cb);
let b_greater = reach.reaches(&cb, &ca);
for &(x, x_in) in &interval_samples(&ba, aa) {
for &(y, y_in) in &interval_samples(&bb, ab) {
let coupling_ok = if a_greater {
x > y
} else if b_greater {
x < y
} else {
true
};
let claimed = x_in && y_in && coupling_ok;
let extends = oracle_extends(&cs, (&ca, x), (&cb, y));
assert_eq!(
extends, claimed,
"{}: point ({x}, {y}) — oracle {extends}, claimed {claimed}",
s.name
);
}
}
}
}
#[test]
fn extension_oracle_micro_case_rejects_violation() {
let rows = [win("j0", "A", "B")];
let (cs, _) = compiled(&rows, &[]);
let ca = cs.classes["A"].clone();
let cb = cs.classes["B"].clone();
assert!(oracle_extends(&cs, (&ca, 3.0), (&cb, 1.0)));
assert!(!oracle_extends(&cs, (&ca, 1.0), (&cb, 3.0)));
assert!(!oracle_extends(&cs, (&ca, 2.0), (&cb, 2.0)), "strict edge");
}
#[test]
fn hypothetical_yield_min_over_order_bearing_answers() {
let rows = [
win("j0", "A", "B"),
row("j1", "C", "A", Response::Incomparable),
];
let (cs, reach) = compiled(&rows, &[]);
let refs: Vec<&Judgement> = rows.iter().collect();
let anchors = amap(&[]);
let relevant = [
(side(&cs, "A", 1.0), side(&cs, "C", 1.0)),
(side(&cs, "B", 1.0), side(&cs, "C", 1.0)),
];
let yield_of = |response: Response| {
let hypo = Hypothetical::Answer(Box::new(synthetic_answer_row("A", "C", response)));
hypothetical_yield(&reach, &refs, &anchors, &hypo, &relevant)
};
assert_eq!(yield_of(Response::PreferA), 1); assert_eq!(yield_of(Response::PreferB), 2); assert_eq!(yield_of(Response::Equal), 2); let order_bearing_min = [
yield_of(Response::PreferA),
yield_of(Response::PreferB),
yield_of(Response::Equal),
]
.into_iter()
.min();
assert_eq!(order_bearing_min, Some(1));
}
#[test]
fn incomparable_hypothetical_yields_zero() {
let rows = [
win("j0", "A", "B"),
row("j1", "C", "A", Response::Incomparable),
];
let (cs, reach) = compiled(&rows, &[]);
let refs: Vec<&Judgement> = rows.iter().collect();
let relevant = [(side(&cs, "A", 1.0), side(&cs, "C", 1.0))];
let hypo = Hypothetical::Answer(Box::new(synthetic_answer_row(
"A",
"C",
Response::Incomparable,
)));
assert_eq!(
hypothetical_yield(&reach, &refs, &amap(&[]), &hypo, &relevant),
0
);
}
#[test]
fn contradicting_hypothetical_negative_delta() {
let rows = [win("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[]);
let refs: Vec<&Judgement> = rows.iter().collect();
let relevant = [(side(&cs, "A", 1.0), side(&cs, "B", 1.0))];
let hypo =
Hypothetical::Answer(Box::new(synthetic_answer_row("B", "A", Response::PreferA)));
let outcome = hypothetical_outcome(&reach, &refs, &amap(&[]), &hypo, &relevant);
assert_eq!(outcome.newly_determined.len(), 0);
assert_eq!(outcome.no_longer_determined.len(), 1);
assert_eq!(outcome.yield_delta(), -1);
}
#[test]
fn equal_between_differently_anchored_quarantines_and_goes_negative() {
let rows = [eq_row("j0", "A", "E"), win("j1", "C", "D")];
let anchors = [("E", 5.0), ("C", 3.0)];
let (cs, reach) = compiled(&rows, &anchors);
let refs: Vec<&Judgement> = rows.iter().collect();
let a = side(&cs, "A", 1.0);
let c = side(&cs, "C", 1.0);
assert_eq!(determined(&reach, &a, &c), SignRange::PositiveOnly);
let relevant = [(a, c)];
let hypo = Hypothetical::Answer(Box::new(synthetic_answer_row("A", "C", Response::Equal)));
let outcome = hypothetical_outcome(&reach, &refs, &amap(&anchors), &hypo, &relevant);
assert_eq!(outcome.yield_delta(), -1, "C2 quarantine reopens (A, C)");
}
#[test]
fn synthetic_session_identity_never_touches_real_rows() {
let real = win("j0", "A", "B");
let synthetic = synthetic_answer_row("A", "B", Response::PreferA);
assert!(synthetic.uid.starts_with(SYNTHETIC_SESSION_UID));
assert_ne!(synthetic.uid, real.uid);
assert_eq!(synthetic.supersedes, None);
let rows = [real];
let (cs, reach) = compiled(&rows, &[]);
let refs: Vec<&Judgement> = rows.iter().collect();
let relevant = [(side(&cs, "A", 1.0), side(&cs, "B", 1.0))];
let hypo = Hypothetical::Answer(Box::new(synthetic));
let outcome = hypothetical_outcome(&reach, &refs, &amap(&[]), &hypo, &relevant);
assert_eq!(outcome.yield_delta(), 0);
}
#[test]
fn anchor_removed_reactivates_closure_rows() {
let rows = [win("j0", "A", "B"), win("j1", "B", "C")];
let anchors = [("A", 1.0), ("B", 5.0)];
let (cs, reach) = compiled(&rows, &anchors);
assert!(!cs.quarantined.is_empty(), "conflict must quarantine");
let refs: Vec<&Judgement> = rows.iter().collect();
let relevant = [(side(&cs, "A", 1.0), side(&cs, "C", 1.0))];
assert_eq!(
determined(&reach, &relevant[0].0, &relevant[0].1),
SignRange::Mixed,
"baseline pair open while the chain is sterilised"
);
let hypo = Hypothetical::AnchorRemoved("B");
assert_eq!(
hypothetical_yield(&reach, &refs, &amap(&anchors), &hypo, &relevant),
1
);
}
#[test]
fn rows_retired_counts_lost_determinations() {
let rows = [win("j0", "A", "B")];
let (cs, reach) = compiled(&rows, &[]);
let refs: Vec<&Judgement> = rows.iter().collect();
let relevant = [(side(&cs, "A", 1.0), side(&cs, "B", 1.0))];
let retired: BTreeSet<RowUid> = ["j0".to_string()].into_iter().collect();
let hypo = Hypothetical::RowsRetired(&retired);
assert_eq!(
hypothetical_yield(&reach, &refs, &amap(&[]), &hypo, &relevant),
-1
);
}
}