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::{
NestedCircuit, OutputHandle, RootCircuit, Stream, ZSetHandle, ZWeight,
typed_batch::{OrdZSet, SpineSnapshot},
utils::{Tup2, Tup4, test::CIRCUIT_CASES},
};
type Leg = Tup2<u64, u64>;
type Trip = Tup4<u64, u64, bool, bool>;
#[derive(Clone, Debug, Default)]
struct LegChanges {
road: Vec<(Leg, ZWeight)>,
rail: Vec<(Leg, ZWeight)>,
}
#[derive(Clone)]
struct MultimodalTrips;
type TripsState = (BTreeSet<Leg>, BTreeSet<Leg>, BTreeSet<Trip>);
impl Program for MultimodalTrips {
type Input = LegChanges;
type Handles = (
ZSetHandle<Leg>,
ZSetHandle<Leg>,
OutputHandle<SpineSnapshot<OrdZSet<Trip>>>,
);
type Output = ZSet<Trip>;
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (road, road_handle) = circuit.add_input_zset::<Leg>();
let (rail, rail_handle) = circuit.add_input_zset::<Leg>();
let (_, _, trips) = circuit
.recursive(
|child,
(by_road, by_rail, trips): (
Stream<_, OrdZSet<Leg>>,
Stream<_, OrdZSet<Leg>>,
Stream<_, OrdZSet<Trip>>,
)| {
let road = road.delta0(child);
let rail = rail.delta0(child);
let trip_ends = trips.map_index(|Tup4(from, to, _, _)| (*to, *from));
let extend = |legs: &Stream<NestedCircuit, OrdZSet<Leg>>| {
legs.plus(&trip_ends.join(
&legs.map_index(|Tup2(from, to)| (*from, *to)),
|_via, from, to| Tup2(*from, *to),
))
};
let joined = by_road.map_index(|leg| (*leg, ())).outer_join(
&by_rail.map_index(|leg| (*leg, ())),
|Tup2(from, to), _, _| Tup4(*from, *to, true, true),
|Tup2(from, to), _| Tup4(*from, *to, true, false),
|Tup2(from, to), _| Tup4(*from, *to, false, true),
);
Ok((extend(&road), extend(&rail), joined))
},
)
.unwrap();
(
road_handle,
rail_handle,
trips.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (road, rail, _): &Self::Handles, input: &LegChanges) {
for (row, weight) in &input.road {
road.push(*row, *weight);
}
for (row, weight) in &input.rail {
rail.push(*row, *weight);
}
}
fn read(&self, (_, _, trips): &Self::Handles) -> ZSet<Trip> {
read_zset(trips)
}
fn model(&self, inputs: &[LegChanges]) -> ZSet<Trip> {
let road = set_after(inputs.iter().map(|input| input.road.as_slice()));
let rail = set_after(inputs.iter().map(|input| input.rail.as_slice()));
let (_, _, trips) = fixpoint(
TripsState::default(),
|(by_road, by_rail, trips): &TripsState| {
let extend = |legs: &BTreeSet<Leg>| {
let mut extended = legs.clone();
for Tup4(from, via, _, _) in trips {
for Tup2(_, to) in legs.range(Tup2(*via, 0)..=Tup2(*via, u64::MAX)) {
extended.insert(Tup2(*from, *to));
}
}
extended
};
let joined = by_road
.union(by_rail)
.map(|leg @ Tup2(from, to)| {
Tup4(*from, *to, by_road.contains(leg), by_rail.contains(leg))
})
.collect();
(extend(&road), extend(&rail), joined)
},
);
set_zset(&trips)
}
}
fn road(legs: &[(u64, u64)], weight: ZWeight) -> LegChanges {
LegChanges {
road: legs
.iter()
.map(|&(from, to)| (Tup2(from, to), weight))
.collect(),
..LegChanges::default()
}
}
fn rail(legs: &[(u64, u64)], weight: ZWeight) -> LegChanges {
LegChanges {
rail: legs
.iter()
.map(|&(from, to)| (Tup2(from, to), weight))
.collect(),
..LegChanges::default()
}
}
fn trips_triggers() -> Vec<Vec<Transaction<LegChanges>>> {
let rail_line: Vec<(u64, u64)> = (0..8).map(|node| (node, node + 1)).collect();
let road_line = [
(0, 11),
(11, 12),
(12, 13),
(13, 14),
(14, 15),
(15, 16),
(16, 17),
(17, 8),
];
let setup = LegChanges {
road: road(&road_line, 1).road,
rail: rail(&rail_line, 1).rail,
};
vec![
vec![
vec![setup.clone()],
vec![road(&[(0, 8)], 1)],
vec![rail(&[(7, 8)], -1)],
],
vec![
vec![setup.clone()],
vec![rail(&[(0, 8)], 1)],
vec![road(&[(17, 8)], -1)],
],
vec![
vec![setup],
vec![road(&[(0, 8)], 1), rail(&[(0, 8)], 1)],
vec![rail(&[(0, 8), (7, 8)], -1)],
],
]
}
fn trips_workloads() -> impl Strategy<Value = Vec<Transaction<LegChanges>>> {
let leg = (0..6u64, 0..6u64).prop_map(|(from, to)| Tup2(from, to));
workloads((proposals(leg.clone(), 3), proposals(leg, 3)), 5, 3).prop_map(|raw| {
let (mut road, mut rail) = (BTreeSet::new(), BTreeSet::new());
map_steps(raw, |(road_proposals, rail_proposals)| LegChanges {
road: apply_proposals(&mut road, &road_proposals),
rail: apply_proposals(&mut rail, &rail_proposals),
})
})
}
#[test]
fn trips_triggers_hold() {
for workload in trips_triggers() {
for config in configs() {
check(&MultimodalTrips, &workload, config);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(CIRCUIT_CASES))]
#[test]
fn trips_random(workload in trips_workloads(), config in any_config()) {
check(&MultimodalTrips, &workload, config);
}
}