use std::collections::VecDeque;
use std::hash::Hash;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use super::lazy::{LazyState, LazyWfstWrapper, StateSource};
use super::{StateId, WeightedTransition, Wfst, NO_STATE};
use crate::semiring::Semiring;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct StringDelay<L> {
pub input: SmallVec<[L; 4]>,
pub output: SmallVec<[L; 4]>,
}
impl<L: Clone> StringDelay<L> {
#[inline]
pub fn empty() -> Self {
Self {
input: SmallVec::new(),
output: SmallVec::new(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.input.is_empty() && self.output.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.input.len() + self.output.len()
}
#[inline]
pub fn car_input(&self) -> Option<L> {
self.input.first().cloned()
}
#[inline]
pub fn car_output(&self) -> Option<L> {
self.output.first().cloned()
}
pub fn cdr(&self) -> Self {
let input = if self.input.len() > 1 {
self.input[1..].iter().cloned().collect()
} else {
SmallVec::new()
};
let output = if self.output.len() > 1 {
self.output[1..].iter().cloned().collect()
} else {
SmallVec::new()
};
Self { input, output }
}
}
impl<L: Clone + Eq> StringDelay<L> {
pub fn sync(mut input: SmallVec<[L; 4]>, mut output: SmallVec<[L; 4]>) -> Self {
while !input.is_empty() && !output.is_empty() && input[0] == output[0] {
input.remove(0);
output.remove(0);
}
Self { input, output }
}
pub fn extend_input(&self, symbols: impl IntoIterator<Item = L>) -> Self {
let mut input: SmallVec<[L; 4]> = self.input.clone();
input.extend(symbols);
Self::sync(input, self.output.clone())
}
pub fn extend_output(&self, symbols: impl IntoIterator<Item = L>) -> Self {
let mut output: SmallVec<[L; 4]> = self.output.clone();
output.extend(symbols);
Self::sync(self.input.clone(), output)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SyncState<L> {
pub original: StateId,
pub delay: StringDelay<L>,
pub draining: bool,
}
impl<L: Clone> SyncState<L> {
pub fn initial(original: StateId) -> Self {
Self {
original,
delay: StringDelay::empty(),
draining: false,
}
}
pub fn draining(delay: StringDelay<L>) -> Self {
Self {
original: NO_STATE, delay,
draining: true,
}
}
}
#[derive(Clone)]
pub struct SyncSource<L, W, T>
where
W: Semiring,
T: Wfst<L, W>,
{
fst: T,
state_map: FxHashMap<SyncState<L>, StateId>,
state_index: Vec<SyncState<L>>,
max_delay: usize,
_phantom: std::marker::PhantomData<W>,
}
impl<L, W, T> SyncSource<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
pub fn new(fst: T, max_delay: usize) -> Self {
let mut state_map = FxHashMap::default();
let mut state_index = Vec::new();
let start = fst.start();
if start != NO_STATE {
let initial = SyncState::initial(start);
state_map.insert(initial.clone(), 0);
state_index.push(initial);
}
Self {
fst,
state_map,
state_index,
max_delay,
_phantom: std::marker::PhantomData,
}
}
fn get_sync_state(&self, state: StateId) -> Option<&SyncState<L>> {
self.state_index.get(state as usize)
}
}
impl<L, W, T> StateSource<L, W> for SyncSource<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
fn compute_state(&self, state: StateId) -> LazyState<L, W> {
let sync_state = match self.get_sync_state(state) {
Some(s) => s,
None => return LazyState::non_final(SmallVec::new()),
};
let mut transitions = SmallVec::new();
if sync_state.draining {
if sync_state.delay.is_empty() {
return LazyState::final_state(W::one(), SmallVec::new());
}
let input_label = sync_state.delay.car_input();
let output_label = sync_state.delay.car_output();
let next_delay = sync_state.delay.cdr();
let _next_state = SyncState::draining(next_delay.clone());
let next_id = if next_delay.is_empty() {
NO_STATE } else {
state + 1 };
if next_id != NO_STATE {
transitions.push(WeightedTransition::new(
state,
input_label,
output_label,
next_id,
W::one(),
));
LazyState::non_final(transitions)
} else {
transitions.push(WeightedTransition::new(
state,
input_label,
output_label,
state, W::one(),
));
LazyState::final_state(W::one(), transitions)
}
} else {
let original = sync_state.original;
if self.fst.is_final(original) && sync_state.delay.is_empty() {
let final_weight = self.fst.final_weight(original);
for trans in self.fst.transitions(original) {
if let Some(next_trans) = self.compute_transition(state, sync_state, trans) {
transitions.push(next_trans);
}
}
return LazyState::final_state(final_weight, transitions);
}
if self.fst.is_final(original) && !sync_state.delay.is_empty() {
let final_weight = self.fst.final_weight(original);
let input_label = sync_state.delay.car_input();
let output_label = sync_state.delay.car_output();
transitions.push(WeightedTransition::new(
state,
input_label,
output_label,
state, final_weight,
));
}
for trans in self.fst.transitions(original) {
if let Some(next_trans) = self.compute_transition(state, sync_state, trans) {
transitions.push(next_trans);
}
}
LazyState::non_final(transitions)
}
}
fn start(&self) -> StateId {
if self.fst.start() == NO_STATE {
NO_STATE
} else {
0
}
}
fn num_states_hint(&self) -> Option<usize> {
Some(self.state_index.len())
}
}
impl<L, W, T> SyncSource<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
fn compute_transition(
&self,
from_state: StateId,
sync_state: &SyncState<L>,
trans: &WeightedTransition<L, W>,
) -> Option<WeightedTransition<L, W>> {
let mut new_input: SmallVec<[L; 4]> = sync_state.delay.input.clone();
let mut new_output: SmallVec<[L; 4]> = sync_state.delay.output.clone();
if let Some(ref i) = trans.input {
new_input.push(i.clone());
}
if let Some(ref o) = trans.output {
new_output.push(o.clone());
}
let new_delay = StringDelay::sync(new_input, new_output);
if new_delay.len() > self.max_delay {
return None;
}
let out_input = if !sync_state.delay.input.is_empty() || trans.input.is_some() {
new_delay
.car_input()
.or_else(|| sync_state.delay.car_input())
} else {
None
};
let out_output = if !sync_state.delay.output.is_empty() || trans.output.is_some() {
new_delay
.car_output()
.or_else(|| sync_state.delay.car_output())
} else {
None
};
let next_sync = SyncState {
original: trans.to,
delay: new_delay.cdr(),
draining: false,
};
let next_id = self.state_map.get(&next_sync).copied().unwrap_or(
self.state_index.len() as StateId,
);
Some(WeightedTransition::new(
from_state,
out_input,
out_output,
next_id,
trans.weight,
))
}
}
#[derive(Clone)]
pub struct MutableSyncSource<L, W, T>
where
W: Semiring,
T: Wfst<L, W>,
{
fst: T,
state_map: FxHashMap<SyncState<L>, StateId>,
state_index: Vec<SyncState<L>>,
max_delay: usize,
computed_transitions: FxHashMap<StateId, SmallVec<[WeightedTransition<L, W>; 4]>>,
final_states: FxHashMap<StateId, W>,
_phantom: std::marker::PhantomData<W>,
}
impl<L, W, T> MutableSyncSource<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
pub fn new(fst: T, max_delay: usize) -> Self {
let mut state_map = FxHashMap::default();
let mut state_index = Vec::new();
let start = fst.start();
if start != NO_STATE {
let initial = SyncState::initial(start);
state_map.insert(initial.clone(), 0);
state_index.push(initial);
}
Self {
fst,
state_map,
state_index,
max_delay,
computed_transitions: FxHashMap::default(),
final_states: FxHashMap::default(),
_phantom: std::marker::PhantomData,
}
}
pub fn get_or_create_state(&mut self, sync_state: SyncState<L>) -> StateId {
if let Some(&id) = self.state_map.get(&sync_state) {
return id;
}
let id = self.state_index.len() as StateId;
self.state_map.insert(sync_state.clone(), id);
self.state_index.push(sync_state);
id
}
pub fn expand_state(&mut self, state: StateId) {
if self.computed_transitions.contains_key(&state) {
return;
}
let sync_state = match self.state_index.get(state as usize) {
Some(s) => s.clone(),
None => return,
};
let mut transitions = SmallVec::new();
if sync_state.draining {
if sync_state.delay.is_empty() {
self.final_states.insert(state, W::one());
} else {
let input_label = sync_state.delay.car_input();
let output_label = sync_state.delay.car_output();
let next_delay = sync_state.delay.cdr();
let next_sync = SyncState::draining(next_delay);
let next_id = self.get_or_create_state(next_sync);
transitions.push(WeightedTransition::new(
state,
input_label,
output_label,
next_id,
W::one(),
));
}
} else {
let original = sync_state.original;
if self.fst.is_final(original) {
if sync_state.delay.is_empty() {
self.final_states
.insert(state, self.fst.final_weight(original));
} else {
let input_label = sync_state.delay.car_input();
let output_label = sync_state.delay.car_output();
let next_delay = sync_state.delay.cdr();
let next_sync = SyncState::draining(next_delay);
let next_id = self.get_or_create_state(next_sync);
transitions.push(WeightedTransition::new(
state,
input_label,
output_label,
next_id,
self.fst.final_weight(original),
));
}
}
let fst_transitions: Vec<_> = self.fst.transitions(original).to_vec();
for trans in &fst_transitions {
if let Some(new_trans) = self.compute_transition(state, &sync_state, trans) {
transitions.push(new_trans);
}
}
}
self.computed_transitions.insert(state, transitions);
}
fn compute_transition(
&mut self,
from_state: StateId,
sync_state: &SyncState<L>,
trans: &WeightedTransition<L, W>,
) -> Option<WeightedTransition<L, W>> {
let mut new_input: SmallVec<[L; 4]> = sync_state.delay.input.clone();
let mut new_output: SmallVec<[L; 4]> = sync_state.delay.output.clone();
if let Some(ref i) = trans.input {
new_input.push(i.clone());
}
if let Some(ref o) = trans.output {
new_output.push(o.clone());
}
let new_delay = StringDelay::sync(new_input, new_output);
if new_delay.len() > self.max_delay {
return None;
}
let out_input = new_delay.car_input();
let out_output = new_delay.car_output();
let next_sync = SyncState {
original: trans.to,
delay: new_delay.cdr(),
draining: false,
};
let next_id = self.get_or_create_state(next_sync);
Some(WeightedTransition::new(
from_state,
out_input,
out_output,
next_id,
trans.weight,
))
}
pub fn transitions(&self, state: StateId) -> &[WeightedTransition<L, W>] {
self.computed_transitions
.get(&state)
.map(|t| t.as_slice())
.unwrap_or(&[])
}
pub fn is_final(&self, state: StateId) -> bool {
self.final_states.contains_key(&state)
}
pub fn final_weight(&self, state: StateId) -> W {
self.final_states
.get(&state)
.copied()
.unwrap_or_else(W::zero)
}
pub fn start(&self) -> StateId {
if self.fst.start() == NO_STATE {
NO_STATE
} else {
0
}
}
pub fn num_states(&self) -> usize {
self.state_index.len()
}
pub fn is_expanded(&self, state: StateId) -> bool {
self.computed_transitions.contains_key(&state)
}
}
pub fn has_bounded_delay<L, W, T>(fst: &T) -> bool
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
let start = fst.start();
if start == NO_STATE {
return true;
}
let num_states = fst.num_states();
let mut visited = vec![false; num_states];
let mut on_stack = vec![false; num_states];
let mut delays: FxHashMap<StateId, i32> = FxHashMap::default();
fn dfs<L, W, T>(
fst: &T,
state: StateId,
current_delay: i32,
visited: &mut [bool],
on_stack: &mut [bool],
delays: &mut FxHashMap<StateId, i32>,
) -> bool
where
W: Semiring,
L: Clone + Eq + Hash,
T: Wfst<L, W>,
{
let idx = state as usize;
if idx >= visited.len() {
return true;
}
if on_stack[idx] {
if let Some(&prev_delay) = delays.get(&state) {
return current_delay == prev_delay;
}
return false;
}
if visited[idx] {
return true;
}
visited[idx] = true;
on_stack[idx] = true;
delays.insert(state, current_delay);
for trans in fst.transitions(state) {
let delta = trans.output.is_some() as i32 - trans.input.is_some() as i32;
let new_delay = current_delay + delta;
if !dfs(fst, trans.to, new_delay, visited, on_stack, delays) {
return false;
}
}
on_stack[idx] = false;
true
}
dfs(fst, start, 0, &mut visited, &mut on_stack, &mut delays)
}
const MAX_DELAY_VISIT_LIMIT: usize = 100_000;
pub fn compute_max_delay<L, W, T>(fst: &T) -> Option<usize>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
if !has_bounded_delay(fst) {
return None;
}
let start = fst.start();
if start == NO_STATE {
return Some(0);
}
let mut visited = FxHashSet::default();
let mut queue = VecDeque::new();
let mut max_delay = 0i32;
queue.push_back((start, 0i32));
visited.insert((start, 0i32));
while let Some((state, delay)) = queue.pop_front() {
if visited.len() > MAX_DELAY_VISIT_LIMIT {
return None;
}
max_delay = max_delay.max(delay.abs());
for trans in fst.transitions(state) {
let delta = trans.output.is_some() as i32 - trans.input.is_some() as i32;
let new_delay = delay + delta;
if !visited.contains(&(trans.to, new_delay)) {
visited.insert((trans.to, new_delay));
queue.push_back((trans.to, new_delay));
}
}
}
Some(max_delay as usize)
}
pub type SyncWfst<L, W, T> = LazyWfstWrapper<SyncSource<L, W, T>, L, W>;
pub fn synchronize<L, W, T>(fst: &T) -> SyncWfst<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
let max_delay = compute_max_delay(fst).unwrap_or(100);
let source = SyncSource::new(fst.clone(), max_delay);
LazyWfstWrapper::new(source)
}
pub fn synchronize_bounded<L, W, T>(fst: &T, max_delay: usize) -> SyncWfst<L, W, T>
where
W: Semiring,
L: Clone + Eq + Hash + Send + Sync,
T: Wfst<L, W>,
{
let source = SyncSource::new(fst.clone(), max_delay);
LazyWfstWrapper::new(source)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::semiring::TropicalWeight;
use crate::wfst::{VectorWfst, VectorWfstBuilder};
#[test]
fn test_string_delay_empty() {
let delay: StringDelay<char> = StringDelay::empty();
assert!(delay.is_empty());
assert_eq!(delay.len(), 0);
}
#[test]
fn test_string_delay_sync_cancellation() {
let input: SmallVec<[char; 4]> = smallvec::smallvec!['a', 'b'];
let output: SmallVec<[char; 4]> = smallvec::smallvec!['a'];
let delay = StringDelay::sync(input, output);
assert_eq!(delay.input.as_slice(), &['b']);
assert!(delay.output.is_empty());
}
#[test]
fn test_string_delay_sync_no_common_prefix() {
let input: SmallVec<[char; 4]> = smallvec::smallvec!['a', 'b'];
let output: SmallVec<[char; 4]> = smallvec::smallvec!['x', 'y'];
let delay = StringDelay::sync(input, output);
assert_eq!(delay.input.as_slice(), &['a', 'b']);
assert_eq!(delay.output.as_slice(), &['x', 'y']);
}
#[test]
fn test_string_delay_car_cdr() {
let delay = StringDelay {
input: smallvec::smallvec!['a', 'b'],
output: smallvec::smallvec![],
};
assert_eq!(delay.car_input(), Some('a'));
assert_eq!(delay.car_output(), None);
let cdr = delay.cdr();
assert_eq!(cdr.input.as_slice(), &['b']);
}
#[test]
fn test_has_bounded_delay_simple() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(2)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.final_state(1, TropicalWeight::one())
.build();
assert!(has_bounded_delay(&fst));
}
#[test]
fn test_has_bounded_delay_with_epsilon() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(3)
.start(0)
.arc(0, Some('a'), None, 1, TropicalWeight::one()) .arc(1, None, Some('x'), 2, TropicalWeight::one()) .final_state(2, TropicalWeight::one())
.build();
assert!(has_bounded_delay(&fst));
}
#[test]
fn test_compute_max_delay() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(3)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.arc(1, Some('b'), None, 2, TropicalWeight::one()) .final_state(2, TropicalWeight::one())
.build();
let max_delay = compute_max_delay(&fst);
assert!(max_delay.is_some());
assert!(max_delay.expect("wfst/synchronize.rs: required value was None/Err") >= 1);
}
#[test]
fn test_unbounded_delay_cycle() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(2)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.arc(1, None, Some('y'), 1, TropicalWeight::one()) .final_state(1, TropicalWeight::one())
.build();
assert!(!has_bounded_delay(&fst));
assert!(compute_max_delay(&fst).is_none());
}
#[test]
fn test_bounded_delay_zero_cycle() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(2)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.arc(1, Some('b'), Some('y'), 1, TropicalWeight::one()) .final_state(1, TropicalWeight::one())
.build();
assert!(has_bounded_delay(&fst));
assert!(compute_max_delay(&fst).is_some());
}
#[test]
fn test_synchronize_simple() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(2)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.final_state(1, TropicalWeight::one())
.build();
let synced = synchronize(&fst);
assert_eq!(synced.start(), 0);
assert_eq!(synced.num_states(), 1); }
#[test]
fn test_sync_state_initial() {
let state: SyncState<char> = SyncState::initial(5);
assert_eq!(state.original, 5);
assert!(state.delay.is_empty());
assert!(!state.draining);
}
#[test]
fn test_sync_state_draining() {
let delay = StringDelay {
input: smallvec::smallvec!['a'],
output: smallvec::smallvec![],
};
let state: SyncState<char> = SyncState::draining(delay.clone());
assert_eq!(state.original, NO_STATE);
assert!(!state.delay.is_empty());
assert!(state.draining);
}
#[test]
fn test_mutable_sync_source_basic() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(2)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.final_state(1, TropicalWeight::one())
.build();
let mut sync = MutableSyncSource::new(fst, 10);
assert_eq!(sync.start(), 0);
assert_eq!(sync.num_states(), 1);
sync.expand_state(0);
assert!(sync.num_states() >= 1);
assert!(sync.is_expanded(0));
}
#[test]
fn test_mutable_sync_source_delayed_output() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(3)
.start(0)
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::one())
.arc(1, None, Some('y'), 2, TropicalWeight::one())
.final_state(2, TropicalWeight::one())
.build();
let mut sync = MutableSyncSource::new(fst, 10);
sync.expand_state(0);
sync.expand_state(1);
assert!(sync.is_expanded(0));
assert!(sync.is_expanded(1));
}
#[test]
fn test_empty_transducer() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfst::new();
assert!(has_bounded_delay(&fst));
assert_eq!(compute_max_delay(&fst), Some(0));
let synced = synchronize(&fst);
assert_eq!(synced.start(), NO_STATE);
}
#[test]
fn test_single_final_state() {
let fst: VectorWfst<char, TropicalWeight> = VectorWfstBuilder::new()
.add_states(1)
.start(0)
.final_state(0, TropicalWeight::one())
.build();
assert!(has_bounded_delay(&fst));
assert_eq!(compute_max_delay(&fst), Some(0));
let synced = synchronize(&fst);
assert_eq!(synced.start(), 0);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use crate::semiring::TropicalWeight;
use crate::test_utils::arb_tropical_wfst;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn single_state_has_bounded_delay(
fst in arb_tropical_wfst(1, 0)
) {
prop_assert!(has_bounded_delay(&fst));
}
#[test]
fn single_state_max_delay_zero(
fst in arb_tropical_wfst(1, 0)
) {
let max_delay = compute_max_delay(&fst);
prop_assert!(max_delay.is_some());
prop_assert_eq!(max_delay.expect("wfst/synchronize.rs: required value was None/Err"), 0);
}
#[test]
fn synchronize_preserves_start_validity(
fst in arb_tropical_wfst(3, 1)
) {
if has_bounded_delay(&fst) {
let synced = synchronize(&fst);
if fst.start() != NO_STATE {
prop_assert!(synced.start() != NO_STATE);
}
}
}
#[test]
fn bounded_delay_implies_max_delay_some(
fst in arb_tropical_wfst(3, 1)
) {
if has_bounded_delay(&fst) {
prop_assert!(compute_max_delay(&fst).is_some());
}
}
#[test]
fn unbounded_delay_implies_max_delay_none(
fst in arb_tropical_wfst(3, 1)
) {
if !has_bounded_delay(&fst) {
prop_assert!(compute_max_delay(&fst).is_none());
}
}
#[test]
fn string_delay_empty_properties(_dummy in 0..1i32) {
let delay: StringDelay<char> = StringDelay::empty();
prop_assert!(delay.is_empty());
prop_assert_eq!(delay.len(), 0);
prop_assert!(delay.car_input().is_none());
prop_assert!(delay.car_output().is_none());
}
#[test]
fn string_delay_sync_identical_is_empty(chars in prop::collection::vec(any::<char>(), 0..5)) {
let input: SmallVec<[char; 4]> = chars.iter().cloned().collect();
let output: SmallVec<[char; 4]> = chars.iter().cloned().collect();
let delay = StringDelay::sync(input, output);
prop_assert!(delay.is_empty());
}
#[test]
fn sync_state_initial_properties(state_id in 0..100u32) {
let state: SyncState<char> = SyncState::initial(state_id);
prop_assert_eq!(state.original, state_id);
prop_assert!(state.delay.is_empty());
prop_assert!(!state.draining);
}
#[test]
fn mutable_sync_source_state_count(
fst in arb_tropical_wfst(4, 2)
) {
if fst.start() != NO_STATE && has_bounded_delay(&fst) {
let sync = MutableSyncSource::new(fst, 10);
prop_assert!(sync.num_states() >= 1);
}
}
#[test]
fn mutable_sync_source_start_consistency(
fst in arb_tropical_wfst(4, 2)
) {
if has_bounded_delay(&fst) {
let sync: MutableSyncSource<char, TropicalWeight, _> = MutableSyncSource::new(fst.clone(), 10);
if fst.start() == NO_STATE {
prop_assert_eq!(sync.start(), NO_STATE);
} else {
prop_assert_eq!(sync.start(), 0);
}
}
}
}
}