use rustc_hash::FxHashSet;
use crate::algorithms::cc_visitors::SccVisitor;
use crate::algorithms::connect::connect;
use crate::algorithms::dfs_visit::dfs_visit_any;
use crate::arc::{Arc, ArcLabel, ArcStateId};
use crate::data_structures::bit_set::GrowableBitSet;
use crate::fst::MutableFst;
use crate::weight::{LEFT_SEMIRING, Weight};
pub fn rm_final_epsilon<A: Arc, F: MutableFst<A>>(fst: &mut F) {
let mut coaccess = GrowableBitSet::new();
let mut props = 0;
{
let mut visitor = SccVisitor::new(&*fst, None, None, Some(&mut coaccess), &mut props);
dfs_visit_any(&*fst, &mut visitor);
}
let epsilon = A::Label::epsilon();
let zero = A::Weight::zero();
let mut removable: FxHashSet<A::StateId> = FxHashSet::default();
for s in fst.states() {
if fst.final_weight(s) == zero {
continue;
}
let future_coaccess = fst
.arcs(s)
.any(|arc| coaccess.contains(arc.nextstate().as_usize()));
if !future_coaccess {
removable.insert(s);
}
}
let left_distributive = A::Weight::properties() & LEFT_SEMIRING != 0;
let states: Vec<A::StateId> = fst.states().collect();
let mut kept: Vec<A> = Vec::new();
for s in states {
let mut weight = fst.final_weight(s);
kept.clear();
let narcs = fst.num_arcs(s);
for arc in fst.arcs(s) {
let folds = removable.contains(&arc.nextstate())
&& arc.ilabel() == epsilon
&& arc.olabel() == epsilon
&& (weight == zero || left_distributive);
if folds {
let through = arc.weight().times(&fst.final_weight(arc.nextstate()));
weight = through.plus(&weight);
} else {
kept.push(arc);
}
}
if kept.len() < narcs {
fst.delete_arcs(s);
fst.set_final(s, weight);
for arc in kept.drain(..) {
fst.add_arc(s, arc);
}
}
}
connect(fst);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::test_support::{Rng, random_acyclic_fst, string_weights, visible_paths};
use crate::arc::StdArc;
use crate::fst::{ExpandedFst as _, Fst as _};
use crate::fsts::vector_fst::StdVectorFst;
use crate::weights::float_weight::TropicalWeight;
fn chain_with_epsilon_tail() -> StdVectorFst {
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(0, 0, TropicalWeight(2.0), 2));
fst.set_final(2, TropicalWeight(3.0));
fst
}
#[test]
fn an_epsilon_only_final_state_is_folded_into_the_state_before_it() {
let mut fst = chain_with_epsilon_tail();
rm_final_epsilon(&mut fst);
assert_eq!(fst.num_states(), 2);
assert_eq!(fst.final_weight(1), TropicalWeight(5.0));
assert_eq!(fst.num_arcs(1), 0);
}
#[test]
fn a_final_state_reached_by_a_labelled_arc_is_kept() {
let mut fst = StdVectorFst::new();
for _ in 0..2 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(7, 7, TropicalWeight(1.0), 1));
fst.set_final(1, TropicalWeight(2.0));
rm_final_epsilon(&mut fst);
assert_eq!(fst.num_states(), 2);
assert_eq!(fst.final_weight(1), TropicalWeight(2.0));
}
#[test]
fn a_final_state_with_a_useful_future_is_kept() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(0, 0, TropicalWeight(1.0), 1));
fst.add_arc(1, StdArc::new(5, 5, TropicalWeight(1.0), 2));
fst.set_final(1, TropicalWeight(2.0));
fst.set_final(2, TropicalWeight(3.0));
rm_final_epsilon(&mut fst);
assert_eq!(fst.num_states(), 3, "state 1 leads on to state 2");
}
#[test]
fn removing_final_epsilons_preserves_the_language() {
let mut rng = Rng::new(0xF1A1_EB50);
for round in 0..200 {
let mut fst = random_acyclic_fst(&mut rng, 6);
for s in 0..fst.num_states() as i32 {
if rng.below(2) == 0 {
fst.mutate_arcs(s, |arc| {
*arc = StdArc::new(0, 0, *arc.weight(), arc.nextstate());
});
}
}
let before = string_weights(visible_paths(&fst, 8));
rm_final_epsilon(&mut fst);
let after = string_weights(visible_paths(&fst, 8));
assert_eq!(after, before, "round {round}");
}
}
#[test]
fn an_fst_with_nothing_to_remove_is_left_alone() {
let mut fst = StdVectorFst::new();
fst.add_state();
fst.set_start(0);
fst.set_final(0, TropicalWeight(1.0));
rm_final_epsilon(&mut fst);
assert_eq!(fst.num_states(), 1);
assert_eq!(fst.final_weight(0), TropicalWeight(1.0));
}
}