use crate::arc::Arc;
use crate::fst::{Fst, Label, MutableFst, StateId};
use crate::semiring::{DivisibleSemiring, Semiring};
use crate::{Error, Result};
use core::hash::Hash;
use rustc_hash::FxHashMap;
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq)]
struct WeightedSubset<W: Semiring> {
states: BTreeMap<StateId, W>,
}
impl<W: Semiring> Eq for WeightedSubset<W> where W: Eq {}
impl<W: Semiring> std::hash::Hash for WeightedSubset<W>
where
W: std::hash::Hash,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.states.hash(state);
}
}
impl<W: Semiring> WeightedSubset<W> {
fn new() -> Self {
Self {
states: BTreeMap::new(),
}
}
fn insert(&mut self, state: StateId, weight: W) {
self.states
.entry(state)
.and_modify(|w| w.plus_assign(&weight))
.or_insert(weight);
}
fn normalize(&mut self) -> Option<W>
where
W: DivisibleSemiring + Ord,
{
if self.states.is_empty() {
return None;
}
let min_weight = self.states.values().min()?.clone();
if <W as num_traits::Zero>::is_zero(&min_weight) {
return None;
}
for weight in self.states.values_mut() {
match weight.divide(&min_weight) {
Some(normalized) => *weight = normalized,
None => {
return None;
}
}
}
Some(min_weight)
}
}
pub fn determinize<W, F, M>(fst: &F) -> Result<M>
where
W: DivisibleSemiring + Hash + Eq + Ord,
F: Fst<W>,
M: MutableFst<W> + Default,
{
let start = fst
.start()
.ok_or_else(|| Error::Algorithm("FST has no start state".into()))?;
let mut result = M::default();
let mut subset_map = FxHashMap::default();
let mut queue = Vec::new();
let mut start_subset = WeightedSubset::new();
start_subset.insert(start, W::one());
let start_new = result.add_state();
result.set_start(start_new);
subset_map.insert(start_subset.clone(), start_new);
queue.push((start_subset, start_new));
while let Some((subset, current_state)) = queue.pop() {
let mut transitions: FxHashMap<(Label, Label), WeightedSubset<W>> = FxHashMap::default();
let mut final_weight = W::zero();
for (&state, weight) in &subset.states {
if let Some(fw) = fst.final_weight(state) {
final_weight.plus_assign(&weight.times(fw));
}
for arc in fst.arcs(state) {
let next_weight = weight.times(&arc.weight);
transitions
.entry((arc.ilabel, arc.olabel))
.or_insert_with(WeightedSubset::new)
.insert(arc.nextstate, next_weight);
}
}
if !<W as num_traits::Zero>::is_zero(&final_weight) {
result.set_final(current_state, final_weight);
}
for ((ilabel, olabel), mut next_subset) in transitions {
if let Some(norm_weight) = next_subset.normalize() {
let next_state = match subset_map.get(&next_subset) {
Some(&state) => state,
None => {
let state = result.add_state();
subset_map.insert(next_subset.clone(), state);
queue.push((next_subset, state));
state
}
};
result.add_arc(
current_state,
Arc::new(ilabel, olabel, norm_weight, next_state),
);
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use num_traits::One;
#[test]
fn test_determinize_simple() {
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(s0, Arc::new(1, 1, TropicalWeight::new(2.0), s2));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s2));
let det: VectorFst<TropicalWeight> = determinize(&fst).unwrap();
for state in det.states() {
let mut seen_labels = std::collections::HashSet::new();
for arc in det.arcs(state) {
assert!(
seen_labels.insert(arc.ilabel),
"Found duplicate input label {} from state {}",
arc.ilabel,
state
);
}
}
assert!(det.start().is_some());
assert!(det.num_states() > 0);
}
#[test]
fn test_determinize_already_deterministic() {
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::new(1.0), s1));
let det: VectorFst<TropicalWeight> = determinize(&fst).unwrap();
assert_eq!(det.num_states(), fst.num_states());
assert!(det.start().is_some());
for state in det.states() {
let mut seen_labels = std::collections::HashSet::new();
for arc in det.arcs(state) {
assert!(seen_labels.insert(arc.ilabel));
}
}
}
#[test]
fn test_determinize_normalization_all_infinity() {
let mut subset = WeightedSubset::<TropicalWeight>::new();
let zero = TropicalWeight::zero(); subset.insert(0, zero);
subset.insert(1, zero);
subset.insert(2, zero);
let result = subset.normalize();
assert!(
result.is_none(),
"Normalization should return None for all-infinity weights"
);
}
#[test]
fn test_determinize_normalization_mixed_weights() {
let mut subset = WeightedSubset::<TropicalWeight>::new();
subset.insert(0, TropicalWeight::new(5.0));
subset.insert(1, TropicalWeight::new(3.0));
subset.insert(2, TropicalWeight::zero());
let result = subset.normalize();
assert!(
result.is_some(),
"Normalization should work with finite minimum"
);
if let Some(norm_weight) = result {
assert_eq!(norm_weight, TropicalWeight::new(3.0));
}
}
#[test]
fn test_determinize_normalization_empty_subset() {
let mut subset = WeightedSubset::<TropicalWeight>::new();
let result = subset.normalize();
assert!(
result.is_none(),
"Normalization should return None for empty subset"
);
}
#[test]
fn test_determinize_with_infinity_weights() {
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(s0, Arc::new(1, 1, TropicalWeight::zero(), s2));
let det: VectorFst<TropicalWeight> = determinize(&fst).unwrap();
assert!(det.start().is_some());
for state in det.states() {
let mut seen_labels = std::collections::HashSet::new();
for arc in det.arcs(state) {
assert!(
seen_labels.insert(arc.ilabel),
"Found duplicate input label"
);
}
}
}
#[test]
fn test_determinize_transducer() {
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(b'a' as u32, b'x' as u32, TropicalWeight::new(1.0), s1),
);
fst.add_arc(
s0,
Arc::new(b'a' as u32, b'y' as u32, TropicalWeight::new(2.0), s2),
);
fst.add_arc(
s1,
Arc::new(b'b' as u32, b'z' as u32, TropicalWeight::one(), s3),
);
fst.add_arc(
s2,
Arc::new(b'b' as u32, b'z' as u32, TropicalWeight::one(), s3),
);
let det: VectorFst<TropicalWeight> = determinize(&fst).unwrap();
if let Some(start) = det.start() {
let arcs: Vec<_> = det.arcs(start).collect();
assert_eq!(arcs.len(), 2, "Should preserve both output labels");
let olabels: std::collections::HashSet<_> = arcs.iter().map(|a| a.olabel).collect();
assert!(
olabels.contains(&(b'x' as u32)),
"Should preserve output label 'x'"
);
assert!(
olabels.contains(&(b'y' as u32)),
"Should preserve output label 'y'"
);
for arc in &arcs {
assert_eq!(arc.ilabel, b'a' as u32);
}
}
}
}