extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::fixed::NonNegativeFixed;
pub const MAX_LENS_MAGNITUDE: u32 = crate::generated_profile::MAX_LENS_MAGNITUDE;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CascadeTree {
parent: Vec<Option<usize>>,
mass: Vec<NonNegativeFixed>,
rho: Vec<NonNegativeFixed>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CascadeRefusal {
LengthMismatch { parents: usize, masses: usize },
ParentOutOfRange {
node: usize,
parent: usize,
len: usize,
},
Cyclic { node: usize },
NoRoot,
ExponentOutOfRange { lens: i32, max_magnitude: u32 },
DegenerateSiblingSet { parent: Option<usize> },
DegenerateSubtreeLeaves { node: usize },
ZeroMassUnderNegativeLens { node: usize, lens: i32 },
NumericFault {
operation: NumericContext,
node: usize,
error_code: u32,
},
EscortUnderflow {
node: usize,
lens: i32,
mass_bits: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericContext {
EscortWeight { lens: i32 },
EscortReciprocal { lens: i32 },
SiblingSum,
SubtreeLeafSum,
ShareDivision,
DescendantSplit,
FlatSplit,
FlowAccumulation,
FlatAccumulation,
FinalAccumulation,
}
#[inline]
pub fn admit_fixed(
value: NonNegativeFixed,
operation: NumericContext,
node: usize,
) -> Result<NonNegativeFixed, CascadeRefusal> {
if value.err == u32::MAX {
return Ok(value);
}
Err(CascadeRefusal::NumericFault {
operation,
node,
error_code: value.err,
})
}
impl CascadeTree {
pub fn new(
parent: Vec<Option<usize>>,
mass: Vec<NonNegativeFixed>,
) -> Result<Self, CascadeRefusal> {
let rho = vec![NonNegativeFixed::ONE; parent.len()];
Self::with_rho(parent, mass, rho)
}
pub fn with_rho(
parent: Vec<Option<usize>>,
mass: Vec<NonNegativeFixed>,
rho: Vec<NonNegativeFixed>,
) -> Result<Self, CascadeRefusal> {
if parent.len() != mass.len() {
return Err(CascadeRefusal::LengthMismatch {
parents: parent.len(),
masses: mass.len(),
});
}
if parent.len() != rho.len() {
return Err(CascadeRefusal::LengthMismatch {
parents: parent.len(),
masses: rho.len(),
});
}
let len = parent.len();
for (node, slot) in parent.iter().enumerate() {
if let Some(p) = slot {
if *p >= len {
return Err(CascadeRefusal::ParentOutOfRange {
node,
parent: *p,
len,
});
}
}
}
Ok(Self { parent, mass, rho })
}
#[must_use]
pub fn rho(&self) -> &[NonNegativeFixed] {
&self.rho
}
#[must_use]
pub fn len(&self) -> usize {
self.parent.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.parent.is_empty()
}
#[must_use]
pub fn parent(&self) -> &[Option<usize>] {
&self.parent
}
#[must_use]
pub fn mass(&self) -> &[NonNegativeFixed] {
&self.mass
}
}
fn escort_power(
mass: NonNegativeFixed,
lens: i32,
node: usize,
) -> Result<NonNegativeFixed, CascadeRefusal> {
let magnitude = lens.unsigned_abs();
if magnitude > MAX_LENS_MAGNITUDE {
return Err(CascadeRefusal::ExponentOutOfRange {
lens,
max_magnitude: MAX_LENS_MAGNITUDE,
});
}
debug_assert_ne!(
lens, 0,
"escort_power is only for lens != 0; callers handle lens == 0 themselves"
);
let mut accumulated = NonNegativeFixed::ONE;
for _ in 0..magnitude {
accumulated = accumulated.saturating_mul(mass);
}
let accumulated = admit_fixed(accumulated, NumericContext::EscortWeight { lens }, node)?;
if accumulated.to_bits() == 0 && mass.to_bits() != 0 {
return Err(CascadeRefusal::EscortUnderflow {
node,
lens,
mass_bits: mass.to_bits(),
});
}
if lens > 0 {
return Ok(accumulated);
}
if accumulated.to_bits() == 0 {
return Err(CascadeRefusal::ZeroMassUnderNegativeLens { node, lens });
}
let reciprocal = (1u64 << 32) / u64::from(accumulated.to_bits());
if reciprocal > u32::MAX as u64 {
return Err(CascadeRefusal::NumericFault {
operation: NumericContext::EscortReciprocal { lens },
node,
error_code: crate::allocator::StabilityRefusal::NumericRangeExceeded as u32,
});
}
Ok(NonNegativeFixed::from_bits(reciprocal as u32))
}
pub fn escort_weight(
mass: NonNegativeFixed,
lens: i32,
node: usize,
) -> Result<NonNegativeFixed, CascadeRefusal> {
if lens == 0 {
return Ok(uniform_sibling_weight(mass, node));
}
escort_power(mass, lens, node)
}
#[must_use]
pub fn uniform_sibling_weight(_mass: NonNegativeFixed, _node: usize) -> NonNegativeFixed {
NonNegativeFixed::ONE
}
pub fn escort_weight_support(
mass: NonNegativeFixed,
lens: i32,
node: usize,
) -> Result<NonNegativeFixed, CascadeRefusal> {
if lens == 0 {
if mass.to_bits() == 0 {
return Ok(NonNegativeFixed::ZERO);
}
return Ok(NonNegativeFixed::ONE);
}
escort_power(mass, lens, node)
}
fn depths(tree: &CascadeTree) -> Result<Vec<usize>, CascadeRefusal> {
let len = tree.len();
let mut depth = vec![0usize; len];
for (node, slot) in depth.iter_mut().enumerate() {
let mut steps = 0usize;
let mut cursor = node;
while let Some(p) = tree.parent[cursor] {
steps += 1;
if steps > len {
return Err(CascadeRefusal::Cyclic { node });
}
cursor = p;
}
*slot = steps;
}
Ok(depth)
}
pub fn consequence_mass(
tree: &CascadeTree,
lenses: &[i32],
) -> Result<Vec<NonNegativeFixed>, CascadeRefusal> {
consequence_mass_with_sink(tree, lenses, &mut NoTrace)
}
trait AllocationTraceSink {
fn record(&mut self, step: AllocationStep);
}
struct NoTrace;
impl AllocationTraceSink for NoTrace {
#[inline(always)]
fn record(&mut self, _step: AllocationStep) {}
}
#[derive(Default)]
struct VecTrace {
steps: Vec<AllocationStep>,
}
impl AllocationTraceSink for VecTrace {
fn record(&mut self, step: AllocationStep) {
self.steps.push(step);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AllocationStep {
pub node: usize,
pub parent: Option<usize>,
pub wave: usize,
pub input_share: NonNegativeFixed,
pub child_shares: Vec<(usize, NonNegativeFixed)>,
pub child_sum: NonNegativeFixed,
pub residual_bits: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AllocationTrace {
pub steps: Vec<AllocationStep>,
pub leaves: Vec<NonNegativeFixed>,
}
pub fn consequence_mass_traced(
tree: &CascadeTree,
lenses: &[i32],
) -> Result<AllocationTrace, CascadeRefusal> {
let mut sink = VecTrace::default();
let leaves = consequence_mass_with_sink(tree, lenses, &mut sink)?;
Ok(AllocationTrace {
steps: sink.steps,
leaves,
})
}
fn consequence_mass_with_sink(
tree: &CascadeTree,
lenses: &[i32],
sink: &mut impl AllocationTraceSink,
) -> Result<Vec<NonNegativeFixed>, CascadeRefusal> {
let len = tree.len();
if len == 0 {
return Ok(Vec::new());
}
if !tree.parent.iter().any(Option::is_none) {
return Err(CascadeRefusal::NoRoot);
}
let depth = depths(tree)?;
let lens_at = |d: usize| -> i32 {
if lenses.is_empty() {
0
} else {
lenses[d.min(lenses.len() - 1)]
}
};
let mut weight = Vec::with_capacity(len);
for (node, (&mass, &node_depth)) in tree.mass.iter().zip(depth.iter()).enumerate() {
let lens = lens_at(node_depth);
let w = if lens == 0 {
uniform_sibling_weight(mass, node)
} else {
escort_power(mass, lens, node)?
};
weight.push(w);
}
let is_leaf: Vec<bool> = (0..len)
.map(|node| !tree.parent.contains(&Some(node)))
.collect();
let children: Vec<Vec<usize>> = (0..len)
.map(|v| (0..len).filter(|c| tree.parent[*c] == Some(v)).collect())
.collect();
let mut subtree_leaves: Vec<Vec<usize>> = vec![Vec::new(); len];
for leaf in (0..len).filter(|node| is_leaf[*node]) {
let mut cursor = tree.parent[leaf];
while let Some(ancestor) = cursor {
subtree_leaves[ancestor].push(leaf);
cursor = tree.parent[ancestor];
}
}
let mut flow = vec![NonNegativeFixed::ZERO; len];
let mut flat = vec![NonNegativeFixed::ZERO; len];
let roots: Vec<usize> = (0..len)
.filter(|node| tree.parent[*node].is_none())
.collect();
let mut root_total = NonNegativeFixed::ZERO;
for &root in &roots {
root_total = admit_fixed(
root_total.saturating_add(weight[root]),
NumericContext::SiblingSum,
root,
)?;
}
if root_total.to_bits() == 0 {
return Err(CascadeRefusal::DegenerateSiblingSet { parent: None });
}
for &root in &roots {
flow[root] = admit_fixed(
weight[root].saturating_div(root_total),
NumericContext::ShareDivision,
root,
)?;
}
let mut order: Vec<usize> = (0..len).collect();
order.sort_by_key(|node| depth[*node]);
for v in order {
if is_leaf[v] {
continue;
}
let descendant_part = admit_fixed(
tree.rho[v].saturating_mul(flow[v]),
NumericContext::DescendantSplit,
v,
)?;
let flat_part = NonNegativeFixed::ONE
.saturating_sub(tree.rho[v])
.saturating_mul(flow[v]);
let flat_part = admit_fixed(flat_part, NumericContext::FlatSplit, v)?;
let mut child_total = NonNegativeFixed::ZERO;
for &c in &children[v] {
child_total = admit_fixed(
child_total.saturating_add(weight[c]),
NumericContext::SiblingSum,
c,
)?;
}
if child_total.to_bits() == 0 {
return Err(CascadeRefusal::DegenerateSiblingSet { parent: Some(v) });
}
let mut child_shares: Vec<(usize, NonNegativeFixed)> =
Vec::with_capacity(children[v].len());
let mut child_sum = NonNegativeFixed::ZERO;
for &c in &children[v] {
let share = admit_fixed(
weight[c].saturating_div(child_total),
NumericContext::ShareDivision,
c,
)?;
let contribution = admit_fixed(
descendant_part.saturating_mul(share),
NumericContext::FlowAccumulation,
c,
)?;
flow[c] = admit_fixed(
flow[c].saturating_add(contribution),
NumericContext::FlowAccumulation,
c,
)?;
child_sum = child_sum.saturating_add(contribution);
child_shares.push((c, contribution));
}
sink.record(AllocationStep {
node: v,
parent: tree.parent[v],
wave: depth[v],
input_share: descendant_part,
residual_bits: descendant_part.to_bits() as i64 - child_sum.to_bits() as i64,
child_shares,
child_sum,
});
if flat_part.to_bits() == 0 {
continue;
}
let mut leaf_total = NonNegativeFixed::ZERO;
for &x in &subtree_leaves[v] {
leaf_total = admit_fixed(
leaf_total.saturating_add(weight[x]),
NumericContext::SubtreeLeafSum,
x,
)?;
}
if leaf_total.to_bits() == 0 {
return Err(CascadeRefusal::DegenerateSubtreeLeaves { node: v });
}
for &x in &subtree_leaves[v] {
let share = admit_fixed(
weight[x].saturating_div(leaf_total),
NumericContext::ShareDivision,
x,
)?;
flat[x] = admit_fixed(
flat[x].saturating_add(flat_part.saturating_mul(share)),
NumericContext::FlatAccumulation,
x,
)?;
}
}
(0..len)
.map(|node| {
admit_fixed(
flow[node].saturating_add(flat[node]),
NumericContext::FinalAccumulation,
node,
)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn mass(x: f32) -> NonNegativeFixed {
NonNegativeFixed::from_bits((x * 65536.0).round() as u32)
}
#[test]
fn escort_weight_support_is_zero_at_q_zero_for_zero_mass() {
assert_eq!(
escort_weight_support(NonNegativeFixed::ZERO, 0, 0).unwrap(),
NonNegativeFixed::ZERO
);
}
#[test]
fn escort_weight_support_is_one_at_q_zero_for_nonzero_mass() {
assert_eq!(
escort_weight_support(mass(3.0), 0, 0).unwrap(),
NonNegativeFixed::ONE
);
}
#[test]
fn escort_weight_support_matches_escort_weight_for_nonzero_lens() {
for lens in [-2, -1, 1, 2] {
for m in [mass(0.0), mass(0.5), mass(1.0), mass(3.0)] {
assert_eq!(
escort_weight_support(m, lens, 0),
escort_weight(m, lens, 0),
"lens={lens} m={m:?}"
);
}
}
}
#[test]
fn uniform_sibling_weight_is_one_regardless_of_mass() {
assert_eq!(
uniform_sibling_weight(NonNegativeFixed::ZERO, 0),
NonNegativeFixed::ONE
);
assert_eq!(uniform_sibling_weight(mass(7.0), 0), NonNegativeFixed::ONE);
}
#[test]
fn escort_power_ulp_loss_witness() {
let mut max_loss: i64 = 0;
let mut witness: Option<(u32, i32, u32, u32)> = None;
let mass_bits_grid: [u32; 7] = [1, 100, 32768, 65500, 65535, 65536, 131072];
for &mb in &mass_bits_grid {
let m = NonNegativeFixed::from_bits(mb);
let m_real = f64::from(mb) / 65536.0;
for lens in 1..=(MAX_LENS_MAGNITUDE as i32) {
for signed_lens in [lens, -lens] {
let Ok(actual) = escort_power(m, signed_lens, 0) else {
continue; };
let exact_real = m_real.powi(signed_lens);
if !exact_real.is_finite() || exact_real < 0.0 || exact_real > 65535.0 {
continue; }
let exact_bits = (exact_real * 65536.0).round() as i64;
let loss = (exact_bits - actual.to_bits() as i64).abs();
if loss > max_loss {
max_loss = loss;
witness = Some((mb, signed_lens, exact_bits as u32, actual.to_bits()));
}
}
}
}
extern crate std;
std::eprintln!(
"escort_power ULP-loss witness: max_loss={max_loss} details={witness:?} \
(compare against the ticket's claimed 498 -- no such value was found or reproduced \
for this admitted domain)"
);
assert!(
max_loss <= 32,
"escort_power ULP-loss witness regressed: max_loss={max_loss} details={witness:?} \
(previously observed <= a small single-digit/low-double-digit bound; nowhere near \
the ticket's claimed 498)"
);
}
#[test]
fn escort_weight_at_q_zero_still_matches_uniform_sibling_weight() {
assert_eq!(
escort_weight(NonNegativeFixed::ZERO, 0, 0).unwrap(),
uniform_sibling_weight(NonNegativeFixed::ZERO, 0)
);
}
fn small_tree() -> CascadeTree {
CascadeTree::new(
vec![None, Some(0), Some(0), Some(1), Some(1)],
vec![
mass(1.0), mass(3.0), mass(1.0), mass(2.0), mass(1.0), ],
)
.unwrap()
}
#[test]
fn traced_leaves_match_untraced_bit_for_bit() {
let tree = small_tree();
let lenses = [1i32];
let untraced = consequence_mass(&tree, &lenses).unwrap();
let traced = consequence_mass_traced(&tree, &lenses).unwrap();
assert_eq!(traced.leaves, untraced, "projection equivalence");
}
#[test]
fn trace_is_deterministic_across_identical_calls() {
let tree = small_tree();
let lenses = [1i32];
let first = consequence_mass_traced(&tree, &lenses).unwrap();
let second = consequence_mass_traced(&tree, &lenses).unwrap();
assert_eq!(first, second);
}
#[test]
fn trace_steps_only_cover_internal_nodes_and_reference_real_children() {
let tree = small_tree();
let lenses = [1i32];
let trace = consequence_mass_traced(&tree, &lenses).unwrap();
let stepped_nodes: alloc::vec::Vec<usize> = trace.steps.iter().map(|s| s.node).collect();
assert_eq!(
stepped_nodes,
alloc::vec![0, 1],
"depth-ascending order, internal nodes only"
);
for step in &trace.steps {
for &(child, _) in &step.child_shares {
assert_eq!(
tree.parent[child],
Some(step.node),
"every recorded child must actually be a child of the stepped node"
);
}
}
}
#[test]
fn residual_bits_matches_its_own_definition() {
let tree = small_tree();
let lenses = [1i32];
let trace = consequence_mass_traced(&tree, &lenses).unwrap();
for step in &trace.steps {
let expected = step.input_share.to_bits() as i64 - step.child_sum.to_bits() as i64;
assert_eq!(step.residual_bits, expected);
}
}
}