use super::itc::{Event, Id, Stamp};
fn split_id(id: &Id) -> (Id, Id) {
match id {
Id::Zero => (Id::zero(), Id::zero()),
Id::One => (
Id::branch(Id::one(), Id::zero()),
Id::branch(Id::zero(), Id::one()),
),
Id::Branch(l, r) => {
if !l.is_zero() && r.is_zero() {
let (ll, lr) = split_id(l);
(Id::branch(ll, Id::zero()), Id::branch(lr, Id::zero()))
} else if l.is_zero() && !r.is_zero() {
let (rl, rr) = split_id(r);
(Id::branch(Id::zero(), rl), Id::branch(Id::zero(), rr))
} else {
(
Id::branch((**l).clone(), Id::zero()),
Id::branch(Id::zero(), (**r).clone()),
)
}
}
}
}
fn sum_id(a: &Id, b: &Id) -> Id {
match (a, b) {
(Id::Zero, _) => b.clone(),
(_, Id::Zero) => a.clone(),
(Id::Branch(al, ar), Id::Branch(bl, br)) => Id::branch(sum_id(al, bl), sum_id(ar, br)),
_ => Id::one(),
}
}
fn join_event(a: &Event, b: &Event) -> Event {
match (a, b) {
(Event::Leaf(na), Event::Leaf(nb)) => Event::leaf((*na).max(*nb)),
(Event::Leaf(na), Event::Branch(nb, bl, br)) => {
if *na >= b.max_value() {
Event::leaf(*na)
} else {
let lifted_a_l = Event::leaf(na.saturating_sub(*nb));
let lifted_a_r = Event::leaf(na.saturating_sub(*nb));
Event::branch(
*nb,
join_event(&lifted_a_l, bl),
join_event(&lifted_a_r, br),
)
}
}
(Event::Branch(na, al, ar), Event::Leaf(nb)) => {
if *nb >= a.max_value() {
Event::leaf(*nb)
} else {
let lifted_b_l = Event::leaf(nb.saturating_sub(*na));
let lifted_b_r = Event::leaf(nb.saturating_sub(*na));
Event::branch(
*na,
join_event(al, &lifted_b_l),
join_event(ar, &lifted_b_r),
)
}
}
(Event::Branch(na, al, ar), Event::Branch(nb, bl, br)) => {
if na >= nb {
let diff = na - nb;
let lifted_a_left = (**al).clone().lift(diff);
let lifted_a_right = (**ar).clone().lift(diff);
Event::branch(
*nb,
join_event(&lifted_a_left, bl),
join_event(&lifted_a_right, br),
)
} else {
let diff = nb - na;
let lifted_b_left = (**bl).clone().lift(diff);
let lifted_b_right = (**br).clone().lift(diff);
Event::branch(
*na,
join_event(al, &lifted_b_left),
join_event(ar, &lifted_b_right),
)
}
}
}
}
fn leq_event(a: &Event, b: &Event) -> bool {
match (a, b) {
(Event::Leaf(na), Event::Leaf(nb)) => na <= nb,
(Event::Leaf(na), Event::Branch(nb, bl, br)) => {
if *na <= *nb {
true
} else {
let remainder = na - nb;
leq_event(&Event::leaf(remainder), bl) && leq_event(&Event::leaf(remainder), br)
}
}
(Event::Branch(_na, _al, _ar), Event::Leaf(nb)) => {
a.max_value() <= *nb
}
(Event::Branch(na, al, ar), Event::Branch(nb, bl, br)) => {
if na <= nb {
let diff = nb - na;
let lifted_b_left = (**bl).clone().lift(diff);
let lifted_b_right = (**br).clone().lift(diff);
leq_event(al, &lifted_b_left) && leq_event(ar, &lifted_b_right)
} else {
let diff = na - nb;
let lifted_a_left = (**al).clone().lift(diff);
let lifted_a_right = (**ar).clone().lift(diff);
leq_event(&lifted_a_left, bl) && leq_event(&lifted_a_right, br)
}
}
}
}
fn fill(id: &Id, event: &Event) -> (Event, bool) {
match (id, event) {
(Id::Zero, _) | (Id::One, Event::Leaf(_)) => (event.clone(), false),
(Id::One, Event::Branch(n, l, r)) => {
let max_child = l.max_value().max(r.max_value());
(Event::leaf(n + max_child), true)
}
(Id::Branch(il, ir), Event::Leaf(n)) => {
let (el, changed_l) = fill(il, &Event::leaf(0));
let (er, changed_r) = fill(ir, &Event::leaf(0));
if changed_l || changed_r {
(Event::branch(*n, el, er), true)
} else {
(Event::Leaf(*n), false)
}
}
(Id::Branch(il, ir), Event::Branch(n, el, er)) => {
let (new_l, changed_l) = fill(il, el);
let (new_r, changed_r) = fill(ir, er);
if changed_l || changed_r {
(Event::branch(*n, new_l, new_r), true)
} else {
(event.clone(), false)
}
}
}
}
fn grow(id: &Id, event: &Event) -> Option<(Event, u32)> {
match (id, event) {
(Id::One, Event::Leaf(n)) => {
Some((Event::leaf(n + 1), 0))
}
(Id::Zero, _) => None,
(Id::One, Event::Branch(n, l, r)) => {
let opt_l = grow(&Id::one(), l);
let opt_r = grow(&Id::one(), r);
match (opt_l, opt_r) {
(Some((new_l, cl)), Some((new_r, cr))) => {
if cl <= cr {
Some((Event::branch(*n, new_l, (**r).clone()), cl))
} else {
Some((Event::branch(*n, (**l).clone(), new_r), cr))
}
}
(Some((new_l, cl)), None) => Some((Event::branch(*n, new_l, (**r).clone()), cl)),
(None, Some((new_r, cr))) => Some((Event::branch(*n, (**l).clone(), new_r), cr)),
(None, None) => None,
}
}
(Id::Branch(il, ir), Event::Leaf(n)) => {
let opt_l = grow(il, &Event::leaf(0));
let opt_r = grow(ir, &Event::leaf(0));
match (opt_l, opt_r) {
(Some((new_l, cl)), Some((new_r, cr))) => {
if cl < cr {
Some((Event::branch(*n, new_l, Event::leaf(0)), cl + 1_000))
} else {
Some((Event::branch(*n, Event::leaf(0), new_r), cr + 1_000))
}
}
(Some((new_l, cl)), None) => {
Some((Event::branch(*n, new_l, Event::leaf(0)), cl + 1_000))
}
(None, Some((new_r, cr))) => {
Some((Event::branch(*n, Event::leaf(0), new_r), cr + 1_000))
}
(None, None) => None,
}
}
(Id::Branch(il, ir), Event::Branch(n, el, er)) => {
let opt_l = grow(il, el);
let opt_r = grow(ir, er);
match (opt_l, opt_r) {
(Some((new_l, cl)), Some((new_r, cr))) => {
if cl <= cr {
Some((Event::branch(*n, new_l, (**er).clone()), cl))
} else {
Some((Event::branch(*n, (**el).clone(), new_r), cr))
}
}
(Some((new_l, cl)), None) => Some((Event::branch(*n, new_l, (**er).clone()), cl)),
(None, Some((new_r, cr))) => Some((Event::branch(*n, (**el).clone(), new_r), cr)),
(None, None) => None,
}
}
}
}
impl Stamp {
#[must_use]
pub fn fork(&self) -> (Self, Self) {
assert!(
!self.id.is_zero(),
"cannot fork an anonymous stamp (owns no interval)"
);
let (id_l, id_r) = split_id(&self.id);
(
Self::new(id_l, self.event.clone()).normalize(),
Self::new(id_r, self.event.clone()).normalize(),
)
}
#[must_use]
pub fn join(a: &Self, b: &Self) -> Self {
let id = sum_id(&a.id, &b.id);
let event = join_event(&a.event, &b.event);
Self::new(id, event).normalize()
}
pub fn event(&mut self) {
assert!(
!self.id.is_zero(),
"cannot record event on an anonymous stamp"
);
let (filled, changed) = fill(&self.id, &self.event);
if changed {
self.event = filled;
*self = self.clone().normalize();
return;
}
if let Some((grown, _cost)) = grow(&self.id, &self.event) {
self.event = grown;
*self = self.clone().normalize();
} else {
panic!("event: could not grow event tree (internal error)");
}
}
#[must_use]
pub const fn peek(&self) -> &Event {
&self.event
}
#[must_use]
pub fn leq(&self, other: &Self) -> bool {
leq_event(&self.event, &other.event)
}
#[must_use]
pub fn concurrent(&self, other: &Self) -> bool {
!self.leq(other) && !other.leq(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn fork_seed_produces_two_halves() {
let seed = Stamp::seed();
let (left, right) = seed.fork();
assert!(!left.id.is_zero());
assert!(!right.id.is_zero());
assert!(!left.id.is_one());
assert!(!right.id.is_one());
assert_eq!(left.event, Event::zero());
assert_eq!(right.event, Event::zero());
}
#[test]
fn fork_preserves_interval_coverage() {
let seed = Stamp::seed();
let (left, right) = seed.fork();
let reunited = sum_id(&left.id, &right.id);
assert_eq!(reunited, Id::one());
}
#[test]
fn fork_ids_are_disjoint() {
let seed = Stamp::seed();
let (left, right) = seed.fork();
assert_eq!(left.id, Id::branch(Id::one(), Id::zero()));
assert_eq!(right.id, Id::branch(Id::zero(), Id::one()));
}
#[test]
fn fork_of_half_further_splits() {
let seed = Stamp::seed();
let (left, _) = seed.fork();
let (ll, lr) = left.fork();
assert!(!ll.id.is_zero());
assert!(!lr.id.is_zero());
let reunited = sum_id(&ll.id, &lr.id);
assert_eq!(reunited, Id::branch(Id::one(), Id::zero()));
}
#[test]
fn fork_preserves_event_history() {
let mut s = Stamp::seed();
s.event();
s.event();
let (left, right) = s.fork();
assert_eq!(left.event, s.event);
assert_eq!(right.event, s.event);
}
#[test]
#[should_panic(expected = "cannot fork an anonymous stamp")]
fn fork_anonymous_panics() {
let anon = Stamp::anonymous();
let _ = anon.fork();
}
#[test]
fn join_recovers_seed_from_fork() {
let seed = Stamp::seed();
let (left, right) = seed.fork();
let joined = Stamp::join(&left, &right);
assert_eq!(joined.id, Id::one());
assert_eq!(joined.event, Event::zero());
}
#[test]
fn join_merges_divergent_events() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
b.event();
let joined = Stamp::join(&a, &b);
assert!(a.leq(&joined));
assert!(b.leq(&joined));
}
#[test]
fn join_with_anonymous() {
let seed = Stamp::seed();
let anon = Stamp::anonymous();
let joined = Stamp::join(&seed, &anon);
assert_eq!(joined.id, Id::one());
assert_eq!(joined.event, Event::zero());
}
#[test]
fn join_is_commutative() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
b.event();
let ab = Stamp::join(&a, &b);
let ba = Stamp::join(&b, &a);
assert_eq!(ab, ba);
}
#[test]
fn event_monotonically_increases() {
let mut s = Stamp::seed();
let before = s.event.max_value();
s.event();
let after = s.event.max_value();
assert!(after > before, "event must increase: {before} -> {after}");
}
#[test]
fn event_multiple_increments() {
let mut s = Stamp::seed();
for i in 1..=10 {
s.event();
assert!(
s.event.max_value() >= i,
"after {i} events, max_value should be >= {i}"
);
}
}
#[test]
fn event_on_forked_stamp() {
let seed = Stamp::seed();
let (mut a, _b) = seed.fork();
a.event();
assert!(a.event.max_value() >= 1);
}
#[test]
#[should_panic(expected = "cannot record event on an anonymous stamp")]
fn event_anonymous_panics() {
let mut anon = Stamp::anonymous();
anon.event();
}
#[test]
fn peek_returns_event_ref() {
let s = Stamp::seed();
assert_eq!(s.peek(), &Event::zero());
}
#[test]
fn peek_reflects_events() {
let mut s = Stamp::seed();
s.event();
let peeked = s.peek();
assert!(peeked.max_value() >= 1);
}
#[test]
fn leq_identical_stamps() {
let s = Stamp::seed();
assert!(s.leq(&s));
}
#[test]
fn leq_after_event() {
let mut s = Stamp::seed();
let before = s.clone();
s.event();
assert!(before.leq(&s), "before should be <= after event");
assert!(!s.leq(&before), "after event should NOT be <= before");
}
#[test]
fn leq_forked_then_diverged() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
assert!(!a.leq(&b));
assert!(!b.leq(&a));
}
#[test]
fn leq_joined_dominates_parts() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
let joined = Stamp::join(&a, &b);
assert!(a.leq(&joined));
assert!(b.leq(&joined));
}
#[test]
fn leq_zero_events() {
let a = Stamp::seed();
let b = Stamp::seed();
assert!(a.leq(&b));
assert!(b.leq(&a));
}
#[test]
fn leq_transitive() {
let mut s = Stamp::seed();
let s0 = s.clone();
s.event();
let s1 = s.clone();
s.event();
let s2 = s.clone();
assert!(s0.leq(&s1));
assert!(s1.leq(&s2));
assert!(s0.leq(&s2)); }
#[test]
fn concurrent_after_fork_and_events() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
assert!(a.concurrent(&b));
assert!(b.concurrent(&a));
}
#[test]
fn not_concurrent_when_dominated() {
let mut s = Stamp::seed();
let before = s.clone();
s.event();
assert!(!before.concurrent(&s));
assert!(!s.concurrent(&before));
}
#[test]
fn not_concurrent_when_equal() {
let s = Stamp::seed();
assert!(!s.concurrent(&s));
}
#[test]
fn concurrent_is_symmetric() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
b.event();
assert_eq!(a.concurrent(&b), b.concurrent(&a));
}
#[test]
fn two_agent_fork_work_retire() {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
a.event();
a.event();
a.event();
b.event();
b.event();
assert!(a.concurrent(&b));
let retired = Stamp::join(&a, &b);
assert!(a.leq(&retired));
assert!(b.leq(&retired));
assert_eq!(retired.id, Id::one());
}
#[test]
fn four_agent_scenario() {
let seed = Stamp::seed();
let (ab, cd) = seed.fork();
let (mut a, mut b) = ab.fork();
let (mut c, mut d) = cd.fork();
a.event();
b.event();
b.event();
c.event();
c.event();
c.event();
d.event();
assert!(a.concurrent(&b));
assert!(a.concurrent(&c));
assert!(a.concurrent(&d));
assert!(b.concurrent(&c));
assert!(b.concurrent(&d));
assert!(c.concurrent(&d));
let ab_merged = Stamp::join(&a, &b);
let cd_merged = Stamp::join(&c, &d);
assert!(a.leq(&ab_merged));
assert!(b.leq(&ab_merged));
assert!(c.leq(&cd_merged));
assert!(d.leq(&cd_merged));
let all = Stamp::join(&ab_merged, &cd_merged);
assert!(a.leq(&all));
assert!(b.leq(&all));
assert!(c.leq(&all));
assert!(d.leq(&all));
assert_eq!(all.id, Id::one());
}
#[test]
fn eight_agent_fork_work_retire_cycle() {
let seed = Stamp::seed();
let (half_l, half_r) = seed.fork();
let (q1, q2) = half_l.fork();
let (q3, q4) = half_r.fork();
let (mut a1, mut a2) = q1.fork();
let (mut a3, mut a4) = q2.fork();
let (mut a5, mut a6) = q3.fork();
let (mut a7, mut a8) = q4.fork();
let mut agents = [
&mut a1, &mut a2, &mut a3, &mut a4, &mut a5, &mut a6, &mut a7, &mut a8,
];
for (i, agent) in agents.iter_mut().enumerate() {
for _ in 0..=(i as u32) {
agent.event();
}
}
let snapshots: Vec<Stamp> = [&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8]
.iter()
.map(|s| (*s).clone())
.collect();
for i in 0..8 {
for j in (i + 1)..8 {
assert!(
snapshots[i].concurrent(&snapshots[j]),
"agents {i} and {j} should be concurrent"
);
}
}
let mut merged = snapshots[0].clone();
for s in &snapshots[1..] {
merged = Stamp::join(&merged, s);
}
for (i, s) in snapshots.iter().enumerate() {
assert!(s.leq(&merged), "agent {i} should be <= merged");
}
assert_eq!(merged.id, Id::one());
}
#[test]
fn sixteen_agent_cycle() {
let seed = Stamp::seed();
fn fork_n(stamp: Stamp, depth: u32) -> Vec<Stamp> {
if depth == 0 {
return vec![stamp];
}
let (l, r) = stamp.fork();
let mut result = fork_n(l, depth - 1);
result.extend(fork_n(r, depth - 1));
result
}
let mut agents = fork_n(seed, 4);
assert_eq!(agents.len(), 16);
for (i, agent) in agents.iter_mut().enumerate() {
for _ in 0..((i % 5) + 1) {
agent.event();
}
}
for i in 0..16 {
for j in (i + 1)..16 {
assert!(
agents[i].concurrent(&agents[j]),
"agents {i} and {j} should be concurrent"
);
}
}
let mut merged = agents[0].clone();
for a in &agents[1..] {
merged = Stamp::join(&merged, a);
}
for (i, a) in agents.iter().enumerate() {
assert!(a.leq(&merged), "agent {i} should be <= merged");
}
assert_eq!(merged.id, Id::one());
}
proptest! {
#[test]
fn prop_fork_join_roundtrip(events_before in 0u32..10) {
let mut s = Stamp::seed();
for _ in 0..events_before {
s.event();
}
let original_event = s.event.clone();
let (a, b) = s.fork();
let joined = Stamp::join(&a, &b);
prop_assert_eq!(&joined.id, &Id::one());
prop_assert!(leq_event(&original_event, &joined.event));
}
#[test]
fn prop_event_monotonic(n_events in 1u32..20) {
let mut s = Stamp::seed();
let mut prev_max = 0u32;
for _ in 0..n_events {
s.event();
let new_max = s.event.max_value();
prop_assert!(new_max > prev_max, "monotonicity violated: {} -> {}", prev_max, new_max);
prev_max = new_max;
}
}
#[test]
fn prop_leq_reflexive(n_events in 0u32..10) {
let mut s = Stamp::seed();
for _ in 0..n_events {
s.event();
}
prop_assert!(s.leq(&s));
}
#[test]
fn prop_leq_antisymmetric(n_a in 0u32..5, n_b in 0u32..5) {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
for _ in 0..n_a {
a.event();
}
for _ in 0..n_b {
b.event();
}
if a.leq(&b) && b.leq(&a) {
prop_assert_eq!(a.event, b.event);
}
}
#[test]
fn prop_fork_preserves_coverage(n_events in 0u32..5) {
let mut s = Stamp::seed();
for _ in 0..n_events {
s.event();
}
let (a, b) = s.fork();
let reunited = sum_id(&a.id, &b.id);
prop_assert_eq!(reunited, Id::one());
}
#[test]
fn prop_join_dominates_both(n_a in 1u32..5, n_b in 1u32..5) {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
for _ in 0..n_a {
a.event();
}
for _ in 0..n_b {
b.event();
}
let joined = Stamp::join(&a, &b);
prop_assert!(a.leq(&joined), "a should be <= join(a,b)");
prop_assert!(b.leq(&joined), "b should be <= join(a,b)");
}
#[test]
fn prop_concurrent_symmetric(n_a in 1u32..5, n_b in 1u32..5) {
let seed = Stamp::seed();
let (mut a, mut b) = seed.fork();
for _ in 0..n_a {
a.event();
}
for _ in 0..n_b {
b.event();
}
prop_assert_eq!(a.concurrent(&b), b.concurrent(&a));
}
#[test]
fn prop_leq_transitive(n1 in 0u32..4, n2 in 1u32..4, n3 in 1u32..4) {
let mut s = Stamp::seed();
for _ in 0..n1 {
s.event();
}
let s0 = s.clone();
for _ in 0..n2 {
s.event();
}
let s1 = s.clone();
for _ in 0..n3 {
s.event();
}
let s2 = s;
prop_assert!(s0.leq(&s1));
prop_assert!(s1.leq(&s2));
prop_assert!(s0.leq(&s2));
}
}
}