use std::collections::{BTreeMap, BTreeSet};
use super::compile::{ClassId, ConstraintSet, EdgeMap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueProvenance {
Authored,
Projected,
Gauge,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct ProjectionCfg {
pub gauge_step: f64,
pub gauge_center: f64,
}
pub(crate) const VALUE_PROJECTION_PARAMS: ProjectionCfg = ProjectionCfg {
gauge_step: 0.25,
gauge_center: 1.0,
};
pub(crate) type Projection = BTreeMap<String, (f64, ValueProvenance)>;
type Adj = BTreeMap<ClassId, BTreeSet<ClassId>>;
pub(crate) fn project(cs: &ConstraintSet, cfg: &ProjectionCfg) -> Projection {
let nodes: BTreeSet<ClassId> = cs.classes.values().cloned().collect();
let (out, inn) = adjacency(&cs.edges);
let class_values = place(&nodes, &out, &inn, &cs.anchors, cfg);
cs.classes
.iter()
.filter_map(|(entity, class)| {
class_values
.get(class)
.map(|&(value, prov)| (entity.clone(), (value, prov)))
})
.collect()
}
fn adjacency(edges: &EdgeMap) -> (Adj, Adj) {
let mut out: Adj = BTreeMap::new();
let mut inn: Adj = BTreeMap::new();
for (winner, loser) in edges.keys() {
out.entry(winner.clone()).or_default().insert(loser.clone());
inn.entry(loser.clone()).or_default().insert(winner.clone());
}
(out, inn)
}
fn topo_order(nodes: &BTreeSet<ClassId>, out: &Adj, inn: &Adj) -> Vec<ClassId> {
let mut indeg: BTreeMap<ClassId, usize> = nodes
.iter()
.map(|n| (n.clone(), out.get(n).map_or(0, BTreeSet::len)))
.collect();
let mut ready: BTreeSet<ClassId> = nodes
.iter()
.filter(|n| indeg.get(*n).copied().unwrap_or(0) == 0)
.cloned()
.collect();
let mut order = Vec::with_capacity(nodes.len());
while let Some(n) = ready.pop_first() {
if let Some(preds) = inn.get(&n) {
for p in preds {
if let Some(d) = indeg.get_mut(p) {
*d -= 1;
if *d == 0 {
ready.insert(p.clone());
}
}
}
}
order.push(n);
}
order
}
fn heights(order: &[ClassId], out: &Adj) -> BTreeMap<ClassId, usize> {
let mut h: BTreeMap<ClassId, usize> = BTreeMap::new();
for v in order {
let hv = out.get(v).map_or(0, |succ| {
succ.iter()
.map(|s| h.get(s).copied().unwrap_or(0) + 1)
.max()
.unwrap_or(0)
});
h.insert(v.clone(), hv);
}
h
}
fn longest_up(
order: &[ClassId],
inn: &Adj,
anchors: &BTreeMap<ClassId, f64>,
) -> (BTreeMap<ClassId, f64>, BTreeMap<ClassId, usize>) {
let mut hi: BTreeMap<ClassId, f64> = BTreeMap::new();
let mut dup: BTreeMap<ClassId, usize> = BTreeMap::new();
for v in order.iter().rev() {
let mut best: Option<(f64, usize)> = None;
if let Some(preds) = inn.get(v) {
for p in preds {
let cand = if let Some(&a) = anchors.get(p) {
Some((a, 1usize))
} else if let (Some(&hp), Some(&dp)) = (hi.get(p), dup.get(p)) {
Some((hp, dp + 1))
} else {
None
};
if let Some((cv, cd)) = cand {
let take = match best {
None => true,
Some((bv, bd)) => {
cv.total_cmp(&bv).is_lt() || (cv.total_cmp(&bv).is_eq() && cd > bd)
}
};
if take {
best = Some((cv, cd));
}
}
}
}
if let Some((bv, bd)) = best {
hi.insert(v.clone(), bv);
dup.insert(v.clone(), bd);
}
}
(hi, dup)
}
fn depth_below_ceiling(
order: &[ClassId],
inn: &Adj,
anchors: &BTreeMap<ClassId, f64>,
) -> BTreeMap<ClassId, usize> {
let mut depth: BTreeMap<ClassId, usize> = BTreeMap::new();
for v in order.iter().rev() {
let mut best: Option<usize> = None;
if let Some(preds) = inn.get(v) {
for p in preds {
let cand = if anchors.contains_key(p) {
Some(1)
} else {
depth.get(p).map(|d| d + 1)
};
if let Some(c) = cand {
best = Some(best.map_or(c, |b: usize| b.max(c)));
}
}
}
if let Some(b) = best {
depth.insert(v.clone(), b);
}
}
depth
}
fn successor_max(values: &BTreeMap<ClassId, f64>, out: &Adj, v: &str) -> Option<f64> {
out.get(v).and_then(|succ| {
succ.iter()
.filter_map(|s| values.get(s).copied())
.reduce(|a, b| if a.total_cmp(&b).is_ge() { a } else { b })
})
}
fn nonzero_or_one(x: Option<usize>) -> usize {
match x {
Some(0) | None => 1,
Some(n) => n,
}
}
fn count_f64(n: usize) -> f64 {
f64::from(u32::try_from(n).unwrap_or(u32::MAX))
}
fn components(nodes: &BTreeSet<ClassId>, out: &Adj, inn: &Adj) -> Vec<BTreeSet<ClassId>> {
let mut unvisited: BTreeSet<ClassId> = nodes.clone();
let mut comps = Vec::new();
while let Some(seed) = unvisited.pop_first() {
let mut comp = BTreeSet::new();
let mut frontier = vec![seed];
while let Some(n) = frontier.pop() {
for adj in [out, inn] {
if let Some(neighbours) = adj.get(&n) {
for m in neighbours {
if unvisited.remove(m) {
frontier.push(m.clone());
}
}
}
}
comp.insert(n);
}
comps.push(comp);
}
comps
}
fn place(
nodes: &BTreeSet<ClassId>,
out: &Adj,
inn: &Adj,
anchors: &BTreeMap<ClassId, f64>,
cfg: &ProjectionCfg,
) -> BTreeMap<ClassId, (f64, ValueProvenance)> {
let mut result = BTreeMap::new();
for members in components(nodes, out, inn) {
let component_anchors: BTreeMap<ClassId, f64> = anchors
.iter()
.filter(|(class, _)| members.contains(*class))
.map(|(class, &v)| (class.clone(), v))
.collect();
result.extend(place_component(&members, out, inn, &component_anchors, cfg));
}
result
}
fn place_component(
nodes: &BTreeSet<ClassId>,
out: &Adj,
inn: &Adj,
anchors: &BTreeMap<ClassId, f64>,
cfg: &ProjectionCfg,
) -> BTreeMap<ClassId, (f64, ValueProvenance)> {
let order = topo_order(nodes, out, inn);
if anchors.is_empty() {
let h = heights(&order, out);
let big_h = h.values().copied().max().unwrap_or(0);
let denom = count_f64(big_h) + 2.0;
return nodes
.iter()
.map(|n| {
let hn = h.get(n).copied().unwrap_or(0);
let value = 2.0 * cfg.gauge_center * (count_f64(hn) + 1.0) / denom;
(n.clone(), (value, ValueProvenance::Gauge))
})
.collect();
}
let (hi, dup) = longest_up(&order, inn, anchors);
let dbc = depth_below_ceiling(&order, inn, anchors);
let mut values: BTreeMap<ClassId, f64> = BTreeMap::new();
let mut result: BTreeMap<ClassId, (f64, ValueProvenance)> = BTreeMap::new();
for v in &order {
if let Some(&anchor) = anchors.get(v) {
debug_assert!(
successor_max(&values, out, v).is_none_or(|f| f < anchor),
"P3: infeasible anchor placement — C5 should have guaranteed floor < anchor"
);
values.insert(v.clone(), anchor);
result.insert(v.clone(), (anchor, ValueProvenance::Authored));
continue;
}
let floor = successor_max(&values, out, v);
let ceiling = hi.get(v).copied();
let value = match (floor, ceiling) {
(None, None) => {
values.insert(v.clone(), cfg.gauge_center);
result.insert(v.clone(), (cfg.gauge_center, ValueProvenance::Gauge));
continue;
}
(None, Some(c)) => {
let down = nonzero_or_one(dbc.get(v).copied());
let mut synthetic = c - cfg.gauge_step * (count_f64(down) + 1.0);
if c > 0.0 {
synthetic = synthetic.max(0.0);
}
let up = nonzero_or_one(dup.get(v).copied());
synthetic + (c - synthetic) / (count_f64(up) + 1.0)
}
(Some(f), None) => f + cfg.gauge_step,
(Some(f), Some(c)) => {
let up = nonzero_or_one(dup.get(v).copied());
f + (c - f) / (count_f64(up) + 1.0)
}
};
values.insert(v.clone(), value);
result.insert(v.clone(), (value, ValueProvenance::Projected));
}
result
}
#[cfg(test)]
mod tests {
use super::super::compile::{AnchorMap, QuarantinePolicy, compile};
use super::{Projection, ProjectionCfg, VALUE_PROJECTION_PARAMS, ValueProvenance, project};
use crate::comparison::{
DOMAIN_VALUE, FRAME_EQUAL_EFFORT, Judgement, RaterKind, Response, RowForm,
};
use ValueProvenance::{Authored, Gauge, Projected};
const CFG: ProjectionCfg = VALUE_PROJECTION_PARAMS;
const EPS: f64 = 1e-4;
#[test]
fn d8_value_projection_params_are_the_shipped_constants() {
assert_eq!(
VALUE_PROJECTION_PARAMS.gauge_step,
crate::priority::config::GAUGE_STEP
);
assert_eq!(
VALUE_PROJECTION_PARAMS.gauge_center,
crate::priority::graph::DEFAULT_VALUE
);
assert_eq!(CFG, VALUE_PROJECTION_PARAMS);
}
fn judgement(uid: &str, winner: &str, loser: &str) -> Judgement {
Judgement {
uid: uid.to_string(),
seq: 0,
a: winner.to_string(),
b: Some(loser.to_string()),
response: Some(Response::PreferA),
domain: DOMAIN_VALUE.to_string(),
frame: FRAME_EQUAL_EFFORT.to_string(),
form: RowForm::Order,
magnitude: None,
supersedes: None,
lens: None,
rater: RaterKind::Human,
by: None,
note: None,
date: Some("2026-07-11".to_string()),
observed_at: None,
basis: None,
est_lower: None,
est_upper: None,
admission: None,
}
}
fn project_scenario(edges: &[(&str, &str)], anchors: &[(&str, f64)]) -> Projection {
project(&compiled(edges, anchors), &CFG)
}
fn compiled(edges: &[(&str, &str)], anchors: &[(&str, f64)]) -> super::ConstraintSet {
let rows: Vec<Judgement> = edges
.iter()
.enumerate()
.map(|(i, (w, l))| judgement(&format!("j{i}"), w, l))
.collect();
let refs: Vec<&Judgement> = rows.iter().collect();
let amap: AnchorMap = anchors.iter().map(|&(e, v)| (e.to_string(), v)).collect();
let cs = compile(&refs, &amap, QuarantinePolicy::Symmetric);
assert!(cs.quarantined.is_empty(), "fixture must be a feasible DAG");
cs
}
fn value(p: &Projection, e: &str) -> f64 {
p.get(e).unwrap_or_else(|| panic!("missing entity {e}")).0
}
fn golden(p: &Projection, e: &str, expected: f64, prov: ValueProvenance) {
let (got, got_prov) = *p.get(e).unwrap_or_else(|| panic!("missing entity {e}"));
assert!(
(got - expected).abs() < EPS,
"{e}: got {got:.4}, want {expected:.4}"
);
assert_eq!(got_prov, prov, "{e} provenance");
assert!(!got.is_nan(), "{e} is NaN");
}
fn chain(prefix: &str, n: usize) -> Vec<(String, String)> {
(0..n.saturating_sub(1))
.map(|i| (format!("{prefix}{i}"), format!("{prefix}{}", i + 1)))
.collect()
}
fn edge_refs(edges: &[(String, String)]) -> Vec<(&str, &str)> {
edges
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect()
}
#[test]
fn s1_chain8_gauge() {
let e = chain("n", 8);
let p = project_scenario(&edge_refs(&e), &[]);
for (i, want) in [
1.7778, 1.5556, 1.3333, 1.1111, 0.8889, 0.6667, 0.4444, 0.2222,
]
.into_iter()
.enumerate()
{
golden(&p, &format!("n{i}"), want, Gauge);
}
}
#[test]
fn s2_partial_order_gauge() {
let edges = [
("a", "b"),
("a", "c"),
("b", "d"),
("c", "d"),
("d", "e"),
("f", "g"),
];
let p = project_scenario(&edges, &[]);
golden(&p, "a", 1.6000, Gauge);
golden(&p, "b", 1.2000, Gauge);
golden(&p, "c", 1.2000, Gauge);
golden(&p, "d", 0.8000, Gauge);
golden(&p, "f", 1.3333, Gauge);
golden(&p, "e", 0.4000, Gauge);
golden(&p, "g", 0.6667, Gauge);
}
#[test]
fn s3_mid_anchor_budgeted() {
let e = chain("n", 8);
let p = project_scenario(&edge_refs(&e), &[("n4", 5.0)]);
golden(&p, "n0", 6.0000, Projected);
golden(&p, "n1", 5.7500, Projected);
golden(&p, "n2", 5.5000, Projected);
golden(&p, "n3", 5.2500, Projected);
golden(&p, "n4", 5.0000, Authored);
golden(&p, "n5", 4.7500, Projected);
golden(&p, "n6", 4.5000, Projected);
golden(&p, "n7", 4.2500, Projected);
}
#[test]
fn s4_low_anchor_deep_tail() {
let e = chain("m", 6);
let p = project_scenario(&edge_refs(&e), &[("m0", 0.5)]);
golden(&p, "m0", 0.5000, Authored);
golden(&p, "m1", 0.4167, Projected);
golden(&p, "m2", 0.3333, Projected);
golden(&p, "m3", 0.2500, Projected);
golden(&p, "m4", 0.1667, Projected);
golden(&p, "m5", 0.0833, Projected);
}
#[test]
fn s5_bracket_crowding_budgeted() {
let e = chain("b", 6);
let p = project_scenario(&edge_refs(&e), &[("b0", 8.0), ("b5", 2.0)]);
golden(&p, "b0", 8.0000, Authored);
golden(&p, "b1", 6.8000, Projected);
golden(&p, "b2", 5.6000, Projected);
golden(&p, "b3", 4.4000, Projected);
golden(&p, "b4", 3.2000, Projected);
golden(&p, "b5", 2.0000, Authored);
}
#[test]
fn s6_sparse_anchor_before_and_after() {
let edges = [
("p", "q"),
("q", "r"),
("p", "s"),
("s", "t"),
("t", "u"),
("q", "t"),
];
let before = project_scenario(&edges, &[]);
golden(&before, "p", 1.6000, Gauge);
golden(&before, "q", 1.2000, Gauge);
golden(&before, "s", 1.2000, Gauge);
golden(&before, "t", 0.8000, Gauge);
golden(&before, "r", 0.4000, Gauge);
golden(&before, "u", 0.4000, Gauge);
let after = project_scenario(&edges, &[("s", 5.0)]);
golden(&after, "p", 5.2500, Projected);
golden(&after, "q", 5.0000, Projected);
golden(&after, "s", 5.0000, Authored);
golden(&after, "t", 4.7500, Projected);
golden(&after, "u", 4.5000, Projected);
golden(&after, "r", 1.0000, Gauge);
assert!(value(&after, "u") > value(&after, "r"));
}
#[test]
fn s7_cross_window_edge() {
let edges = [("A8", "u"), ("u", "v"), ("v", "A2"), ("A5", "v")];
let p = project_scenario(&edges, &[("A8", 8.0), ("A5", 5.0), ("A2", 2.0)]);
golden(&p, "A8", 8.0000, Authored);
golden(&p, "u", 5.7500, Projected);
golden(&p, "A5", 5.0000, Authored);
golden(&p, "v", 3.5000, Projected);
golden(&p, "A2", 2.0000, Authored);
}
#[test]
fn s8_incremental_locality() {
let base = [("x", "y"), ("z", "w")];
let islands = project_scenario(&base, &[("x", 4.0)]);
golden(&islands, "x", 4.0000, Authored);
golden(&islands, "y", 3.7500, Projected);
golden(&islands, "z", 1.3333, Gauge);
golden(&islands, "w", 0.6667, Gauge);
let joined = project_scenario(&[("x", "y"), ("z", "w"), ("y", "z")], &[("x", 4.0)]);
golden(&joined, "x", 4.0000, Authored);
golden(&joined, "y", 3.7500, Projected);
golden(&joined, "z", 3.5000, Projected);
golden(&joined, "w", 3.2500, Projected);
}
#[test]
fn mixed_corpus_island() {
let edges = [
("a", "b"),
("a", "c"),
("b", "d"),
("c", "d"),
("d", "e"),
("f", "g"),
];
let p = project_scenario(&edges, &[("a", 2.0)]);
golden(&p, "a", 2.0000, Authored);
golden(&p, "b", 1.7500, Projected);
golden(&p, "c", 1.7500, Projected);
golden(&p, "d", 1.5000, Projected);
golden(&p, "e", 1.2500, Projected);
golden(&p, "f", 1.3333, Gauge);
golden(&p, "g", 0.6667, Gauge);
}
#[test]
fn singleton_island_lands_on_default_value() {
let mut rows = vec![judgement("j0", "a", "b")];
let mut eq = judgement("j1", "s1", "s2");
eq.response = Some(Response::Equal);
rows.push(eq);
let refs: Vec<&Judgement> = rows.iter().collect();
let cs = compile(&refs, &AnchorMap::new(), QuarantinePolicy::Symmetric);
assert!(cs.quarantined.is_empty(), "fixture must be a feasible DAG");
let p = project(&cs, &CFG);
golden(&p, "a", 1.3333, Gauge);
golden(&p, "b", 0.6667, Gauge);
golden(&p, "s1", 1.0000, Gauge);
golden(&p, "s2", 1.0000, Gauge);
}
#[test]
fn y1_join_y_gauge() {
let edges = [
("a0", "a1"),
("a1", "a2"),
("a2", "j"),
("b0", "b1"),
("b1", "j"),
];
let p = project_scenario(&edges, &[]);
golden(&p, "a0", 1.6000, Gauge);
golden(&p, "a1", 1.2000, Gauge);
golden(&p, "b0", 1.2000, Gauge);
golden(&p, "a2", 0.8000, Gauge);
golden(&p, "b1", 0.8000, Gauge);
golden(&p, "j", 0.4000, Gauge);
}
#[test]
fn y2_split_y_gauge() {
let edges = [
("h", "c0"),
("c0", "c1"),
("c1", "c2"),
("h", "d0"),
("d0", "d1"),
];
let p = project_scenario(&edges, &[]);
golden(&p, "h", 1.6000, Gauge);
golden(&p, "c0", 1.2000, Gauge);
golden(&p, "c1", 0.8000, Gauge);
golden(&p, "d0", 0.8000, Gauge);
golden(&p, "c2", 0.4000, Gauge);
golden(&p, "d1", 0.4000, Gauge);
}
#[test]
fn y3_sensitivity_extend_short_arm() {
let edges = [
("a0", "a1"),
("a1", "a2"),
("a2", "j"),
("b0", "b1"),
("b1", "b2"),
("b2", "j"),
];
let p = project_scenario(&edges, &[]);
golden(&p, "a0", 1.6000, Gauge);
golden(&p, "b0", 1.6000, Gauge);
golden(&p, "a1", 1.2000, Gauge);
golden(&p, "b1", 1.2000, Gauge);
golden(&p, "a2", 0.8000, Gauge);
golden(&p, "b2", 0.8000, Gauge);
golden(&p, "j", 0.4000, Gauge);
}
#[test]
fn y4_pin_cross_judgement() {
let edges = [
("a0", "a1"),
("a1", "a2"),
("a2", "j"),
("b0", "b1"),
("b1", "j"),
("b0", "a1"),
];
let p = project_scenario(&edges, &[]);
golden(&p, "a0", 1.6000, Gauge);
golden(&p, "b0", 1.6000, Gauge);
golden(&p, "a1", 1.2000, Gauge);
golden(&p, "a2", 0.8000, Gauge);
golden(&p, "b1", 0.8000, Gauge);
golden(&p, "j", 0.4000, Gauge);
}
#[test]
fn y5_pin_with_anchor_collision() {
let edges = [
("a0", "a1"),
("a1", "a2"),
("a2", "j"),
("b0", "b1"),
("b1", "j"),
];
let p = project_scenario(&edges, &[("b0", 3.0)]);
golden(&p, "a0", 3.2500, Projected);
golden(&p, "a1", 3.0000, Projected);
golden(&p, "b0", 3.0000, Authored);
golden(&p, "a2", 2.7500, Projected);
golden(&p, "b1", 2.7500, Projected);
golden(&p, "j", 2.5000, Projected);
}
#[test]
fn y6_bracketed_arms() {
let edges = [
("T", "a1"),
("a1", "a2"),
("a2", "a3"),
("a3", "B"),
("T", "b1"),
("b1", "B"),
];
let p = project_scenario(&edges, &[("T", 8.0), ("B", 2.0)]);
golden(&p, "T", 8.0000, Authored);
golden(&p, "a1", 6.5000, Projected);
golden(&p, "a2", 5.0000, Projected);
golden(&p, "b1", 5.0000, Projected);
golden(&p, "a3", 3.5000, Projected);
golden(&p, "B", 2.0000, Authored);
}
#[test]
fn y7_pin_inside_bracket() {
let edges = [
("T", "a1"),
("a1", "a2"),
("a2", "a3"),
("a3", "B"),
("T", "b1"),
("b1", "B"),
("a2", "b1"),
];
let p = project_scenario(&edges, &[("T", 8.0), ("B", 2.0)]);
golden(&p, "T", 8.0000, Authored);
golden(&p, "a1", 6.5000, Projected);
golden(&p, "a2", 5.0000, Projected);
golden(&p, "a3", 3.5000, Projected);
golden(&p, "b1", 3.5000, Projected);
golden(&p, "B", 2.0000, Authored);
assert!((value(&p, "a2") - 5.0).abs() < EPS);
}
#[test]
fn n1_negative_ceiling_tail() {
let e = chain("k", 4);
let p = project_scenario(&edge_refs(&e), &[("k0", -0.5)]);
golden(&p, "k0", -0.5000, Authored);
golden(&p, "k1", -0.7500, Projected);
golden(&p, "k2", -1.0000, Projected);
golden(&p, "k3", -1.2500, Projected);
}
#[test]
fn n2_sign_crossing_bracket() {
let e = chain("j", 4);
let p = project_scenario(&edge_refs(&e), &[("j0", 3.0), ("j3", -2.0)]);
golden(&p, "j0", 3.0000, Authored);
golden(&p, "j1", 1.3333, Projected);
golden(&p, "j2", -0.3333, Projected);
golden(&p, "j3", -2.0000, Authored);
}
#[test]
fn n3_branch_floor_and_ceiling() {
let edges = [("A", "X"), ("X", "Y"), ("X", "Z")];
let p = project_scenario(&edges, &[("A", 10.0), ("Z", 8.0)]);
golden(&p, "A", 10.0000, Authored);
golden(&p, "X", 9.7500, Projected);
golden(&p, "Y", 9.5000, Projected);
golden(&p, "Z", 8.0000, Authored);
}
#[test]
fn n4_multi_ceiling_tiebreak() {
let edges = [("A", "X"), ("B", "W"), ("W", "X"), ("X", "Y")];
let p = project_scenario(&edges, &[("A", 10.0), ("B", 9.0)]);
golden(&p, "A", 10.0000, Authored);
golden(&p, "B", 9.0000, Authored);
golden(&p, "W", 8.7500, Projected);
golden(&p, "X", 8.5000, Projected);
golden(&p, "Y", 8.2500, Projected);
}
fn generated_cases() -> impl Iterator<Item = (Vec<(String, String)>, Vec<(&'static str, f64)>)>
{
const PAIRS: [(&str, &str); 6] = [
("A", "B"),
("A", "C"),
("A", "D"),
("B", "C"),
("B", "D"),
("C", "D"),
];
let configs: [Vec<(&'static str, f64)>; 3] = [
vec![],
vec![("A", 5.0), ("D", 1.0)],
vec![("A", 8.0), ("B", 3.0), ("D", 1.0)],
];
configs.into_iter().flat_map(move |cfg| {
(0..4_u32.pow(6)).filter_map(move |mask| {
let mut edges = Vec::new();
for (i, (a, b)) in PAIRS.iter().enumerate() {
match (mask / 4_u32.pow(i as u32)) % 4 {
1 => edges.push(((*a).to_string(), (*b).to_string())),
2 => edges.push(((*b).to_string(), (*a).to_string())),
_ => {}
}
}
Some((edges, cfg.clone()))
})
})
}
#[test]
fn p10_generated_order_consistency_no_nan() {
for (edges, anchors) in generated_cases() {
let refs = edge_refs(&edges);
let rows: Vec<Judgement> = refs
.iter()
.enumerate()
.map(|(i, (w, l))| judgement(&format!("j{i}"), w, l))
.collect();
let jrefs: Vec<&Judgement> = rows.iter().collect();
let amap: AnchorMap = anchors.iter().map(|&(e, v)| (e.to_string(), v)).collect();
let cs = compile(&jrefs, &amap, QuarantinePolicy::Symmetric);
let p = project(&cs, &CFG);
for (winner, loser) in cs.edges.keys() {
let wv = cs.classes.get(winner).and_then(|c| class_value(&cs, &p, c));
let lv = cs.classes.get(loser).and_then(|c| class_value(&cs, &p, c));
if let (Some(w), Some(l)) = (wv, lv) {
assert!(w > l, "edge {winner}>{loser}: {w} !> {l}");
}
}
for (_, (val, _)) in &p {
assert!(!val.is_nan(), "NaN projected value");
}
}
}
fn class_value(cs: &super::ConstraintSet, p: &Projection, class: &str) -> Option<f64> {
cs.classes
.iter()
.find(|(_, c)| c.as_str() == class)
.and_then(|(entity, _)| p.get(entity).map(|&(v, _)| v))
}
#[test]
fn p11_determinism_under_permuted_input() {
let edges = [("A", "B"), ("B", "C"), ("C", "D"), ("A", "E"), ("E", "D")];
let anchors = [("A", 9.0), ("D", 1.0)];
let rows: Vec<Judgement> = edges
.iter()
.enumerate()
.map(|(i, (w, l))| judgement(&format!("j{i}"), w, l))
.collect();
let amap: AnchorMap = anchors.iter().map(|&(e, v)| (e.to_string(), v)).collect();
let forward: Vec<&Judgement> = rows.iter().collect();
let backward: Vec<&Judgement> = rows.iter().rev().collect();
let p1 = project(&compile(&forward, &amap, QuarantinePolicy::Symmetric), &CFG);
let p2 = project(
&compile(&backward, &amap, QuarantinePolicy::Symmetric),
&CFG,
);
assert_eq!(
p1, p2,
"projection must be bitwise-identical across input order"
);
}
#[test]
fn p12_locality_disjoint_anchored_components() {
let y_edges = [("P", "Q")];
let x_before = [("A", "B")];
let anchors = [("A", 10.0), ("P", 5.0)];
let combined = |x: &[(&str, &str)]| -> Projection {
let edges: Vec<(&str, &str)> = x.iter().chain(y_edges.iter()).copied().collect();
project_scenario(&edges, &anchors)
};
let base = combined(&x_before);
let perturbed = combined(&[("A", "B"), ("B", "C")]);
for e in ["P", "Q"] {
assert_eq!(
base.get(e),
perturbed.get(e),
"component Y entity {e} moved under a disjoint-X evidence delta"
);
}
}
#[test]
fn p12_locality_anchored_perturbation_freezes_gauge_island() {
let combined = |x: &[(&str, &str)]| -> Projection {
let edges: Vec<(&str, &str)> = x.iter().chain([("P", "Q")].iter()).copied().collect();
project_scenario(&edges, &[("A", 10.0)])
};
let base = combined(&[("A", "B")]);
let perturbed = combined(&[("A", "B"), ("B", "C")]);
for e in ["P", "Q"] {
assert_eq!(
base.get(e),
perturbed.get(e),
"gauge island entity {e} moved under a disjoint anchored-X delta"
);
}
}
#[test]
fn p12_locality_gauge_island_perturbation_freezes_anchored() {
let combined = |y: &[(&str, &str)]| -> Projection {
let edges: Vec<(&str, &str)> = [("A", "B")].iter().chain(y.iter()).copied().collect();
project_scenario(&edges, &[("A", 10.0)])
};
let base = combined(&[("P", "Q")]);
let perturbed = combined(&[("P", "Q"), ("Q", "R")]);
for e in ["A", "B"] {
assert_eq!(
base.get(e),
perturbed.get(e),
"anchored entity {e} moved under a disjoint gauge-island delta"
);
}
}
#[test]
fn p12_locality_first_anchor_leaves_disjoint_island_frozen() {
let edges = [("A", "B"), ("P", "Q")];
let pure = project_scenario(&edges, &[]);
golden(&pure, "P", 1.3333, Gauge);
golden(&pure, "Q", 0.6667, Gauge);
let first_anchor = project_scenario(&edges, &[("A", 10.0)]);
for e in ["P", "Q"] {
assert_eq!(
pure.get(e),
first_anchor.get(e),
"island entity {e} moved when the corpus's first anchor landed in X"
);
}
}
#[test]
fn p14_scoped_affine_equivariance() {
let e = chain("b", 6);
let refs = edge_refs(&e);
let base = project_scenario(&refs, &[("b0", 8.0), ("b5", 2.0)]);
let shifted = project_scenario(&refs, &[("b0", 18.0), ("b5", 12.0)]);
for i in 1..5 {
let k = format!("b{i}");
assert!(
(value(&shifted, &k) - (value(&base, &k) + 10.0)).abs() < EPS,
"shift {k}"
);
}
let scaled = project_scenario(&refs, &[("b0", 16.0), ("b5", 4.0)]);
for i in 1..5 {
let k = format!("b{i}");
assert!(
(value(&scaled, &k) - value(&base, &k) * 2.0).abs() < EPS,
"scale {k}"
);
}
}
#[test]
fn p6_synthetic_floor_strictly_below_ceiling() {
for &(anchor, positive) in &[(0.5_f64, true), (-0.5_f64, false)] {
for depth in 1..6 {
let e = chain("t", depth + 1);
let p = project_scenario(&edge_refs(&e), &[("t0", anchor)]);
let mut prev = anchor;
for i in 1..=depth {
let cur = value(&p, &format!("t{i}"));
assert!(cur < prev, "not strictly decreasing at t{i}");
assert!(cur < anchor, "t{i} not strictly below ceiling");
if positive {
assert!(cur >= 0.0, "positive ceiling manufactured a negative t{i}");
}
prev = cur;
}
}
}
}
#[test]
fn p8_gauge_spread_positive_and_centred() {
let e = chain("n", 8);
let p = project_scenario(&edge_refs(&e), &[]);
let vals: Vec<f64> = (0..8).map(|i| value(&p, &format!("n{i}"))).collect();
let lo = vals.iter().copied().fold(f64::INFINITY, f64::min);
let hi = vals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
assert!(lo > 0.0, "gauge below 0");
assert!(hi < 2.0 * CFG.gauge_center, "gauge above 2·default");
assert!(
(lo + hi - 2.0 * CFG.gauge_center).abs() < EPS,
"not centred"
);
}
#[test]
fn ex3_gauge_step_sweep_order_and_provenance_invariant() {
let tail = chain("m", 6); let head = [("x", "y"), ("z", "w"), ("y", "z")]; let mut step_milli = 5_u32;
while step_milli <= 1000 {
let cfg = ProjectionCfg {
gauge_step: f64::from(step_milli) / 1000.0,
gauge_center: 1.0,
};
let tp = project(&compiled(&edge_refs(&tail), &[("m0", 0.5)]), &cfg);
assert_eq!(tp.get("m0").map(|&(_, pr)| pr), Some(Authored));
for i in 1..6 {
assert_eq!(
tp.get(&format!("m{i}")).map(|&(_, pr)| pr),
Some(Projected),
"tail provenance drifted at step {step_milli}"
);
}
for i in 0..5 {
assert!(
value(&tp, &format!("m{i}")) > value(&tp, &format!("m{}", i + 1)),
"tail order broke at step {step_milli}"
);
}
let hp = project(&compiled(&head, &[("x", 4.0)]), &cfg);
for (winner, loser) in &head {
assert!(
value(&hp, winner) > value(&hp, loser),
"head order {winner}>{loser} broke at step {step_milli}"
);
}
assert_eq!(hp.get("x").map(|&(_, pr)| pr), Some(Authored));
step_milli += 50;
}
}
}