use crate::algorithms::arc_map::{FromGallicMapper, ToGallicMapper, arc_map_to};
use crate::algorithms::connect::connect;
use crate::algorithms::factor_weight::{
FactorIterator, FactorWeightOptions, GallicFactor, factor_weight,
};
use crate::algorithms::invert::invert;
use crate::algorithms::rmepsilon::rm_epsilon;
use crate::arc::{Arc, GallicArc};
use crate::error::OpenFstError;
use crate::fst::{ExpandedFst, Fst, MutableFst};
use crate::fsts::vector_fst::VectorFst;
use crate::properties::K_FST_PROPERTIES;
use crate::weight::{IdempotentWeight, Weight};
use crate::weights::string_weight::{GallicTypeMarker, GallicWeight};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EpsNormalizeType {
#[default]
Input,
Output,
}
pub fn eps_normalize<A, G, F1, F2>(
ifst: &F1,
ofst: &mut F2,
normalize_type: EpsNormalizeType,
_gallic: G,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: std::hash::Hash + Eq,
G: GallicTypeMarker,
F1: Fst<A> + ExpandedFst<A>,
F2: MutableFst<A> + ExpandedFst<A>,
GallicWeight<A::Label, A::Weight, G>: Weight + IdempotentWeight + std::hash::Hash + Eq,
{
let mut source: VectorFst<A> = VectorFst::new();
copy_into(ifst, &mut source);
if normalize_type == EpsNormalizeType::Output {
invert(&mut source)?;
}
let symbols = match normalize_type {
EpsNormalizeType::Input => ifst.output_symbols(),
EpsNormalizeType::Output => ifst.input_symbols(),
};
let mut gfst: VectorFst<GallicArc<A, G>> = VectorFst::new();
arc_map_to(&source, &mut gfst, &mut ToGallicMapper::<G>::new())?;
rm_epsilon(&mut gfst, false)?;
check_representable::<A, G>(&gfst)?;
connect(&mut gfst);
let mut factored: VectorFst<GallicArc<A, G>> = VectorFst::new();
factor_weight(
&gfst,
&mut factored,
GallicFactor::new,
&FactorWeightOptions::<A::Label>::default(),
);
let mut mapper = FromGallicMapper::<A::Label, G>::new();
arc_map_to(&factored, ofst, &mut mapper)?;
if mapper.error() {
return Err(OpenFstError::InvalidOperation(
"EpsNormalize: a weight came out that no single arc can carry".into(),
));
}
ofst.set_output_symbols(symbols);
if normalize_type == EpsNormalizeType::Output {
invert(ofst)?;
}
Ok(())
}
fn check_representable<A, G>(gfst: &VectorFst<GallicArc<A, G>>) -> Result<(), OpenFstError>
where
A: Arc,
G: GallicTypeMarker,
GallicWeight<A::Label, A::Weight, G>: Weight,
{
for state in gfst.states() {
if !gfst.final_weight(state).is_member() {
return Err(not_functional(state));
}
for arc in gfst.arcs(state) {
if !arc.weight().is_member() {
return Err(not_functional(state));
}
}
}
Ok(())
}
fn not_functional<S: std::fmt::Debug>(state: S) -> OpenFstError {
OpenFstError::InvalidOperation(format!(
"EpsNormalize: at state {state:?}, two epsilon paths give different outputs, which the \
gallic type chosen cannot combine. That takes the union gallic weight, which is not \
wired up yet; a functional transducer does not run into it."
))
}
fn copy_into<A, F1, F2>(ifst: &F1, ofst: &mut F2)
where
A: Arc,
F1: Fst<A> + ExpandedFst<A>,
F2: MutableFst<A>,
{
ofst.delete_all_states();
ofst.add_states(ifst.num_states());
ofst.set_input_symbols(ifst.input_symbols());
ofst.set_output_symbols(ifst.output_symbols());
if let Some(start) = ifst.start() {
ofst.set_start(start);
}
for state in ifst.states() {
ofst.set_final(state, ifst.final_weight(state));
for arc in ifst.arcs(state) {
ofst.add_arc(state, arc);
}
}
ofst.set_properties(ifst.properties(K_FST_PROPERTIES, false), K_FST_PROPERTIES);
}
#[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::fsts::vector_fst::StdVectorFst;
use crate::properties::K_FST_PROPERTIES as _KP;
use crate::weights::float_weight::TropicalWeight;
use crate::weights::string_weight::GallicRestrict;
fn normalized(fst: &StdVectorFst, which: EpsNormalizeType) -> StdVectorFst {
let mut out = StdVectorFst::new();
eps_normalize(fst, &mut out, which, GallicRestrict).unwrap();
out
}
fn language(fst: &StdVectorFst) -> Vec<(Vec<i32>, Vec<i32>, String)> {
string_weights(visible_paths(fst, 16))
}
fn is_normalized(fst: &StdVectorFst, which: EpsNormalizeType) -> bool {
let mut stack = vec![(fst.start(), false, 0usize)];
while let Some((state, seen_epsilon, depth)) = stack.pop() {
let Some(state) = state else { continue };
if depth > 16 {
continue;
}
for arc in fst.arcs(state) {
let label = match which {
EpsNormalizeType::Input => arc.ilabel(),
EpsNormalizeType::Output => arc.olabel(),
};
if label == 0 {
stack.push((Some(arc.nextstate()), true, depth + 1));
} else {
if seen_epsilon {
return false;
}
stack.push((Some(arc.nextstate()), false, depth + 1));
}
}
}
true
}
#[test]
fn an_input_epsilon_is_pushed_past_what_follows_it() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(0, 7, TropicalWeight::one(), 1));
fst.add_arc(1, StdArc::new(2, 8, TropicalWeight::one(), 2));
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
assert!(!is_normalized(&fst, EpsNormalizeType::Input));
let out = normalized(&fst, EpsNormalizeType::Input);
assert!(is_normalized(&out, EpsNormalizeType::Input));
assert_eq!(language(&out), language(&fst));
}
#[test]
fn the_output_side_can_be_normalized() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(7, 0, TropicalWeight::one(), 1));
fst.add_arc(1, StdArc::new(8, 2, TropicalWeight::one(), 2));
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
assert!(!is_normalized(&fst, EpsNormalizeType::Output));
let out = normalized(&fst, EpsNormalizeType::Output);
assert!(is_normalized(&out, EpsNormalizeType::Output));
assert_eq!(language(&out), language(&fst));
}
#[test]
fn an_already_normalized_fst_is_unchanged_in_what_it_says() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 7, TropicalWeight(1.0), 1));
fst.add_arc(1, StdArc::new(0, 8, TropicalWeight(2.0), 2));
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
assert!(is_normalized(&fst, EpsNormalizeType::Input));
let out = normalized(&fst, EpsNormalizeType::Input);
assert!(is_normalized(&out, EpsNormalizeType::Input));
assert_eq!(language(&out), language(&fst));
}
#[test]
fn normalizing_keeps_the_transduction_and_normalizes() {
let mut rng = Rng::new(0x00E7_5000_u64);
let mut checked = 0;
let mut refused = 0;
for round in 0..100 {
let mut fst = random_acyclic_fst(&mut rng, 5);
let states: Vec<i32> = fst.states().collect();
for state in states {
fst.mutate_arcs(state, |arc| {
let ilabel = if arc.ilabel() % 3 == 1 {
0
} else {
arc.ilabel()
};
*arc = StdArc::new(ilabel, arc.ilabel() + 10, *arc.weight(), arc.nextstate());
});
}
fst.properties(K_FST_PROPERTIES, true);
if language(&fst).is_empty() {
continue;
}
checked += 1;
let mut out = StdVectorFst::new();
match eps_normalize(&fst, &mut out, EpsNormalizeType::Input, GallicRestrict) {
Ok(()) => {}
Err(err) => {
assert!(format!("{err}").contains("different outputs"), "{err}");
refused += 1;
continue;
}
}
assert!(
is_normalized(&out, EpsNormalizeType::Input),
"round {round}"
);
assert_eq!(language(&out), language(&fst), "round {round}");
}
assert!(
checked > 30,
"only {checked} FSTs said anything ({refused} refused)"
);
}
#[test]
fn an_empty_fst_normalizes_to_nothing() {
let out = normalized(&StdVectorFst::new(), EpsNormalizeType::Input);
assert_eq!(out.num_states(), 0);
}
#[test]
fn a_non_functional_transducer_is_refused_by_name() {
let mut fst = StdVectorFst::new();
for _ in 0..2 {
fst.add_state();
}
fst.set_start(0);
fst.set_final(0, TropicalWeight::one());
fst.add_arc(0, StdArc::new(0, 11, TropicalWeight(2.0), 1));
fst.set_final(1, TropicalWeight::one());
fst.properties(_KP, true);
assert_eq!(language(&fst).len(), 2);
let mut out = StdVectorFst::new();
let err =
eps_normalize(&fst, &mut out, EpsNormalizeType::Input, GallicRestrict).unwrap_err();
assert!(format!("{err}").contains("different outputs"), "{err}");
}
}