use crate::arc::Arc;
use crate::fst::{Fst, MutableFst, StateId};
use crate::semiring::StarSemiring;
use crate::Result;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
pub fn remove_epsilons<W, F, M>(fst: &F) -> Result<M>
where
W: StarSemiring,
F: Fst<W>,
M: MutableFst<W> + Default,
{
let mut result = M::default();
for _ in 0..fst.num_states() {
result.add_state();
}
if let Some(start) = fst.start() {
result.set_start(start);
}
for state in fst.states() {
let closure = compute_epsilon_closure(fst, state)?;
for arc in fst.arcs(state) {
if !arc.is_epsilon() {
result.add_arc(state, arc.clone());
}
}
let mut accumulated_final_weight = fst.final_weight(state).cloned();
for &(closure_state, ref weight) in &closure {
if closure_state != state {
for arc in fst.arcs(closure_state) {
if !arc.is_epsilon() {
result.add_arc(
state,
Arc::new(
arc.ilabel,
arc.olabel,
weight.times(&arc.weight),
arc.nextstate,
),
);
}
}
if let Some(final_weight) = fst.final_weight(closure_state) {
let propagated_weight = weight.times(final_weight);
accumulated_final_weight = match accumulated_final_weight {
Some(existing) => Some(existing.plus(&propagated_weight)),
None => Some(propagated_weight),
};
}
}
}
if let Some(final_weight) = accumulated_final_weight {
result.set_final(state, final_weight);
}
}
Ok(result)
}
fn find_epsilon_sccs<W: StarSemiring, F: Fst<W>>(
fst: &F,
) -> (
FxHashMap<StateId, StateId>,
FxHashMap<StateId, Vec<StateId>>,
) {
let n = fst.num_states();
let mut index_counter = 0;
let mut stack = Vec::new();
let mut indices = vec![None; n];
let mut lowlinks = vec![None; n];
let mut on_stack = vec![false; n];
let mut scc_map = FxHashMap::default();
let mut scc_id = 0;
let mut scc_states: FxHashMap<StateId, Vec<StateId>> = FxHashMap::default();
for state_idx in 0..n {
let state = state_idx as StateId;
if indices[state_idx].is_none() {
tarjan_epsilon_dfs(
fst,
state,
&mut index_counter,
&mut stack,
&mut indices,
&mut lowlinks,
&mut on_stack,
&mut scc_map,
&mut scc_id,
&mut scc_states,
);
}
}
(scc_map, scc_states)
}
#[allow(clippy::too_many_arguments)]
fn tarjan_epsilon_dfs<W: StarSemiring, F: Fst<W>>(
fst: &F,
v: StateId,
index_counter: &mut usize,
stack: &mut Vec<StateId>,
indices: &mut Vec<Option<usize>>,
lowlinks: &mut Vec<Option<usize>>,
on_stack: &mut Vec<bool>,
scc_map: &mut FxHashMap<StateId, StateId>,
scc_id: &mut StateId,
scc_states: &mut FxHashMap<StateId, Vec<StateId>>,
) {
let v_idx = v as usize;
indices[v_idx] = Some(*index_counter);
lowlinks[v_idx] = Some(*index_counter);
*index_counter += 1;
stack.push(v);
on_stack[v_idx] = true;
for arc in fst.arcs(v) {
if arc.is_epsilon() {
let w = arc.nextstate;
let w_idx = w as usize;
if indices[w_idx].is_none() {
tarjan_epsilon_dfs(
fst,
w,
index_counter,
stack,
indices,
lowlinks,
on_stack,
scc_map,
scc_id,
scc_states,
);
lowlinks[v_idx] = Some(lowlinks[v_idx].unwrap().min(lowlinks[w_idx].unwrap()));
} else if on_stack[w_idx] {
lowlinks[v_idx] = Some(lowlinks[v_idx].unwrap().min(indices[w_idx].unwrap()));
}
}
}
if lowlinks[v_idx] == indices[v_idx] {
let current_scc = *scc_id;
*scc_id += 1;
let mut scc = Vec::new();
loop {
let w = stack.pop().unwrap();
let w_idx = w as usize;
on_stack[w_idx] = false;
scc_map.insert(w, current_scc);
scc.push(w);
if w == v {
break;
}
}
scc_states.insert(current_scc, scc);
}
}
fn compute_scc_cycle_weight<W: StarSemiring, F: Fst<W>>(
fst: &F,
scc_states: &[StateId],
) -> Option<W> {
if scc_states.is_empty() {
return None;
}
if scc_states.len() == 1 {
let state = scc_states[0];
for arc in fst.arcs(state) {
if arc.is_epsilon() && arc.nextstate == state {
return Some(arc.weight.star());
}
}
return None; }
let start = scc_states[0];
find_cycle_weight_from_state(fst, start, scc_states).map(|w| w.star())
}
fn find_cycle_weight_from_state<W: StarSemiring, F: Fst<W>>(
fst: &F,
start: StateId,
scc_states: &[StateId],
) -> Option<W> {
let mut queue = VecDeque::new();
let mut visited: FxHashMap<(StateId, usize), W> = FxHashMap::default();
for arc in fst.arcs(start) {
if arc.is_epsilon() && scc_states.contains(&arc.nextstate) {
let next = arc.nextstate;
if next == start {
return Some(arc.weight.clone());
}
queue.push_back((next, arc.weight.clone(), 1));
visited.insert((next, 1), arc.weight.clone());
}
}
let max_depth = scc_states.len() * 2;
while let Some((state, weight, depth)) = queue.pop_front() {
if depth > max_depth {
continue;
}
for arc in fst.arcs(state) {
if arc.is_epsilon() && scc_states.contains(&arc.nextstate) {
let next = arc.nextstate;
let next_weight = weight.times(&arc.weight);
let next_depth = depth + 1;
if next == start {
return Some(next_weight);
}
let key = (next, next_depth);
let should_explore = match visited.get(&key) {
Some(existing) => {
let combined = existing.plus(&next_weight);
if combined != *existing {
visited.insert(key, combined.clone());
true
} else {
false
}
}
None => {
visited.insert(key, next_weight.clone());
true
}
};
if should_explore && next_depth <= max_depth {
queue.push_back((next, visited[&key].clone(), next_depth));
}
}
}
}
None
}
fn compute_epsilon_closure<W: StarSemiring, F: Fst<W>>(
fst: &F,
start: StateId,
) -> Result<Vec<(StateId, W)>> {
let (scc_map, scc_states_map) = find_epsilon_sccs(fst);
let mut scc_star_weights: FxHashMap<StateId, W> = FxHashMap::default();
for (scc_id, states) in &scc_states_map {
if let Some(star_weight) = compute_scc_cycle_weight(fst, states) {
scc_star_weights.insert(*scc_id, star_weight);
}
}
let mut closure = FxHashMap::default();
let mut queue = VecDeque::new();
queue.push_back((start, W::one()));
closure.insert(start, W::one());
while let Some((state, weight)) = queue.pop_front() {
let mut current_best = match closure.get(&state) {
Some(existing) => {
let combined = existing.plus(&weight);
if combined != *existing {
closure.insert(state, combined.clone());
combined
} else {
existing.clone()
}
}
None => {
closure.insert(state, weight.clone());
weight.clone()
}
};
let state_scc = scc_map.get(&state).copied();
if let Some(scc_id) = state_scc {
if let Some(star_weight) = scc_star_weights.get(&scc_id) {
let cycle_enhanced = current_best.times(star_weight);
let best_with_cycle = current_best.plus(&cycle_enhanced);
if best_with_cycle != current_best {
closure.insert(state, best_with_cycle.clone());
current_best = best_with_cycle;
}
}
}
for arc in fst.arcs(state) {
if arc.is_epsilon() {
let next_state = arc.nextstate;
let next_weight = current_best.times(&arc.weight);
let should_update = match closure.get(&next_state) {
Some(existing) => {
let combined = existing.plus(&next_weight);
if combined != *existing {
closure.insert(next_state, combined.clone());
true
} else {
false
}
}
None => {
closure.insert(next_state, next_weight.clone());
true
}
};
if should_update {
queue.push_back((next_state, closure[&next_state].clone()));
}
}
}
}
let mut result: Vec<(StateId, W)> = closure.into_iter().collect();
result.sort_by_key(|(state, _)| *state);
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use num_traits::One;
#[test]
fn test_remove_epsilons() {
let mut bool_fst = VectorFst::<BooleanWeight>::new();
let s0 = bool_fst.add_state();
let s1 = bool_fst.add_state();
let s2 = bool_fst.add_state();
bool_fst.set_start(s0);
bool_fst.set_final(s2, BooleanWeight::one());
bool_fst.add_arc(s0, Arc::epsilon(BooleanWeight::new(true), s1));
bool_fst.add_arc(s1, Arc::new(1, 1, BooleanWeight::new(true), s2));
let no_eps: VectorFst<BooleanWeight> =
remove_epsilons::<BooleanWeight, VectorFst<BooleanWeight>, VectorFst<BooleanWeight>>(
&bool_fst,
)
.unwrap();
for state in no_eps.states() {
for arc in no_eps.arcs(state) {
assert!(!arc.is_epsilon(), "Found epsilon arc: {arc:?}");
}
}
assert!(no_eps.start().is_some());
}
#[test]
fn test_remove_epsilons_none() {
let mut bool_fst = VectorFst::<BooleanWeight>::new();
let s0 = bool_fst.add_state();
let s1 = bool_fst.add_state();
let s2 = bool_fst.add_state();
bool_fst.set_start(s0);
bool_fst.set_final(s2, BooleanWeight::one());
bool_fst.add_arc(s0, Arc::epsilon(BooleanWeight::new(true), s1));
bool_fst.add_arc(s1, Arc::new(1, 1, BooleanWeight::new(true), s2));
let no_eps: VectorFst<BooleanWeight> =
remove_epsilons::<BooleanWeight, VectorFst<BooleanWeight>, VectorFst<BooleanWeight>>(
&bool_fst,
)
.unwrap();
assert!(no_eps.num_states() >= bool_fst.num_states() - 1); }
#[test]
fn test_remove_epsilons_multiple_paths() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s2));
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s2));
fst.add_arc(s2, Arc::new(1, 1, BooleanWeight::one(), s3));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
let arcs_from_start: Vec<_> = result.arcs(s0).collect();
assert!(arcs_from_start
.iter()
.any(|a| a.ilabel == 1 && a.nextstate == s3));
for state in result.states() {
for arc in result.arcs(state) {
assert!(!arc.is_epsilon());
}
}
}
#[test]
fn test_remove_epsilons_cycles() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s2));
fst.add_arc(s2, Arc::epsilon(BooleanWeight::one(), s0));
fst.add_arc(s1, Arc::new(1, 1, BooleanWeight::one(), s3));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
assert!(result.is_final(s3));
let arcs_from_start: Vec<_> = result.arcs(s0).collect();
assert!(!arcs_from_start.is_empty());
}
#[test]
fn test_remove_epsilons_to_final() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, BooleanWeight::one());
fst.add_arc(s0, Arc::new(1, 1, BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s2));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert!(result.is_final(s1));
assert!(result.is_final(s2));
}
#[test]
fn test_remove_epsilons_all_epsilon() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s2));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert!(result.is_final(s0));
let total_arcs: usize = result.states().map(|s| result.num_arcs(s)).sum();
assert_eq!(total_arcs, 0);
}
#[test]
fn test_remove_epsilons_mixed_paths() {
let mut fst = VectorFst::<BooleanWeight>::new();
let states: Vec<_> = (0..5).map(|_| fst.add_state()).collect();
fst.set_start(states[0]);
fst.set_final(states[4], BooleanWeight::one());
fst.add_arc(states[0], Arc::new(1, 1, BooleanWeight::one(), states[1]));
fst.add_arc(states[1], Arc::epsilon(BooleanWeight::one(), states[2]));
fst.add_arc(states[2], Arc::new(2, 2, BooleanWeight::one(), states[3]));
fst.add_arc(states[3], Arc::epsilon(BooleanWeight::one(), states[4]));
fst.add_arc(states[0], Arc::epsilon(BooleanWeight::one(), states[2]));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
for state in result.states() {
for arc in result.arcs(state) {
assert!(!arc.is_epsilon());
}
}
assert!(result.is_final(states[4]));
}
#[test]
fn test_epsilon_closure_computation() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s1));
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s2));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s3));
fst.add_arc(s2, Arc::epsilon(BooleanWeight::one(), s3));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
let reached_states: Vec<_> = closure.iter().map(|(state, _)| *state).collect();
assert!(reached_states.contains(&s0));
assert!(reached_states.contains(&s1));
assert!(reached_states.contains(&s2));
assert!(reached_states.contains(&s3));
}
#[test]
fn test_remove_epsilons_preserves_weights() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, BooleanWeight::one());
fst.add_arc(s0, Arc::new(1, 1, BooleanWeight::new(true), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::new(true), s2));
fst.add_arc(s2, Arc::new(2, 2, BooleanWeight::new(true), s3));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
let has_direct_path = result.arcs(s1).any(|a| a.ilabel == 2 && a.nextstate == s3);
assert!(has_direct_path);
}
#[test]
fn test_remove_epsilons_empty_fst() {
let fst = VectorFst::<BooleanWeight>::new();
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert_eq!(result.num_states(), 0);
assert!(result.start().is_none());
}
#[test]
fn test_remove_epsilons_single_state() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s0));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert_eq!(result.num_states(), 1);
assert!(result.is_final(s0));
let self_loops: Vec<_> = result.arcs(s0).filter(|a| a.is_epsilon()).collect();
assert!(self_loops.is_empty());
}
#[test]
fn test_remove_epsilons_tropical_weight() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::new(1.0));
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.5), s1));
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.3), s2));
fst.add_arc(s1, Arc::new(1, 1, TropicalWeight::new(2.0), s3));
fst.add_arc(s2, Arc::new(1, 1, TropicalWeight::new(1.5), s3));
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
for state in result.states() {
for arc in result.arcs(state) {
assert!(!arc.is_epsilon(), "Epsilon arc found after removal");
}
}
assert!(result.num_states() >= fst.num_states());
assert!(result.start().is_some());
}
#[test]
fn test_epsilon_closure_with_self_loop() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.5), s0));
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(1, 1, TropicalWeight::new(2.0), s2));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
let reached_states: Vec<_> = closure.iter().map(|(state, _)| *state).collect();
assert!(reached_states.contains(&s0));
assert!(reached_states.contains(&s1));
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
let arcs_from_s0: Vec<_> = result.arcs(s0).collect();
assert!(arcs_from_s0
.iter()
.any(|a| a.ilabel == 1 && a.nextstate == s2));
}
#[test]
fn test_epsilon_closure_with_multi_state_cycle() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.5), s1));
fst.add_arc(s1, Arc::epsilon(TropicalWeight::new(0.3), s2));
fst.add_arc(s2, Arc::epsilon(TropicalWeight::new(0.2), s0));
fst.add_arc(s1, Arc::new(1, 1, TropicalWeight::new(1.0), s3));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
let reached_states: Vec<_> = closure.iter().map(|(state, _)| *state).collect();
assert!(reached_states.contains(&s0));
assert!(reached_states.contains(&s1));
assert!(reached_states.contains(&s2));
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
let arcs_from_s0: Vec<_> = result.arcs(s0).collect();
assert!(arcs_from_s0
.iter()
.any(|a| a.ilabel == 1 && a.nextstate == s3));
}
#[test]
fn test_epsilon_closure_boolean_weight_cycle() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, BooleanWeight::one());
fst.add_arc(s0, Arc::epsilon(BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::epsilon(BooleanWeight::one(), s0));
fst.add_arc(s0, Arc::new(1, 1, BooleanWeight::one(), s2));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
let reached_states: Vec<_> = closure.iter().map(|(state, _)| *state).collect();
assert!(reached_states.contains(&s0));
assert!(reached_states.contains(&s1));
let result: VectorFst<BooleanWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
assert!(result.is_final(s2));
}
#[test]
fn test_epsilon_closure_multiple_cycles() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
let s4 = fst.add_state();
fst.set_start(s0);
fst.set_final(s4, TropicalWeight::one());
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.5), s1));
fst.add_arc(s1, Arc::epsilon(TropicalWeight::new(0.3), s0));
fst.add_arc(s2, Arc::epsilon(TropicalWeight::new(0.2), s3));
fst.add_arc(s3, Arc::epsilon(TropicalWeight::new(0.4), s2));
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(1.0), s2));
fst.add_arc(s2, Arc::new(1, 1, TropicalWeight::new(2.0), s4));
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
let arcs_from_s0: Vec<_> = result.arcs(s0).collect();
assert!(arcs_from_s0
.iter()
.any(|a| a.ilabel == 1 && a.nextstate == s4));
}
#[test]
fn test_epsilon_closure_acyclic_path() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.5), s1));
fst.add_arc(s1, Arc::epsilon(TropicalWeight::new(0.3), s2));
fst.add_arc(s2, Arc::new(1, 1, TropicalWeight::new(1.0), s3));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
let reached_states: Vec<_> = closure.iter().map(|(state, _)| *state).collect();
assert!(reached_states.contains(&s0));
assert!(reached_states.contains(&s1));
assert!(reached_states.contains(&s2));
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
let arcs_from_s0: Vec<_> = result.arcs(s0).collect();
assert!(arcs_from_s0
.iter()
.any(|a| a.ilabel == 1 && a.nextstate == s3));
}
#[test]
fn test_epsilon_closure_termination() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, TropicalWeight::one());
fst.add_arc(s0, Arc::epsilon(TropicalWeight::new(0.1), s0)); fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
let closure = compute_epsilon_closure(&fst, s0).unwrap();
assert!(!closure.is_empty());
let result: VectorFst<TropicalWeight> = remove_epsilons(&fst).unwrap();
assert!(result.start().is_some());
}
}