use rustc_hash::FxHashMap;
use crate::arc::{Arc, ArcLabel, ArcStateId};
use crate::fst::{Fst, MutableFst};
use crate::properties::{K_FST_PROPERTIES, synchronize_properties};
use crate::weight::Weight;
type StringId = usize;
struct Strings<L> {
strings: Vec<Vec<L>>,
ids: FxHashMap<Vec<L>, StringId>,
}
impl<L: ArcLabel> Strings<L> {
fn new() -> Self {
let mut table = Self {
strings: Vec::new(),
ids: FxHashMap::default(),
};
table.intern(Vec::new());
table
}
fn intern(&mut self, string: Vec<L>) -> StringId {
if let Some(&id) = self.ids.get(&string) {
return id;
}
let id = self.strings.len();
self.ids.insert(string.clone(), id);
self.strings.push(string);
id
}
fn get(&self, id: StringId) -> &[L] {
&self.strings[id]
}
fn head(&self, id: StringId, label: L) -> L {
self.get(id).first().copied().unwrap_or(label)
}
fn tail(&mut self, id: StringId, label: L) -> StringId {
if self.get(id).is_empty() {
return self.intern(Vec::new());
}
let rest = self.get(id)[1..].to_vec();
let rest = self.intern(rest);
self.append(rest, label)
}
fn append(&mut self, id: StringId, label: L) -> StringId {
if label == L::epsilon() {
return id;
}
let mut string = self.get(id).to_vec();
string.push(label);
self.intern(string)
}
fn is_empty(&self, id: StringId, label: L) -> bool {
self.get(id).is_empty() && label == L::epsilon()
}
fn len(&self, id: StringId) -> usize {
self.get(id).len()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Element<S> {
state: Option<S>,
istring: StringId,
ostring: StringId,
}
pub fn synchronize<A: Arc, F1: Fst<A>, F2: MutableFst<A>>(ifst: &F1, ofst: &mut F2) {
ofst.delete_all_states();
ofst.set_input_symbols(ifst.input_symbols());
ofst.set_output_symbols(ifst.output_symbols());
let iprops = ifst.properties(K_FST_PROPERTIES, false);
let Some(istart) = ifst.start() else {
ofst.set_properties(synchronize_properties(iprops), K_FST_PROPERTIES);
return;
};
let mut strings = Strings::<A::Label>::new();
let empty = strings.intern(Vec::new());
let mut elements: Vec<Element<A::StateId>> = Vec::new();
let mut ids: FxHashMap<Element<A::StateId>, A::StateId> = FxHashMap::default();
let mut find_state = |element: Element<A::StateId>,
elements: &mut Vec<Element<A::StateId>>,
ofst: &mut F2|
-> A::StateId {
*ids.entry(element).or_insert_with(|| {
elements.push(element);
ofst.add_state()
})
};
let start = find_state(
Element {
state: Some(istart),
istring: empty,
ostring: empty,
},
&mut elements,
ofst,
);
ofst.set_start(start);
let zero = A::Weight::zero();
let epsilon = A::Label::epsilon();
let mut next = 0;
while next < elements.len() {
let element = elements[next];
let state = A::StateId::from_usize(next);
next += 1;
if let Some(input_state) = element.state {
for arc in ifst.arcs(input_state) {
let target = if !strings.is_empty(element.istring, arc.ilabel())
&& !strings.is_empty(element.ostring, arc.olabel())
{
let ilabel = strings.head(element.istring, arc.ilabel());
let olabel = strings.head(element.ostring, arc.olabel());
let istring = strings.tail(element.istring, arc.ilabel());
let ostring = strings.tail(element.ostring, arc.olabel());
let next_state = find_state(
Element {
state: Some(arc.nextstate()),
istring,
ostring,
},
&mut elements,
ofst,
);
A::new(ilabel, olabel, arc.weight().clone(), next_state)
} else {
let istring = strings.append(element.istring, arc.ilabel());
let ostring = strings.append(element.ostring, arc.olabel());
let next_state = find_state(
Element {
state: Some(arc.nextstate()),
istring,
ostring,
},
&mut elements,
ofst,
);
A::new(epsilon, epsilon, arc.weight().clone(), next_state)
};
ofst.add_arc(state, target);
}
}
let weight = match element.state {
Some(input_state) => ifst.final_weight(input_state),
None => A::Weight::one(),
};
let held = strings.len(element.istring) + strings.len(element.ostring);
if weight != zero && held > 0 {
let ilabel = strings.head(element.istring, epsilon);
let olabel = strings.head(element.ostring, epsilon);
let istring = strings.tail(element.istring, epsilon);
let ostring = strings.tail(element.ostring, epsilon);
let next_state = find_state(
Element {
state: None,
istring,
ostring,
},
&mut elements,
ofst,
);
ofst.add_arc(state, A::new(ilabel, olabel, weight.clone(), next_state));
}
if weight != zero && held == 0 {
ofst.set_final(state, weight);
}
}
ofst.set_properties(synchronize_properties(iprops), K_FST_PROPERTIES);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::test_support::{string_weights, visible_paths};
use crate::arc::StdArc;
use crate::fst::ExpandedFst as _;
use crate::fsts::vector_fst::StdVectorFst;
use crate::weights::float_weight::TropicalWeight;
fn delays<F: Fst<StdArc>>(fst: &F, max_len: usize) -> Vec<Vec<i32>> {
fn walk<F: Fst<StdArc>>(
fst: &F,
state: i32,
delay: i32,
trace: &mut Vec<i32>,
left: usize,
out: &mut Vec<Vec<i32>>,
) {
if fst.final_weight(state) != TropicalWeight::zero() {
out.push(trace.clone());
}
if left == 0 {
return;
}
for arc in fst.arcs(state) {
let next = delay + i32::from(arc.olabel() != 0) - i32::from(arc.ilabel() != 0);
trace.push(next);
walk(fst, arc.nextstate(), next, trace, left - 1, out);
trace.pop();
}
}
let mut out = Vec::new();
if let Some(start) = fst.start() {
walk(fst, start, 0, &mut Vec::new(), max_len, &mut out);
}
out
}
fn lagging() -> StdVectorFst {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 0, TropicalWeight(1.0), 1));
fst.add_arc(1, StdArc::new(0, 2, TropicalWeight(2.0), 2));
fst.set_final(2, TropicalWeight(3.0));
fst
}
#[test]
fn synchronizing_pairs_the_labels_up() {
let ifst = lagging();
let mut ofst = StdVectorFst::new();
synchronize(&ifst, &mut ofst);
let labelled: Vec<(i32, i32)> = (0..ofst.num_states() as i32)
.flat_map(|s| {
ofst.arcs(s)
.map(|a| (a.ilabel(), a.olabel()))
.collect::<Vec<_>>()
})
.filter(|&(i, o)| i != 0 || o != 0)
.collect();
assert_eq!(
labelled,
vec![(1, 2)],
"the two labels travel on one arc, and there is only the one"
);
}
#[test]
fn synchronizing_preserves_what_the_transducer_maps() {
let ifst = lagging();
let mut ofst = StdVectorFst::new();
synchronize(&ifst, &mut ofst);
assert_eq!(
string_weights(visible_paths(&ofst, 8)),
string_weights(visible_paths(&ifst, 8))
);
}
#[test]
fn the_delay_never_comes_back_down() {
let mut fst = StdVectorFst::new();
for _ in 0..4 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(0, 1, TropicalWeight::one(), 1));
fst.add_arc(1, StdArc::new(0, 2, TropicalWeight::one(), 2));
fst.add_arc(2, StdArc::new(3, 0, TropicalWeight::one(), 3));
fst.set_final(3, TropicalWeight::one());
let before = delays(&fst, 8);
assert!(
before
.iter()
.any(|trace| trace.windows(2).any(|w| w[1] < w[0] && w[1] != 0)),
"the input should not already be synchronized"
);
let mut ofst = StdVectorFst::new();
synchronize(&fst, &mut ofst);
for trace in delays(&ofst, 8) {
for window in trace.windows(2) {
assert!(
window[1] == 0 || window[1] > window[0],
"delay went from {} to {} in {trace:?}",
window[0],
window[1]
);
}
}
assert_eq!(
string_weights(visible_paths(&ofst, 8)),
string_weights(visible_paths(&fst, 8))
);
}
#[test]
fn an_already_synchronized_fst_keeps_its_paths() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight(1.0), 1));
fst.add_arc(1, StdArc::new(2, 2, TropicalWeight(2.0), 2));
fst.set_final(2, TropicalWeight::one());
let mut ofst = StdVectorFst::new();
synchronize(&fst, &mut ofst);
assert_eq!(
string_weights(visible_paths(&ofst, 8)),
string_weights(visible_paths(&fst, 8))
);
assert_eq!(ofst.num_states(), 3);
}
#[test]
fn a_zero_delay_cycle_terminates() {
let mut fst = StdVectorFst::new();
for _ in 0..2 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 0, TropicalWeight::one(), 1));
fst.add_arc(1, StdArc::new(0, 2, TropicalWeight::one(), 0));
fst.set_final(0, TropicalWeight::one());
let mut ofst = StdVectorFst::new();
synchronize(&fst, &mut ofst);
assert!(ofst.num_states() > 0);
assert_eq!(
string_weights(visible_paths(&ofst, 6)),
string_weights(visible_paths(&fst, 6))
);
}
#[test]
fn an_fst_with_no_start_state_synchronizes_to_nothing() {
let ifst = StdVectorFst::new();
let mut ofst = StdVectorFst::new();
ofst.add_state();
synchronize(&ifst, &mut ofst);
assert_eq!(ofst.num_states(), 0);
}
}