use std::collections::{BTreeMap, BTreeSet};
use proptest::prelude::*;
use super::harness::{
Program, Transaction, ZSet, add_changes, any_config, apply_proposals, check, configs, fixpoint,
map_steps, proposals, read_zset, set_after, workloads,
};
use crate::{
Circuit, FallbackZSet, OutputHandle, RootCircuit, ZSetHandle, ZWeight,
operator::DelayedFeedback,
typed_batch::{OrdZSet, Spine, SpineSnapshot},
utils::{Tup2, Tup3, test::CIRCUIT_CASES},
};
type Edge = Tup2<u64, u64>;
type Walks = Tup3<u64, u64, u64>;
type EdgeChanges = Vec<(Edge, ZWeight)>;
#[derive(Clone)]
struct WalkCounts;
impl Program for WalkCounts {
type Input = EdgeChanges;
type Handles = (
ZSetHandle<Edge>,
OutputHandle<SpineSnapshot<OrdZSet<Walks>>>,
);
type Output = ZSet<Walks>;
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (edges, edges_handle) = circuit.add_input_zset::<Edge>();
let walks = circuit
.fixedpoint(|child| {
let edges = edges.delta0(child);
let walks = <DelayedFeedback<_, OrdZSet<Walks>>>::new(child);
let squared = walks
.stream()
.map_index(|Tup3(from, via, a)| (*via, Tup2(*from, *a)))
.join_flatmap(
&walks
.stream()
.map_index(|Tup3(via, to, b)| (*via, Tup2(*to, *b))),
|_via, Tup2(from, a), Tup2(to, b)| {
(*a == *b || *a == b + 1).then(|| Tup3(*from, *to, a + b))
},
);
let next = edges
.map(|Tup2(from, to)| Tup3(*from, *to, 1))
.plus(&squared);
walks.connect(&next);
Ok(next
.integrate_trace()
.inner()
.export()
.typed::<Spine<FallbackZSet<Walks>>>())
})
.unwrap()
.consolidate()
.map(|walks| *walks);
(
edges_handle,
walks.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (edges, _): &Self::Handles, input: &EdgeChanges) {
for (row, weight) in input {
edges.push(*row, *weight);
}
}
fn read(&self, (_, walks): &Self::Handles) -> ZSet<Walks> {
read_zset(walks)
}
fn model(&self, inputs: &[EdgeChanges]) -> ZSet<Walks> {
let edges = set_after(inputs.iter().map(Vec::as_slice));
fixpoint(ZSet::new(), |walks: &ZSet<Walks>| {
let mut starting = BTreeMap::<u64, Vec<(u64, u64, ZWeight)>>::new();
for (Tup3(via, to, b), weight) in walks {
starting.entry(*via).or_default().push((*to, *b, *weight));
}
let mut next: Vec<(Walks, ZWeight)> = edges
.iter()
.map(|Tup2(from, to)| (Tup3(*from, *to, 1), 1))
.collect();
for (Tup3(from, via, a), left_weight) in walks {
for (to, b, right_weight) in starting.get(via).into_iter().flatten() {
if *a == *b || *a == b + 1 {
next.push((Tup3(*from, *to, a + b), left_weight * right_weight));
}
}
}
let mut next_walks = ZSet::new();
add_changes(&mut next_walks, &next);
next_walks
})
}
}
fn edge_changes(edges: &[(u64, u64)], weight: ZWeight) -> EdgeChanges {
edges
.iter()
.map(|&(from, to)| (Tup2(from, to), weight))
.collect()
}
fn walk_triggers() -> Vec<Vec<Transaction<EdgeChanges>>> {
let chain: Vec<(u64, u64)> = (0..8).map(|node| (node, node + 1)).collect();
let setup = [chain, vec![(0, 10), (10, 11), (11, 12), (12, 4)]].concat();
let append = |weight| edge_changes(&[(8, 9)], weight);
vec![
vec![vec![edge_changes(&setup, 1)], vec![append(1)]],
vec![
vec![edge_changes(&setup, 1)],
vec![append(1), edge_changes(&[(9, 13)], 1)],
],
vec![
vec![edge_changes(&setup, 1)],
vec![edge_changes(&[(14, 0)], 1)],
],
vec![
vec![edge_changes(&setup, 1)],
vec![append(1)],
vec![edge_changes(&[(5, 6)], -1)],
vec![edge_changes(&[(5, 6)], 1)],
vec![append(-1)],
],
]
}
fn walk_workloads() -> impl Strategy<Value = Vec<Transaction<EdgeChanges>>> {
let edge = prop::sample::select(
(0..8u64)
.flat_map(|to| (0..to).map(move |from| Tup2(from, to)))
.collect::<Vec<_>>(),
);
workloads(proposals(edge, 4), 5, 3).prop_map(|raw| {
let mut edges = BTreeSet::new();
map_steps(raw, |proposals| apply_proposals(&mut edges, &proposals))
})
}
#[test]
fn walk_triggers_hold() {
for workload in walk_triggers() {
for config in configs() {
check(&WalkCounts, &workload, config);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(CIRCUIT_CASES))]
#[test]
fn walk_counts_random(workload in walk_workloads(), config in any_config()) {
check(&WalkCounts, &workload, config);
}
}