use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
use alloc::vec::Vec;
use core::cell::RefCell;
use core::cmp::Ordering;
pub fn graded_cmp<V: Ord>(a: &(V, u32), b: &(V, u32)) -> Ordering {
a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0))
}
pub fn graded_image<V, I>(source: &V, neighbors: impl Fn(&V) -> I) -> Vec<(V, u32)>
where
V: Ord + Clone,
I: IntoIterator<Item = V>,
{
let mut image: Vec<(V, u32)> = Vec::new();
let mut seen: BTreeSet<V> = BTreeSet::new();
seen.insert(source.clone());
let mut queue: VecDeque<(V, u32)> = VecDeque::new();
queue.push_back((source.clone(), 0));
while let Some((vertex, hops)) = queue.pop_front() {
for next in neighbors(&vertex) {
if seen.insert(next.clone()) {
queue.push_back((next.clone(), hops + 1));
image.push((next, hops + 1));
}
}
}
image.sort_unstable_by(graded_cmp);
image
}
pub fn graded_reaches<V, I>(source: &V, target: &V, neighbors: impl Fn(&V) -> I) -> bool
where
V: Ord + Clone,
I: IntoIterator<Item = V>,
{
let mut seen: BTreeSet<V> = BTreeSet::new();
seen.insert(source.clone());
let mut queue: VecDeque<V> = VecDeque::new();
queue.push_back(source.clone());
while let Some(vertex) = queue.pop_front() {
for next in neighbors(&vertex) {
if seen.insert(next.clone()) {
if &next == target {
return true;
}
queue.push_back(next);
}
}
}
false
}
pub fn graded_meet<V, I>(a: &V, b: &V, neighbors: impl Fn(&V) -> I) -> Option<V>
where
V: Ord + Clone,
I: IntoIterator<Item = V>,
{
let mut reflexive_a = alloc::vec![(a.clone(), 0u32)];
reflexive_a.extend(graded_image(a, &neighbors));
graded_meet_of(&reflexive_a, &graded_image(b, &neighbors))
}
pub fn graded_meet_of<V: Ord + Clone>(
reflexive_a: &[(V, u32)],
strict_b: &[(V, u32)],
) -> Option<V> {
let anc_a: BTreeSet<&V> = reflexive_a.iter().map(|(v, _)| v).collect();
strict_b
.iter()
.filter(|(v, _)| anc_a.contains(v))
.min_by(|x, y| graded_cmp(x, y))
.map(|(v, _)| v.clone())
}
pub fn graded_chain<V, I>(child: &V, ancestor: &V, neighbors: impl Fn(&V) -> I) -> Option<Vec<V>>
where
V: Ord + Clone,
I: IntoIterator<Item = V>,
{
let on_path = |x: &V| x == ancestor || graded_reaches(x, ancestor, &neighbors);
if !on_path(child) {
return None;
}
let mut chain: Vec<(V, u32)> = alloc::vec![(child.clone(), 0u32)];
chain.extend(
graded_image(child, &neighbors)
.into_iter()
.filter(|(x, _)| on_path(x)),
);
chain.sort_unstable_by(graded_cmp);
Some(chain.into_iter().map(|(v, _)| v).collect())
}
pub trait ReachSubstrate {
type Kind: Ord + Clone;
type Vertex: Ord + Clone;
fn neighbors<'s>(
&'s self,
kind: &Self::Kind,
vertex: &Self::Vertex,
) -> impl Iterator<Item = Self::Vertex> + use<'s, Self>;
}
pub trait ImageMemo<K: Ord + Clone, V: Ord + Clone> {
fn image(&self, kind: &K, source: &V, compute: impl FnOnce() -> Vec<(V, u32)>)
-> Vec<(V, u32)>;
fn reaches(
&self,
kind: &K,
source: &V,
target: &V,
compute: impl FnOnce() -> Vec<(V, u32)>,
probe: impl FnOnce() -> bool,
) -> bool;
}
#[derive(Debug, Clone)]
pub struct Cached<K: Ord + Clone, V: Ord + Clone> {
images: RefCell<MemoImages<K, V>>,
}
type MemoImages<K, V> = BTreeMap<K, BTreeMap<V, Vec<(V, u32)>>>;
impl<K: Ord + Clone, V: Ord + Clone> Default for Cached<K, V> {
fn default() -> Self {
Self {
images: RefCell::new(BTreeMap::new()),
}
}
}
impl<K: Ord + Clone, V: Ord + Clone> Cached<K, V> {
pub fn clear(&self) {
self.images.borrow_mut().clear();
}
fn store(&self, kind: &K, source: &V, image: Vec<(V, u32)>) {
let mut memo = self.images.borrow_mut();
if !memo.contains_key(kind) {
memo.insert(kind.clone(), BTreeMap::new());
}
memo.get_mut(kind)
.expect("per-kind memo map was just ensured present")
.insert(source.clone(), image);
}
}
impl<K: Ord + Clone, V: Ord + Clone> ImageMemo<K, V> for Cached<K, V> {
fn image(
&self,
kind: &K,
source: &V,
compute: impl FnOnce() -> Vec<(V, u32)>,
) -> Vec<(V, u32)> {
if let Some(hit) = self.images.borrow().get(kind).and_then(|m| m.get(source)) {
return hit.clone();
}
let image = compute();
self.store(kind, source, image.clone());
image
}
fn reaches(
&self,
kind: &K,
source: &V,
target: &V,
compute: impl FnOnce() -> Vec<(V, u32)>,
_probe: impl FnOnce() -> bool,
) -> bool {
if let Some(hit) = self.images.borrow().get(kind).and_then(|m| m.get(source)) {
return hit.iter().any(|(v, _)| v == target);
}
let image = compute();
let found = image.iter().any(|(v, _)| v == target);
self.store(kind, source, image);
found
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Uncached;
impl<K: Ord + Clone, V: Ord + Clone> ImageMemo<K, V> for Uncached {
fn image(
&self,
_kind: &K,
_source: &V,
compute: impl FnOnce() -> Vec<(V, u32)>,
) -> Vec<(V, u32)> {
compute()
}
fn reaches(
&self,
_kind: &K,
_source: &V,
_target: &V,
_compute: impl FnOnce() -> Vec<(V, u32)>,
probe: impl FnOnce() -> bool,
) -> bool {
probe()
}
}
pub struct ReachView<'a, S: ReachSubstrate, M: ImageMemo<S::Kind, S::Vertex>> {
substrate: &'a S,
memo: &'a M,
kind: &'a S::Kind,
}
impl<'a, S: ReachSubstrate, M: ImageMemo<S::Kind, S::Vertex>> ReachView<'a, S, M> {
pub fn new(substrate: &'a S, memo: &'a M, kind: &'a S::Kind) -> Self {
Self {
substrate,
memo,
kind,
}
}
fn compute_image(&self, source: &S::Vertex) -> Vec<(S::Vertex, u32)> {
graded_image(source, |v: &S::Vertex| {
self.substrate.neighbors(self.kind, v)
})
}
pub fn strict_image(&self, source: &S::Vertex) -> Vec<(S::Vertex, u32)> {
self.memo
.image(self.kind, source, || self.compute_image(source))
}
pub fn reflexive_image(&self, source: &S::Vertex) -> Vec<(S::Vertex, u32)> {
let mut out = alloc::vec![(source.clone(), 0u32)];
out.extend(self.strict_image(source));
out
}
pub fn reaches(&self, source: &S::Vertex, target: &S::Vertex) -> bool {
self.memo.reaches(
self.kind,
source,
target,
|| self.compute_image(source),
|| {
graded_reaches(source, target, |v: &S::Vertex| {
self.substrate.neighbors(self.kind, v)
})
},
)
}
pub fn meet(&self, a: &S::Vertex, b: &S::Vertex) -> Option<S::Vertex> {
graded_meet_of(&self.reflexive_image(a), &self.strict_image(b))
}
pub fn chain(&self, child: &S::Vertex, ancestor: &S::Vertex) -> Option<Vec<S::Vertex>> {
if child != ancestor && !self.reaches(child, ancestor) {
return None;
}
let mut chain: Vec<(S::Vertex, u32)> = self
.reflexive_image(child)
.into_iter()
.filter(|(x, _)| x == ancestor || self.reaches(x, ancestor))
.collect();
chain.sort_unstable_by(graded_cmp);
Some(chain.into_iter().map(|(v, _)| v).collect())
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use alloc::collections::BTreeMap;
use alloc::vec;
use proptest::prelude::*;
type Adj = BTreeMap<u8, Vec<u8>>;
fn adjacency(edges: &[(u8, u8)]) -> Adj {
let mut adj = Adj::new();
for &(s, t) in edges {
adj.entry(s).or_default().push(t);
}
adj
}
fn fwd(adj: &Adj) -> impl Fn(&u8) -> Vec<u8> + '_ {
move |v| adj.get(v).cloned().unwrap_or_default()
}
fn rev(adj: &Adj) -> impl Fn(&u8) -> Vec<u8> + '_ {
move |v| {
let mut n = adj.get(v).cloned().unwrap_or_default();
n.reverse();
n
}
}
fn relaxation_distances(adj: &Adj, source: u8) -> BTreeMap<u8, u32> {
let mut dist: BTreeMap<u8, u32> = BTreeMap::new();
dist.insert(source, 0);
loop {
let mut grew = false;
for (&s, targets) in adj.iter() {
let Some(&ds) = dist.get(&s) else { continue };
for &t in targets {
let candidate = ds + 1;
if dist.get(&t).is_none_or(|&dt| candidate < dt) {
dist.insert(t, candidate);
grew = true;
}
}
}
if !grew {
break;
}
}
dist.remove(&source);
dist
}
proptest! {
#[test]
fn graded_image_is_canonical_minimal_and_cycle_safe(
edges in prop::collection::vec((0u8..12, 0u8..12), 0..48),
source in 0u8..12,
) {
let adj = adjacency(&edges);
let image = graded_image(&source, fwd(&adj));
prop_assert!(image.iter().all(|(v, _)| *v != source));
let literally_sorted = image.windows(2).all(|w| {
let ((v1, d1), (v2, d2)) = (&w[0], &w[1]);
d1 < d2 || (d1 == d2 && v1 < v2)
});
prop_assert!(literally_sorted, "image not in literal (hops, V::Ord) order");
prop_assert!(
image.windows(2).all(|w| graded_cmp(&w[0], &w[1]) == Ordering::Less)
);
prop_assert_eq!(&image, &graded_image(&source, rev(&adj)));
let want = relaxation_distances(&adj, source);
let got: BTreeMap<u8, u32> = image.iter().copied().collect();
prop_assert_eq!(got.len(), image.len(), "image vertices are distinct");
prop_assert_eq!(&got, &want);
for v in 0u8..12 {
prop_assert_eq!(
graded_reaches(&source, &v, fwd(&adj)),
want.contains_key(&v),
"probe/image divergence at {}", v
);
}
}
}
crate::register_praxis_value!(
graded_image_is_canonical_minimal_and_cycle_safe,
Deterministic,
Verifiable
);
#[crate::praxis_value(Deterministic, Verifiable)]
#[test]
fn meet_and_chain_break_equal_distance_ties_by_vertex_order() {
let adj = adjacency(&[(0, 7), (0, 3), (7, 9), (3, 9)]);
assert_eq!(
graded_meet(&0, &0, fwd(&adj)),
Some(3),
"V::Ord wins the tie"
);
assert_eq!(
graded_meet(&0, &0, rev(&adj)),
Some(3),
"the winner is enumeration-order independent"
);
assert_eq!(graded_chain(&0, &9, fwd(&adj)), Some(vec![0, 3, 7, 9]));
assert_eq!(graded_chain(&0, &9, rev(&adj)), Some(vec![0, 3, 7, 9]));
let mut reflexive = vec![(0u8, 0u32)];
reflexive.extend(graded_image(&0, fwd(&adj)));
assert_eq!(
graded_meet_of(&reflexive, &graded_image(&0, fwd(&adj))),
Some(3)
);
}
#[crate::praxis_value(Honest, Verifiable)]
#[test]
fn reflexive_and_unreachable_cases_are_honest() {
let adj = adjacency(&[(0, 1), (1, 2), (2, 0)]);
assert!(!graded_reaches(&0, &0, fwd(&adj)));
assert!(graded_reaches(&0, &2, fwd(&adj)));
assert!(graded_image(&0, fwd(&adj)).iter().all(|(v, _)| *v != 0));
assert_eq!(graded_chain(&0, &0, fwd(&adj)), Some(vec![0, 1, 2]));
assert_eq!(graded_chain(&5, &0, fwd(&adj)), None);
assert_eq!(graded_meet(&5, &0, fwd(&adj)), None);
}
struct MapSubstrate {
adj: BTreeMap<u8, Adj>,
}
impl ReachSubstrate for MapSubstrate {
type Kind = u8;
type Vertex = u8;
fn neighbors<'s>(&'s self, kind: &u8, vertex: &u8) -> impl Iterator<Item = u8> + use<'s> {
self.adj
.get(kind)
.and_then(|per_kind| per_kind.get(vertex))
.map(|targets| targets.as_slice())
.unwrap_or(&[])
.iter()
.copied()
}
}
#[crate::praxis_value(Deterministic, Verifiable)]
#[test]
fn memo_policy_is_semantically_transparent() {
let mut adj = BTreeMap::new();
adj.insert(0u8, adjacency(&[(0, 7), (0, 3), (7, 9), (3, 9)]));
adj.insert(1u8, adjacency(&[(0, 5)]));
let substrate = MapSubstrate { adj };
let cached: Cached<u8, u8> = Cached::default();
for kind in [0u8, 1u8] {
let warm = ReachView::new(&substrate, &cached, &kind);
let cold = ReachView::new(&substrate, &Uncached, &kind);
let kernel_neighbors = |v: &u8| substrate.neighbors(&kind, v);
for v in 0u8..10 {
let want = graded_image(&v, kernel_neighbors);
assert_eq!(warm.strict_image(&v), want, "cached image, kind {kind}");
assert_eq!(warm.strict_image(&v), want, "memoized repeat, kind {kind}");
assert_eq!(cold.strict_image(&v), want, "uncached image, kind {kind}");
for w in 0u8..10 {
assert_eq!(
warm.reaches(&v, &w),
graded_reaches(&v, &w, kernel_neighbors),
"cached probe ({v} ⇝ {w}), kind {kind}"
);
assert_eq!(warm.reaches(&v, &w), cold.reaches(&v, &w));
assert_eq!(warm.meet(&v, &w), graded_meet(&v, &w, kernel_neighbors));
assert_eq!(warm.meet(&v, &w), cold.meet(&v, &w));
assert_eq!(
warm.chain(&v, &w),
graded_chain(&v, &w, kernel_neighbors),
"cached chain ({v} ⇝ {w}), kind {kind}"
);
assert_eq!(warm.chain(&v, &w), cold.chain(&v, &w));
}
}
}
}
#[crate::praxis_value(Honest, Verifiable)]
#[test]
fn cached_clear_drops_stale_images_after_adjacency_growth() {
let kind = 0u8;
let mut adj = BTreeMap::new();
adj.insert(kind, adjacency(&[(0, 1)]));
let mut substrate = MapSubstrate { adj };
let memo: Cached<u8, u8> = Cached::default();
assert_eq!(
ReachView::new(&substrate, &memo, &kind).strict_image(&0),
vec![(1, 1)]
);
substrate
.adj
.get_mut(&kind)
.expect("kind 0 present")
.entry(1)
.or_default()
.push(2);
assert_eq!(
ReachView::new(&substrate, &memo, &kind).strict_image(&0),
vec![(1, 1)],
"a derived cache does not watch its substrate — this is the staleness clear() exists for"
);
memo.clear();
assert_eq!(
ReachView::new(&substrate, &memo, &kind).strict_image(&0),
vec![(1, 1), (2, 2)],
"clear() must drop the stale image so the union'd graph is seen"
);
}
}