use crate::algorithms::arc_map::{FromGallicMapper, ToGallicMapper, arc_map_to};
use crate::algorithms::factor_weight::{
FactorIterator, FactorMode, FactorWeightOptions, GallicFactor, factor_weight,
};
use crate::arc::{Arc, ArcLabel, ArcStateId, GallicArc};
use crate::data_structures::bi_table::CompactHashBiTable;
use crate::error::OpenFstError;
use crate::fst::{Fst, MutableFst};
use crate::fsts::vector_fst::VectorFst;
use crate::properties::{K_ACCEPTOR, K_FST_PROPERTIES, determinize_properties};
use crate::weight::{Divide, DivideType, Weight};
use crate::weights::string_weight::{
GallicRestrict, GallicTypeMarker, GallicWeight, StringTypeMarker, StringWeight,
StringWeightValue,
};
pub const DELTA: f32 = 1.0 / 1024.0;
pub trait CommonDivisor<W: Weight> {
fn divisor(&self, w1: &W, w2: &W) -> W;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultCommonDivisor;
impl<W: Weight> CommonDivisor<W> for DefaultCommonDivisor {
#[inline]
fn divisor(&self, w1: &W, w2: &W) -> W {
w1.plus(w2)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LabelCommonDivisor;
impl<L: ArcLabel, S: StringTypeMarker> CommonDivisor<StringWeight<L, S>> for LabelCommonDivisor {
fn divisor(&self, w1: &StringWeight<L, S>, w2: &StringWeight<L, S>) -> StringWeight<L, S> {
let first = |w: &StringWeight<L, S>| match &w.value {
StringWeightValue::Labels(labels) => labels.first().copied(),
_ => None,
};
let zero = StringWeight::<L, S>::zero();
match (w1 == &zero, w2 == &zero) {
(true, true) => return StringWeight::one(),
(true, false) => {
return first(w2).map_or_else(StringWeight::one, |l| StringWeight::new(vec![l]));
}
(false, true) => {
return first(w1).map_or_else(StringWeight::one, |l| StringWeight::new(vec![l]));
}
(false, false) => {}
}
if w1.size() == 0 || w2.size() == 0 {
return StringWeight::one();
}
match (first(w1), first(w2)) {
(Some(a), Some(b)) if a == b => StringWeight::new(vec![a]),
_ => StringWeight::one(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GallicCommonDivisor<D> {
pub weights: D,
}
impl<L, W, G, D> CommonDivisor<GallicWeight<L, W, G>> for GallicCommonDivisor<D>
where
L: ArcLabel,
W: Weight,
G: GallicTypeMarker,
D: CommonDivisor<W>,
GallicWeight<L, W, G>: Weight,
{
fn divisor(
&self,
w1: &GallicWeight<L, W, G>,
w2: &GallicWeight<L, W, G>,
) -> GallicWeight<L, W, G> {
GallicWeight::from_parts(
LabelCommonDivisor.divisor(w1.labels(), w2.labels()),
self.weights.divisor(w1.weight(), w2.weight()),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DeterminizeType {
#[default]
Functional,
NonFunctional,
Disambiguate,
}
#[derive(Debug, Clone)]
pub struct DeterminizeOptions<L> {
pub delta: f32,
pub subsequential_label: L,
pub increment_subsequential_label: bool,
pub det_type: DeterminizeType,
pub max_states: Option<usize>,
}
impl<L: ArcLabel> Default for DeterminizeOptions<L> {
fn default() -> Self {
Self {
delta: DELTA,
subsequential_label: L::epsilon(),
increment_subsequential_label: false,
det_type: DeterminizeType::Functional,
max_states: None,
}
}
}
type Subset<S, W> = Vec<(S, W)>;
struct Distances<'a, W> {
to_final: &'a [W],
out: &'a mut Vec<W>,
}
fn subset_distance<S: ArcStateId, W: Weight>(subset: &Subset<S, W>, to_final: &[W]) -> W {
let mut out = W::zero();
for (state, weight) in subset {
let ind = to_final
.get(state.as_usize())
.cloned()
.unwrap_or_else(W::zero);
out = out.plus(&weight.times(&ind));
}
out
}
fn find_state<S, W>(
subsets: &mut CompactHashBiTable<usize, Subset<S, W>>,
pending: &mut Vec<usize>,
distances: &mut Option<Distances<'_, W>>,
subset: &Subset<S, W>,
) -> usize
where
S: ArcStateId,
W: Weight + std::hash::Hash + Eq,
{
let before = subsets.size();
let id = subsets
.find_id(subset, true)
.expect("find_id inserts when asked to");
if id == before {
pending.push(id);
if let Some(distances) = distances.as_mut() {
let distance = subset_distance(subset, distances.to_final);
distances.out.push(distance);
}
}
id
}
pub fn determinize_fsa<A, D, F1, F2>(
ifst: &F1,
ofst: &mut F2,
divisor: &D,
delta: f32,
max_states: Option<usize>,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + std::hash::Hash + Eq,
D: CommonDivisor<A::Weight>,
F1: Fst<A>,
F2: MutableFst<A>,
{
determinize_fsa_impl(ifst, ofst, divisor, delta, max_states, None)
}
pub fn determinize_fsa_with_distance<A, D, F1, F2>(
ifst: &F1,
ofst: &mut F2,
divisor: &D,
delta: f32,
max_states: Option<usize>,
in_dist: &[A::Weight],
out_dist: &mut Vec<A::Weight>,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + std::hash::Hash + Eq,
D: CommonDivisor<A::Weight>,
F1: Fst<A>,
F2: MutableFst<A>,
{
out_dist.clear();
determinize_fsa_impl(
ifst,
ofst,
divisor,
delta,
max_states,
Some(Distances {
to_final: in_dist,
out: out_dist,
}),
)
}
fn determinize_fsa_impl<A, D, F1, F2>(
ifst: &F1,
ofst: &mut F2,
divisor: &D,
delta: f32,
max_states: Option<usize>,
mut distances: Option<Distances<'_, A::Weight>>,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + std::hash::Hash + Eq,
D: CommonDivisor<A::Weight>,
F1: Fst<A>,
F2: MutableFst<A>,
{
ofst.delete_all_states();
ofst.set_input_symbols(ifst.input_symbols());
ofst.set_output_symbols(ifst.output_symbols());
let iprops = ifst.properties(K_FST_PROPERTIES, false);
let Some(istart) = ifst.start() else {
ofst.set_properties(
determinize_properties(iprops, true, false),
K_FST_PROPERTIES,
);
return Ok(());
};
let mut subsets: CompactHashBiTable<usize, Subset<A::StateId, A::Weight>> =
CompactHashBiTable::new(1024);
let mut pending: Vec<usize> = Vec::new();
let mut current: Subset<A::StateId, A::Weight> = Vec::new();
let mut transitions: Vec<(A::Label, A::StateId, A::Weight)> = Vec::new();
let initial: Subset<A::StateId, A::Weight> = vec![(istart, A::Weight::one())];
let start = find_state(&mut subsets, &mut pending, &mut distances, &initial);
ofst.add_state();
ofst.set_start(A::StateId::from_usize(start));
let zero = A::Weight::zero();
while let Some(id) = pending.pop() {
current.clear();
current.extend_from_slice(subsets.find_entry(id).expect("just added"));
let subset = ¤t;
let state = A::StateId::from_usize(id);
let mut final_weight = zero.clone();
for (member, weight) in subset {
final_weight = final_weight.plus(&weight.times(&ifst.final_weight(*member)));
}
if final_weight != zero {
if !final_weight.is_member() {
return Err(OpenFstError::InvalidOperation(
"Determinize: a subset's final weight left the semiring".into(),
));
}
ofst.set_final(state, final_weight);
}
transitions.clear();
for (member, weight) in subset {
for arc in ifst.arcs(*member) {
transitions.push((arc.ilabel(), arc.nextstate(), weight.times(arc.weight())));
}
}
transitions.sort_by_key(|(label, member, _)| (*label, *member));
let mut begin = 0;
while begin < transitions.len() {
let label = transitions[begin].0;
let mut end = begin + 1;
while end < transitions.len() && transitions[end].0 == label {
end += 1;
}
let destinations = &transitions[begin..end];
begin = end;
let mut arc_weight = zero.clone();
for (_, _, weight) in destinations {
arc_weight = divisor.divisor(&arc_weight, weight);
}
let mut merged: Subset<A::StateId, A::Weight> = Vec::with_capacity(destinations.len());
for (_, member, weight) in destinations.iter().cloned() {
match merged.last_mut() {
Some((last, at)) if *last == member => {
*at = at.plus(&weight);
if !at.is_member() {
return Err(OpenFstError::InvalidOperation(
"Determinize: a subset weight left the semiring".into(),
));
}
}
_ => merged.push((member, weight)),
}
}
for (_, weight) in &mut merged {
*weight = weight.divide(&arc_weight, DivideType::Left).quantize(delta);
}
let next = find_state(&mut subsets, &mut pending, &mut distances, &merged);
if max_states.is_some_and(|limit| subsets.size() > limit) {
return Err(OpenFstError::InvalidOperation(format!(
"Determinize: more than {} states; the FST may not be determinizable",
max_states.expect("just checked")
)));
}
while ofst.num_states() < subsets.size() {
ofst.add_state();
}
ofst.add_arc(
state,
A::new(label, label, arc_weight, A::StateId::from_usize(next)),
);
}
}
ofst.set_properties(
determinize_properties(iprops, true, false),
K_FST_PROPERTIES,
);
Ok(())
}
pub fn determinize<A, F1, F2>(
ifst: &F1,
ofst: &mut F2,
opts: &DeterminizeOptions<A::Label>,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + std::hash::Hash + Eq,
F1: Fst<A>,
F2: MutableFst<A>,
GallicWeight<A::Label, A::Weight, GallicRestrict>: Weight + Divide,
{
match opts.det_type {
DeterminizeType::Functional => {}
other => {
return Err(OpenFstError::InvalidOperation(format!(
"Determinize: the {other:?} reading is not implemented"
)));
}
}
if ifst.properties(K_ACCEPTOR, true) & K_ACCEPTOR != 0 {
return determinize_fsa(
ifst,
ofst,
&DefaultCommonDivisor,
opts.delta,
opts.max_states,
);
}
type G = GallicRestrict;
let mut gfst: VectorFst<GallicArc<A, G>> = VectorFst::new();
arc_map_to(ifst, &mut gfst, &mut ToGallicMapper::<G>::new())?;
let mut determinized: VectorFst<GallicArc<A, G>> = VectorFst::new();
determinize_fsa(
&gfst,
&mut determinized,
&GallicCommonDivisor {
weights: DefaultCommonDivisor,
},
opts.delta,
opts.max_states,
)?;
let mut factored: VectorFst<GallicArc<A, G>> = VectorFst::new();
factor_weight(
&determinized,
&mut factored,
GallicFactor::new,
&FactorWeightOptions {
delta: opts.delta,
mode: FactorMode::FINAL_WEIGHTS,
final_ilabel: opts.subsequential_label,
final_olabel: opts.subsequential_label,
increment_final_ilabel: opts.increment_subsequential_label,
increment_final_olabel: opts.increment_subsequential_label,
},
);
let mut mapper =
FromGallicMapper::<A::Label, G>::with_superfinal_label(opts.subsequential_label);
arc_map_to(&factored, ofst, &mut mapper)?;
if mapper.error() {
return Err(OpenFstError::InvalidOperation(
"Determinize: a weight came out that no single arc can carry".into(),
));
}
ofst.set_input_symbols(ifst.input_symbols());
ofst.set_output_symbols(ifst.output_symbols());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::test_support::{
Rng, paths, random_acyclic_fst, sorted, string_weights, visible_paths,
};
use crate::arc::StdArc;
use crate::fst::ExpandedFst as _;
use crate::fst::MutableFst;
use crate::fsts::vector_fst::StdVectorFst;
use crate::weights::float_weight::TropicalWeight;
fn is_deterministic(fst: &StdVectorFst) -> bool {
fst.states().all(|state| {
let mut seen: Vec<i32> = fst.arcs(state).map(|arc| arc.ilabel()).collect();
seen.sort_unstable();
let before = seen.len();
seen.dedup();
seen.len() == before
})
}
fn determinized(fst: &StdVectorFst) -> StdVectorFst {
let mut out = StdVectorFst::new();
determinize(
fst,
&mut out,
&DeterminizeOptions {
max_states: Some(4096),
..Default::default()
},
)
.unwrap();
out
}
#[test]
fn two_arcs_with_the_same_label_become_one() {
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(2.0), 1));
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight(5.0), 2));
fst.set_final(1, TropicalWeight(1.0));
fst.set_final(2, TropicalWeight(1.0));
fst.properties(K_FST_PROPERTIES, true);
let out = determinized(&fst);
assert!(is_deterministic(&out));
assert_eq!(out.num_arcs(out.start().unwrap()), 1);
assert_eq!(
sorted(paths(&out, 8)),
vec![(vec![1], vec![1], "3.0000".to_string())],
"the lighter of 2 + 1 and 5 + 1"
);
}
#[test]
fn the_weight_a_subset_cannot_commit_travels_with_it() {
let mut fst = StdVectorFst::new();
for _ in 0..5 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight(1.0), 1));
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight(3.0), 2));
fst.add_arc(1, StdArc::new(2, 2, TropicalWeight::one(), 3));
fst.add_arc(2, StdArc::new(3, 3, TropicalWeight::one(), 4));
fst.set_final(3, TropicalWeight::one());
fst.set_final(4, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
let out = determinized(&fst);
assert!(is_deterministic(&out));
let first: Vec<f32> = out
.arcs(out.start().unwrap())
.map(|arc| arc.weight().value())
.collect();
assert_eq!(first, vec![1.0]);
assert_eq!(
sorted(paths(&out, 8)),
vec![
(vec![1, 2], vec![1, 2], "1.0000".to_string()),
(vec![1, 3], vec![1, 3], "3.0000".to_string()),
]
);
}
#[test]
fn an_already_deterministic_fst_is_unchanged_in_what_it_accepts() {
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(0, StdArc::new(2, 2, TropicalWeight(2.0), 2));
fst.set_final(1, TropicalWeight::one());
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
assert_eq!(
sorted(paths(&determinized(&fst), 8)),
sorted(paths(&fst, 8))
);
}
#[test]
fn an_empty_fst_determinizes_to_an_empty_one() {
let out = determinized(&StdVectorFst::new());
assert_eq!(out.num_states(), 0);
}
#[test]
fn determinizing_keeps_the_language_and_makes_it_deterministic() {
let mut rng = Rng::new(0x0DE7_E401_u64);
let mut checked = 0;
for round in 0..200 {
let fst = random_acyclic_fst(&mut rng, 6);
let before = string_weights(paths(&fst, 12));
if before.is_empty() {
continue;
}
checked += 1;
let out = determinized(&fst);
assert!(is_deterministic(&out), "round {round}");
assert_eq!(string_weights(paths(&out, 12)), before, "round {round}");
}
assert!(checked > 50, "only {checked} FSTs accepted anything");
}
#[test]
fn a_transducer_is_determinized_on_its_input_side() {
let mut fst = StdVectorFst::new();
for _ in 0..5 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 7, TropicalWeight::one(), 1));
fst.add_arc(0, StdArc::new(1, 8, TropicalWeight::one(), 2));
fst.add_arc(1, StdArc::new(2, 9, TropicalWeight::one(), 3));
fst.add_arc(2, StdArc::new(3, 9, TropicalWeight::one(), 4));
fst.set_final(3, TropicalWeight::one());
fst.set_final(4, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
let out = determinized(&fst);
assert!(is_deterministic(&out), "the input side is deterministic");
let visible = |fst: &StdVectorFst| -> Vec<(Vec<i32>, Vec<i32>, String)> {
sorted(paths(fst, 12))
.into_iter()
.map(|(i, o, w)| {
(
i.into_iter().filter(|l| *l != 0).collect(),
o.into_iter().filter(|l| *l != 0).collect(),
w,
)
})
.collect()
};
assert_eq!(visible(&out), visible(&fst));
}
#[test]
fn determinizing_a_transducer_keeps_the_transduction() {
let mut rng = Rng::new(0x0000_7A11_u64);
let visible = |fst: &StdVectorFst| string_weights(visible_paths(fst, 14));
let mut checked = 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| {
*arc = StdArc::new(
arc.ilabel(),
arc.ilabel() + 10,
*arc.weight(),
arc.nextstate(),
);
});
}
fst.properties(K_FST_PROPERTIES, true);
let before = visible(&fst);
if before.is_empty() {
continue;
}
let mut inputs: Vec<Vec<i32>> = before.iter().map(|(i, _, _)| i.clone()).collect();
inputs.sort();
let unique = inputs.len();
inputs.dedup();
if inputs.len() != unique {
continue;
}
checked += 1;
assert_eq!(visible(&determinized(&fst)), before, "round {round}");
}
assert!(checked > 20, "only {checked} FSTs were functional");
}
#[test]
fn an_undeterminizable_fst_is_refused_when_a_limit_is_given() {
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(0, StdArc::new(1, 1, TropicalWeight(2.0), 2));
fst.add_arc(1, StdArc::new(2, 2, TropicalWeight(1.0), 1));
fst.add_arc(2, StdArc::new(2, 2, TropicalWeight(2.0), 2));
fst.set_final(1, TropicalWeight::one());
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
let mut out = StdVectorFst::new();
let err = determinize(
&fst,
&mut out,
&DeterminizeOptions {
max_states: Some(64),
..Default::default()
},
)
.unwrap_err();
assert!(format!("{err}").contains("determinizable"), "{err}");
}
#[test]
fn an_unimplemented_reading_is_refused() {
let fst = StdVectorFst::new();
for det_type in [
DeterminizeType::NonFunctional,
DeterminizeType::Disambiguate,
] {
let mut out = StdVectorFst::new();
let err = determinize(
&fst,
&mut out,
&DeterminizeOptions {
det_type,
..Default::default()
},
)
.unwrap_err();
assert!(format!("{err}").contains("not implemented"), "{err}");
}
}
#[test]
fn the_distances_it_reports_are_the_result_s_own() {
use crate::algorithms::shortest_distance::shortest_distance_reverse;
let mut rng = Rng::new(0x0000_D157_u64);
let mut checked = 0;
for round in 0..200 {
let fst = random_acyclic_fst(&mut rng, 6);
let in_dist = shortest_distance_reverse::<StdArc, _>(&fst, DELTA).unwrap();
let mut out = StdVectorFst::new();
let mut out_dist = Vec::new();
determinize_fsa_with_distance(
&fst,
&mut out,
&DefaultCommonDivisor,
DELTA,
Some(4096),
&in_dist,
&mut out_dist,
)
.unwrap();
assert_eq!(
out_dist.len(),
out.num_states(),
"round {round}: one distance per state"
);
if out.num_states() == 0 {
continue;
}
checked += 1;
let want = shortest_distance_reverse::<StdArc, _>(&out, DELTA).unwrap();
for (state, got) in out_dist.iter().enumerate() {
let want = want
.get(state)
.cloned()
.unwrap_or_else(TropicalWeight::zero);
assert!(
got.approx_equal(&want, 1e-3),
"round {round}, state {state}: {got} against {want}"
);
}
}
assert!(checked > 50, "only {checked} FSTs had any states");
}
#[test]
fn an_empty_input_reports_no_distances() {
let mut out = StdVectorFst::new();
let mut out_dist = vec![TropicalWeight(7.0)];
determinize_fsa_with_distance(
&StdVectorFst::new(),
&mut out,
&DefaultCommonDivisor,
DELTA,
None,
&[],
&mut out_dist,
)
.unwrap();
assert!(
out_dist.is_empty(),
"the caller's vector is not appended to"
);
}
#[test]
fn the_label_divisor_commits_only_what_both_agree_on() {
type S = StringWeight<i32, crate::weights::string_weight::StringLeft>;
let divisor = LabelCommonDivisor;
assert_eq!(
divisor.divisor(&S::new(vec![1, 2]), &S::new(vec![1, 3])),
S::new(vec![1]),
"they agree on the first letter"
);
assert_eq!(
divisor.divisor(&S::new(vec![1, 2]), &S::new(vec![4, 5])),
S::one(),
"they agree on nothing"
);
assert_eq!(
divisor.divisor(&S::zero(), &S::new(vec![7])),
S::new(vec![7]),
"zero contributes nothing, so the other stands"
);
assert_eq!(
divisor.divisor(&S::one(), &S::new(vec![7])),
S::one(),
"an empty sequence has no letter to agree on"
);
}
}