use std::collections::{BTreeMap, 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,
typed_batch::{OrdZSet, SpineSnapshot},
utils::{Tup2, Tup3, Tup4, test::CIRCUIT_CASES},
};
type Token = Tup3<u64, u64, u64>;
type Unary = Tup2<u64, u64>;
type Binary = Tup3<u64, u64, u64>;
type Parse = Tup4<u64, u64, u64, u64>;
#[derive(Clone, Debug, Default)]
struct ParseInput {
tokens: Vec<(Token, ZWeight)>,
unary: Vec<(Unary, ZWeight)>,
binary: Vec<(Binary, ZWeight)>,
}
#[derive(Clone)]
struct Cyk;
impl Program for Cyk {
type Input = ParseInput;
type Handles = (
ZSetHandle<Token>,
ZSetHandle<Unary>,
ZSetHandle<Binary>,
OutputHandle<SpineSnapshot<OrdZSet<Parse>>>,
);
type Output = ZSet<Parse>;
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (tokens, tokens_handle) = circuit.add_input_zset::<Token>();
let (unary, unary_handle) = circuit.add_input_zset::<Unary>();
let (binary, binary_handle) = circuit.add_input_zset::<Binary>();
let parses = circuit
.recursive(|child, parses: Stream<_, OrdZSet<Parse>>| {
let leaves = tokens
.delta0(child)
.map_index(|Tup3(sentence, position, token)| {
(*token, Tup2(*sentence, *position))
})
.join(
&unary
.delta0(child)
.map_index(|Tup2(nonterminal, token)| (*token, *nonterminal)),
|_token, Tup2(sentence, position), nonterminal| {
Tup4(*sentence, *nonterminal, *position, position + 1)
},
);
let ending = parses.map_index(|Tup4(sentence, b, start, split)| {
(Tup2(*sentence, *split), Tup2(*b, *start))
});
let starting = parses.map_index(|Tup4(sentence, c, split, end)| {
(Tup2(*sentence, *split), Tup2(*c, *end))
});
let adjacent = ending.join_index(
&starting,
|Tup2(sentence, _split), Tup2(b, start), Tup2(c, end)| {
Some((Tup2(*b, *c), Tup3(*sentence, *start, *end)))
},
);
let combined = adjacent.join(
&binary
.delta0(child)
.map_index(|Tup3(a, b, c)| (Tup2(*b, *c), *a)),
|_bc, Tup3(sentence, start, end), a| Tup4(*sentence, *a, *start, *end),
);
Ok(leaves.plus(&combined))
})
.unwrap();
(
tokens_handle,
unary_handle,
binary_handle,
parses.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (tokens, unary, binary, _): &Self::Handles, input: &ParseInput) {
for (row, weight) in &input.tokens {
tokens.push(*row, *weight);
}
for (row, weight) in &input.unary {
unary.push(*row, *weight);
}
for (row, weight) in &input.binary {
binary.push(*row, *weight);
}
}
fn read(&self, (_, _, _, parses): &Self::Handles) -> ZSet<Parse> {
read_zset(parses)
}
fn model(&self, inputs: &[ParseInput]) -> ZSet<Parse> {
let tokens = set_after(inputs.iter().map(|input| input.tokens.as_slice()));
let unary = set_after(inputs.iter().map(|input| input.unary.as_slice()));
let binary = set_after(inputs.iter().map(|input| input.binary.as_slice()));
let mut leaves = BTreeSet::new();
for Tup3(sentence, position, token) in &tokens {
for Tup2(nonterminal, rule_token) in &unary {
if rule_token == token {
leaves.insert(Tup4(*sentence, *nonterminal, *position, position + 1));
}
}
}
let parses = fixpoint(BTreeSet::new(), |parses: &BTreeSet<Parse>| {
let mut starting: BTreeMap<(u64, u64), Vec<(u64, u64)>> = BTreeMap::new();
for Tup4(sentence, c, split, end) in parses {
starting
.entry((*sentence, *split))
.or_default()
.push((*c, *end));
}
let mut next = leaves.clone();
for Tup4(sentence, b, start, split) in parses {
for (c, end) in starting.get(&(*sentence, *split)).into_iter().flatten() {
for Tup3(a, rule_b, rule_c) in &binary {
if rule_b == b && rule_c == c {
next.insert(Tup4(*sentence, *a, *start, *end));
}
}
}
}
next
});
set_zset(&parses)
}
}
fn tokens(positions: impl IntoIterator<Item = u64>, weight: ZWeight) -> ParseInput {
ParseInput {
tokens: positions
.into_iter()
.map(|position| (Tup3(0, position, 0), weight))
.collect(),
..ParseInput::default()
}
}
fn cyk_triggers() -> Vec<Vec<Transaction<ParseInput>>> {
let grammar = ParseInput {
unary: vec![(Tup2(0, 0), 1)],
binary: vec![(Tup3(0, 0, 0), 1)],
..ParseInput::default()
};
let setup = vec![grammar, tokens(0..8, 1)];
vec![
vec![setup.clone(), vec![tokens([8], 1)], vec![tokens([0], -1)]],
vec![
setup.clone(),
vec![tokens([8], 1), tokens([9], 1)],
vec![tokens([4], -1)],
],
vec![
setup.clone(),
vec![tokens([8], 1)],
vec![tokens([4], -1)],
vec![tokens([4], 1)],
],
vec![
setup,
vec![ParseInput {
unary: vec![(Tup2(1, 0), 1)],
binary: vec![(Tup3(1, 1, 0), 1)],
..ParseInput::default()
}],
vec![tokens([4], -1)],
],
]
}
fn cyk_workloads() -> impl Strategy<Value = Vec<Transaction<ParseInput>>> {
let token = (0..2u64, 0..6u64, 0..2u64)
.prop_map(|(sentence, position, token)| Tup3(sentence, position, token));
let unary = (0..3u64, 0..2u64).prop_map(|(nonterminal, token)| Tup2(nonterminal, token));
let binary = (0..3u64, 0..3u64, 0..3u64).prop_map(|(a, b, c)| Tup3(a, b, c));
let step = (
proposals(token, 4),
proposals(unary, 2),
proposals(binary, 2),
);
workloads(step, 5, 3).prop_map(|raw| {
let mut live = (BTreeSet::new(), BTreeSet::new(), BTreeSet::new());
map_steps(raw, |(tokens, unary, binary)| ParseInput {
tokens: apply_proposals(&mut live.0, &tokens),
unary: apply_proposals(&mut live.1, &unary),
binary: apply_proposals(&mut live.2, &binary),
})
})
}
#[test]
fn cyk_triggers_hold() {
for workload in cyk_triggers() {
for config in configs() {
check(&Cyk, &workload, config);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(CIRCUIT_CASES))]
#[test]
fn cyk_random(workload in cyk_workloads(), config in any_config()) {
check(&Cyk, &workload, config);
}
}