use crate::arc::Arc;
use crate::fst::{Fst, Label, MutableFst, StateId, VectorFst};
use crate::semiring::Semiring;
use crate::Result;
use std::cmp::min;
use std::collections::HashMap;
pub fn condense<W, F>(fst: &F) -> Result<VectorFst<W>>
where
W: Semiring,
F: Fst<W>,
{
if fst.num_states() == 0 {
return Ok(VectorFst::new());
}
let scc_map = find_sccs(fst);
let num_sccs = if scc_map.is_empty() {
0
} else {
*scc_map.values().max().unwrap() + 1
};
let mut result = VectorFst::<W>::new();
for _ in 0..num_sccs {
result.add_state();
}
if let Some(start) = fst.start() {
let start_scc = scc_map[&start];
result.set_start(start_scc);
}
let mut added_arcs: HashMap<(StateId, Label, Label, StateId), W> = HashMap::new();
for state_idx in 0..fst.num_states() {
let state = state_idx as StateId;
let src_scc = scc_map[&state];
if let Some(final_weight) = fst.final_weight(state) {
if let Some(existing) = result.final_weight(src_scc) {
result.set_final(src_scc, existing.plus(final_weight));
} else {
result.set_final(src_scc, final_weight.clone());
}
}
for arc in fst.arcs(state) {
let dest_scc = scc_map[&arc.nextstate];
if src_scc != dest_scc {
let key = (src_scc, arc.ilabel, arc.olabel, dest_scc);
if let Some(existing_weight) = added_arcs.get(&key) {
let new_weight = existing_weight.plus(&arc.weight);
added_arcs.insert(key, new_weight.clone());
} else {
added_arcs.insert(key, arc.weight.clone());
}
}
}
}
for ((src_scc, ilabel, olabel, dest_scc), weight) in added_arcs {
result.add_arc(src_scc, Arc::new(ilabel, olabel, weight, dest_scc));
}
Ok(result)
}
fn find_sccs<W, F>(fst: &F) -> HashMap<StateId, StateId>
where
W: Semiring,
F: Fst<W>,
{
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 = HashMap::new();
let mut scc_id = 0;
for state_idx in 0..n {
let state = state_idx as StateId;
if indices[state_idx].is_none() {
tarjan_dfs(
fst,
state,
&mut index_counter,
&mut stack,
&mut indices,
&mut lowlinks,
&mut on_stack,
&mut scc_map,
&mut scc_id,
);
}
}
scc_map
}
#[allow(clippy::too_many_arguments)]
fn tarjan_dfs<W, F>(
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 HashMap<StateId, StateId>,
scc_id: &mut StateId,
) where
W: Semiring,
F: Fst<W>,
{
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) {
let w = arc.nextstate;
let w_idx = w as usize;
if indices[w_idx].is_none() {
tarjan_dfs(
fst,
w,
index_counter,
stack,
indices,
lowlinks,
on_stack,
scc_map,
scc_id,
);
lowlinks[v_idx] = Some(min(lowlinks[v_idx].unwrap(), lowlinks[w_idx].unwrap()));
} else if on_stack[w_idx] {
lowlinks[v_idx] = Some(min(lowlinks[v_idx].unwrap(), indices[w_idx].unwrap()));
}
}
if lowlinks[v_idx] == indices[v_idx] {
let current_scc = *scc_id;
*scc_id += 1;
loop {
let w = stack.pop().unwrap();
let w_idx = w as usize;
on_stack[w_idx] = false;
scc_map.insert(w, current_scc);
if w == v {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_condense_empty_fst() {
let fst = VectorFst::<TropicalWeight>::new();
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 0);
}
#[test]
fn test_condense_single_state() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, TropicalWeight::one());
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 1);
assert_eq!(result.start(), Some(0));
assert!(result.final_weight(0).is_some());
}
#[test]
fn test_condense_acyclic_chain() {
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::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(2.0), s2));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 3);
assert!(result.start().is_some());
assert_eq!(result.arcs(result.start().unwrap()).count(), 1);
}
#[test]
fn test_condense_simple_cycle() {
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::new(1, 1, TropicalWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s0));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 1);
assert!(result.start().is_some());
assert!(result.final_weight(result.start().unwrap()).is_some());
}
#[test]
fn test_condense_multiple_sccs() {
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::new(1, 1, TropicalWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s2));
fst.add_arc(s2, Arc::new(3, 3, TropicalWeight::one(), s1));
fst.add_arc(s2, Arc::new(4, 4, TropicalWeight::one(), s3));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 3);
assert!(result.start().is_some());
}
#[test]
fn test_condense_self_loop() {
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::new(1, 1, TropicalWeight::one(), s0)); fst.add_arc(s0, Arc::new(2, 2, TropicalWeight::one(), s1));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 2);
}
#[test]
fn test_condense_preserves_labels() {
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::new(5, 10, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(15, 20, TropicalWeight::new(2.0), s2));
let result = condense(&fst).unwrap();
let arcs_s0: Vec<_> = result.arcs(result.start().unwrap()).collect();
assert!(arcs_s0.iter().any(|a| a.ilabel == 5 && a.olabel == 10));
}
#[test]
fn test_condense_complex_graph() {
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::new(1, 1, TropicalWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s2));
fst.add_arc(s2, Arc::new(3, 3, TropicalWeight::one(), s3));
fst.add_arc(s3, Arc::new(4, 4, TropicalWeight::one(), s1)); fst.add_arc(s2, Arc::new(5, 5, TropicalWeight::one(), s4));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 3);
}
#[test]
fn test_condense_all_connected() {
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::new(1, 1, TropicalWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s2));
fst.add_arc(s2, Arc::new(3, 3, TropicalWeight::one(), s0));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 1);
assert!(result.start().is_some());
assert!(result.final_weight(0).is_some());
}
#[test]
fn test_condense_with_boolean_weight() {
let mut fst = VectorFst::<BooleanWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, BooleanWeight::one());
fst.add_arc(s0, Arc::new(1, 1, BooleanWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, BooleanWeight::one(), s0));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 1);
}
#[test]
fn test_condense_combines_arc_weights() {
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::new(1, 1, TropicalWeight::new(2.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(3.0), s2));
fst.add_arc(s2, Arc::new(3, 3, TropicalWeight::new(1.0), s1)); fst.add_arc(s1, Arc::new(4, 4, TropicalWeight::new(5.0), s3));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 3);
}
#[test]
fn test_condense_no_start_state() {
let mut fst = VectorFst::<TropicalWeight>::new();
fst.add_state();
fst.add_state();
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 2);
assert_eq!(result.start(), None);
}
#[test]
fn test_condense_multiple_final_weights_in_scc() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, TropicalWeight::new(2.0));
fst.set_final(s1, TropicalWeight::new(3.0));
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::one(), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s0));
let result = condense(&fst).unwrap();
assert_eq!(result.num_states(), 1);
assert!(result.final_weight(0).is_some());
assert_eq!(result.final_weight(0), Some(&TropicalWeight::new(2.0)));
}
}