use std::collections::BTreeSet;
use proptest::prelude::*;
use super::harness::{
Program, Transaction, ZSet, any_config, apply_proposals, check, configs, fixpoint, map_steps,
proposals, read_zset, set_after, set_zset, workloads,
};
use crate::{
OutputHandle, RootCircuit, Stream, ZSetHandle, ZWeight, define_inner_star_join,
typed_batch::{OrdZSet, SpineSnapshot},
utils::{Tup2, test::CIRCUIT_CASES},
};
define_inner_star_join!(3);
type Edge = Tup2<u64, u64>;
type EdgeChanges = Vec<(Edge, ZWeight)>;
type NodeChanges = Vec<(u64, ZWeight)>;
type PathsHandle = OutputHandle<SpineSnapshot<OrdZSet<Edge>>>;
fn concat(
left: &BTreeSet<Edge>,
right: &BTreeSet<Edge>,
joinable: impl Fn(u64) -> bool,
) -> BTreeSet<Edge> {
let mut paths = BTreeSet::new();
for Tup2(from, via) in left {
if joinable(*via) {
for Tup2(_, to) in right.range(Tup2(*via, 0)..=Tup2(*via, u64::MAX)) {
paths.insert(Tup2(*from, *to));
}
}
}
paths
}
#[derive(Clone)]
struct PathDoubling;
impl Program for PathDoubling {
type Input = EdgeChanges;
type Handles = (ZSetHandle<Edge>, PathsHandle);
type Output = ZSet<Edge>;
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (edges, edges_handle) = circuit.add_input_zset::<Edge>();
let paths = circuit
.recursive(|child, paths: Stream<_, OrdZSet<Edge>>| {
let edges = edges.delta0(child);
let by_end = paths.map_index(|Tup2(from, via)| (*via, *from));
let by_start = paths.map_index(|Tup2(via, to)| (*via, *to));
Ok(edges.plus(&by_end.join(&by_start, |_via, from, to| Tup2(*from, *to))))
})
.unwrap();
(
edges_handle,
paths.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (edges, _): &Self::Handles, input: &EdgeChanges) {
for (edge, weight) in input {
edges.push(*edge, *weight);
}
}
fn read(&self, (_, paths): &Self::Handles) -> ZSet<Edge> {
read_zset(paths)
}
fn model(&self, inputs: &[EdgeChanges]) -> ZSet<Edge> {
let edges = set_after(inputs.iter().map(Vec::as_slice));
let paths = fixpoint(BTreeSet::new(), |paths| {
edges
.union(&concat(paths, paths, |_| true))
.cloned()
.collect()
});
set_zset(&paths)
}
}
#[derive(Clone)]
struct MutualPaths;
impl Program for MutualPaths {
type Input = (EdgeChanges, EdgeChanges);
type Handles = (ZSetHandle<Edge>, ZSetHandle<Edge>, PathsHandle, PathsHandle);
type Output = (ZSet<Edge>, ZSet<Edge>);
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (e, e_handle) = circuit.add_input_zset::<Edge>();
let (f, f_handle) = circuit.add_input_zset::<Edge>();
let (r, s) = circuit
.recursive(
|child, (r, s): (Stream<_, OrdZSet<Edge>>, Stream<_, OrdZSet<Edge>>)| {
let r_by_end = r.map_index(|Tup2(from, via)| (*via, *from));
let r_by_start = r.map_index(|Tup2(via, to)| (*via, *to));
let s_by_end = s.map_index(|Tup2(from, via)| (*via, *from));
let s_by_start = s.map_index(|Tup2(via, to)| (*via, *to));
let r_next = e
.delta0(child)
.plus(&r_by_end.join(&s_by_start, |_via, from, to| Tup2(*from, *to)));
let s_next = f
.delta0(child)
.plus(&s_by_end.join(&r_by_start, |_via, from, to| Tup2(*from, *to)));
Ok((r_next, s_next))
},
)
.unwrap();
(
e_handle,
f_handle,
r.accumulate_integrate().accumulate_output(),
s.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (e, f, _, _): &Self::Handles, (e_changes, f_changes): &Self::Input) {
for (edge, weight) in e_changes {
e.push(*edge, *weight);
}
for (edge, weight) in f_changes {
f.push(*edge, *weight);
}
}
fn read(&self, (_, _, r, s): &Self::Handles) -> Self::Output {
(read_zset(r), read_zset(s))
}
fn model(&self, inputs: &[Self::Input]) -> Self::Output {
let e = set_after(inputs.iter().map(|(e, _)| e.as_slice()));
let f = set_after(inputs.iter().map(|(_, f)| f.as_slice()));
let (r, s) = fixpoint(
(BTreeSet::new(), BTreeSet::new()),
|(r, s): &(BTreeSet<Edge>, BTreeSet<Edge>)| {
(
e.union(&concat(r, s, |_| true)).cloned().collect(),
f.union(&concat(s, r, |_| true)).cloned().collect(),
)
},
);
(set_zset(&r), set_zset(&s))
}
}
#[derive(Clone)]
struct StarDoubling;
impl Program for StarDoubling {
type Input = (EdgeChanges, NodeChanges);
type Handles = (ZSetHandle<Edge>, ZSetHandle<u64>, PathsHandle);
type Output = ZSet<Edge>;
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (edges, edges_handle) = circuit.add_input_zset::<Edge>();
let (joinable, joinable_handle) = circuit.add_input_zset::<u64>();
let paths = circuit
.recursive(|child, paths: Stream<_, OrdZSet<Edge>>| {
let by_end = paths.map_index(|Tup2(from, via)| (*via, *from));
let by_start = paths.map_index(|Tup2(via, to)| (*via, *to));
let joinable = joinable.delta0(child).map_index(|via| (*via, ()));
Ok(edges.delta0(child).plus(&inner_star_join3_nested(
&by_end,
&by_start,
&joinable,
|_via, from, to, _| Tup2(*from, *to),
)))
})
.unwrap();
(
edges_handle,
joinable_handle,
paths.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (edges, joinable, _): &Self::Handles, (edge_changes, nodes): &Self::Input) {
for (edge, weight) in edge_changes {
edges.push(*edge, *weight);
}
for (node, weight) in nodes {
joinable.push(*node, *weight);
}
}
fn read(&self, (_, _, paths): &Self::Handles) -> ZSet<Edge> {
read_zset(paths)
}
fn model(&self, inputs: &[Self::Input]) -> ZSet<Edge> {
let edges = set_after(inputs.iter().map(|(edges, _)| edges.as_slice()));
let joinable = set_after(inputs.iter().map(|(_, nodes)| nodes.as_slice()));
let paths = fixpoint(BTreeSet::new(), |paths| {
edges
.union(&concat(paths, paths, |via| joinable.contains(&via)))
.cloned()
.collect()
});
set_zset(&paths)
}
}
fn edge_changes(edges: &[(u64, u64)], weight: ZWeight) -> EdgeChanges {
edges
.iter()
.map(|&(from, to)| (Tup2(from, to), weight))
.collect()
}
fn chain() -> EdgeChanges {
let edges: Vec<(u64, u64)> = (0..8).map(|node| (node, node + 1)).collect();
edge_changes(&edges, 1)
}
fn doubling_triggers() -> Vec<Vec<Transaction<EdgeChanges>>> {
let prepend = vec![edge_changes(&[(9, 0)], 1)];
let cut = |weight| vec![edge_changes(&[(4, 5)], weight)];
vec![
vec![vec![chain()], prepend.clone(), cut(-1)],
vec![
vec![chain()],
vec![edge_changes(&[(9, 0)], 1), edge_changes(&[(10, 9)], 1)],
cut(-1),
],
vec![
vec![chain()],
prepend.clone(),
vec![edge_changes(&[(9, 0)], -1)],
cut(-1),
prepend,
cut(1),
],
vec![vec![chain()], vec![edge_changes(&[(8, 0)], 1)]],
]
}
fn mutual_triggers() -> Vec<Vec<Transaction<(EdgeChanges, EdgeChanges)>>> {
let chains = vec![(chain(), chain())];
let in_e = |edges: &[(u64, u64)], weight| (edge_changes(edges, weight), vec![]);
let in_f = |edges: &[(u64, u64)], weight| (vec![], edge_changes(edges, weight));
let cut = vec![(edge_changes(&[(4, 5)], -1), edge_changes(&[(4, 5)], -1))];
vec![
vec![chains.clone(), vec![in_e(&[(9, 0)], 1)], cut.clone()],
vec![chains.clone(), vec![in_f(&[(9, 0)], 1)], cut.clone()],
vec![
chains.clone(),
vec![in_e(&[(9, 0)], 1), in_f(&[(10, 9)], 1)],
cut,
],
vec![
chains,
vec![in_e(&[(9, 0)], 1)],
vec![in_e(&[(4, 5)], -1)],
vec![in_f(&[(4, 5)], -1)],
vec![in_e(&[(4, 5)], 1), in_f(&[(4, 5)], 1)],
],
]
}
fn star_triggers() -> Vec<Vec<Transaction<(EdgeChanges, NodeChanges)>>> {
let setup = vec![(chain(), (0..11).map(|node| (node, 1)).collect())];
let prepend = vec![(edge_changes(&[(9, 0)], 1), vec![])];
vec![
vec![
setup.clone(),
vec![
(edge_changes(&[(9, 0)], 1), vec![]),
(edge_changes(&[(10, 9)], 1), vec![]),
],
vec![(vec![], vec![(4, -1)])],
],
vec![
setup,
prepend,
vec![(vec![], vec![(4, -1)])],
vec![(vec![], vec![(4, 1)])],
],
]
}
fn edge(nodes: u64) -> impl Strategy<Value = Edge> {
(0..nodes, 0..nodes).prop_map(|(from, to)| Tup2(from, to))
}
fn doubling_workloads() -> impl Strategy<Value = Vec<Transaction<EdgeChanges>>> {
workloads(proposals(edge(7), 4), 5, 3).prop_map(|raw| {
let mut edges = BTreeSet::new();
map_steps(raw, |proposals| apply_proposals(&mut edges, &proposals))
})
}
fn mutual_workloads() -> impl Strategy<Value = Vec<Transaction<(EdgeChanges, EdgeChanges)>>> {
workloads((proposals(edge(6), 4), proposals(edge(6), 4)), 5, 3).prop_map(|raw| {
let (mut e, mut f) = (BTreeSet::new(), BTreeSet::new());
map_steps(raw, |(e_proposals, f_proposals)| {
(
apply_proposals(&mut e, &e_proposals),
apply_proposals(&mut f, &f_proposals),
)
})
})
}
fn star_workloads() -> impl Strategy<Value = Vec<Transaction<(EdgeChanges, NodeChanges)>>> {
workloads((proposals(edge(7), 4), proposals(0..7u64, 4)), 5, 3).prop_map(|raw| {
let (mut edges, mut joinable) = (BTreeSet::new(), BTreeSet::new());
map_steps(raw, |(edge_proposals, node_proposals)| {
(
apply_proposals(&mut edges, &edge_proposals),
apply_proposals(&mut joinable, &node_proposals),
)
})
})
}
#[test]
fn path_doubling_triggers() {
for workload in doubling_triggers() {
for config in configs() {
check(&PathDoubling, &workload, config);
}
}
}
#[test]
fn mutual_paths_triggers() {
for workload in mutual_triggers() {
for config in configs() {
check(&MutualPaths, &workload, config);
}
}
}
#[test]
fn star_doubling_triggers() {
for workload in star_triggers() {
for config in configs() {
check(&StarDoubling, &workload, config);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(CIRCUIT_CASES))]
#[test]
fn path_doubling_random(workload in doubling_workloads(), config in any_config()) {
check(&PathDoubling, &workload, config);
}
#[test]
fn mutual_paths_random(workload in mutual_workloads(), config in any_config()) {
check(&MutualPaths, &workload, config);
}
#[test]
fn star_doubling_random(workload in star_workloads(), config in any_config()) {
check(&StarDoubling, &workload, config);
}
}