use crate::algorithms::arc_map::{FromGallicMapper, RmWeightMapper, ToGallicMapper, arc_map_to};
use crate::algorithms::factor_weight::{
FactorIterator, FactorWeightOptions, GallicFactor, factor_weight,
};
use crate::algorithms::reweight::{reweight_to_final, reweight_to_initial};
use crate::algorithms::shortest_distance::{shortest_distance_forward, shortest_distance_reverse};
use crate::arc::{Arc, ArcStateId, GallicArc};
use crate::error::OpenFstError;
use crate::fst::{Fst, MutableFst};
use crate::fsts::vector_fst::VectorFst;
use crate::weight::{Divide, DivideType, LeftSemiring, RightSemiring, Weight};
use crate::weights::string_weight::{
GallicLeft, GallicRight, GallicTypeMarker, GallicWeight, StringWeight,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReweightType {
ToInitial,
ToFinal,
}
pub const PUSH_WEIGHTS: u8 = 0x01;
pub const PUSH_LABELS: u8 = 0x02;
pub const PUSH_REMOVE_TOTAL_WEIGHT: u8 = 0x04;
pub const PUSH_REMOVE_COMMON_AFFIX: u8 = 0x08;
pub fn total_weight<A, F>(fst: &F, distance: &[A::Weight], reverse: bool) -> A::Weight
where
A: Arc,
F: Fst<A>,
{
if reverse {
return fst
.start()
.and_then(|start| distance.get(start.as_usize()).cloned())
.unwrap_or_else(A::Weight::zero);
}
let mut sum = A::Weight::zero();
for (index, weight) in distance.iter().enumerate() {
sum = sum.plus(&weight.times(&fst.final_weight(A::StateId::from_usize(index))));
}
sum
}
pub fn remove_weight<A, F>(fst: &mut F, weight: &A::Weight, at_final: bool)
where
A: Arc,
A::Weight: Divide,
F: MutableFst<A>,
{
if *weight == A::Weight::one() || *weight == A::Weight::zero() {
return;
}
if at_final {
let states: Vec<A::StateId> = fst.states().collect();
for state in states {
let divided = fst.final_weight(state).divide(weight, DivideType::Right);
fst.set_final(state, divided);
}
return;
}
let Some(start) = fst.start() else { return };
fst.mutate_arcs(start, |arc| {
let divided = arc.weight().divide(weight, DivideType::Left);
*arc = A::new(arc.ilabel(), arc.olabel(), divided, arc.nextstate());
});
let divided = fst.final_weight(start).divide(weight, DivideType::Left);
fst.set_final(start, divided);
}
pub fn push_weights<A, F>(
fst: &mut F,
to: ReweightType,
delta: f32,
remove_total_weight: bool,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + LeftSemiring + RightSemiring,
F: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
{
let reverse = to == ReweightType::ToInitial;
let distance = if reverse {
shortest_distance_reverse(fst, delta)?
} else {
shortest_distance_forward(fst, delta)?
};
let total = remove_total_weight.then(|| total_weight(fst, &distance, reverse));
match to {
ReweightType::ToInitial => reweight_to_initial(fst, &distance),
ReweightType::ToFinal => reweight_to_final(fst, &distance),
}
if let Some(total) = total {
remove_weight(fst, &total, !reverse);
}
Ok(())
}
pub fn push_weights_to_initial<A, F>(
fst: &mut F,
delta: f32,
remove_total_weight: bool,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + LeftSemiring,
F: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
{
let distance = shortest_distance_reverse(fst, delta)?;
let total = remove_total_weight.then(|| total_weight(fst, &distance, true));
reweight_to_initial(fst, &distance);
if let Some(total) = total {
remove_weight(fst, &total, false);
}
Ok(())
}
pub fn push_weights_to_final<A, F>(
fst: &mut F,
delta: f32,
remove_total_weight: bool,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + RightSemiring,
F: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
{
let distance = shortest_distance_forward(fst, delta)?;
let total = remove_total_weight.then(|| total_weight(fst, &distance, false));
reweight_to_final(fst, &distance);
if let Some(total) = total {
remove_weight(fst, &total, true);
}
Ok(())
}
pub fn push_to_initial<A, F1, F2>(
ifst: &F1,
ofst: &mut F2,
flags: u8,
delta: f32,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + LeftSemiring + std::hash::Hash + Eq,
F1: Fst<A>,
F2: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
GallicWeight<A::Label, A::Weight, GallicLeft>: Divide + LeftSemiring,
<GallicWeight<A::Label, A::Weight, GallicLeft> as Weight>::ReverseWeight:
Weight<ReverseWeight = GallicWeight<A::Label, A::Weight, GallicLeft>>,
{
push_impl::<A, GallicLeft, F1, F2>(
ifst,
ofst,
ReweightType::ToInitial,
flags,
delta,
reweight_to_initial,
reweight_to_initial,
)
}
pub fn push_to_final<A, F1, F2>(
ifst: &F1,
ofst: &mut F2,
flags: u8,
delta: f32,
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + RightSemiring + std::hash::Hash + Eq,
F1: Fst<A>,
F2: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
GallicWeight<A::Label, A::Weight, GallicRight>: Divide + RightSemiring,
<GallicWeight<A::Label, A::Weight, GallicRight> as Weight>::ReverseWeight:
Weight<ReverseWeight = GallicWeight<A::Label, A::Weight, GallicRight>>,
{
push_impl::<A, GallicRight, F1, F2>(
ifst,
ofst,
ReweightType::ToFinal,
flags,
delta,
reweight_to_final,
reweight_to_final,
)
}
#[allow(clippy::too_many_arguments)]
fn push_impl<A, G, F1, F2>(
ifst: &F1,
ofst: &mut F2,
to: ReweightType,
flags: u8,
delta: f32,
reweight_plain: fn(&mut F2, &[A::Weight]),
reweight_gallic: fn(&mut VectorFst<GallicArc<A, G>>, &[GallicWeight<A::Label, A::Weight, G>]),
) -> Result<(), OpenFstError>
where
A: Arc,
A::Weight: Divide + std::hash::Hash + Eq,
G: GallicTypeMarker,
F1: Fst<A>,
F2: MutableFst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
GallicWeight<A::Label, A::Weight, G>: Divide,
<GallicWeight<A::Label, A::Weight, G> as Weight>::ReverseWeight:
Weight<ReverseWeight = GallicWeight<A::Label, A::Weight, G>>,
{
let reverse = to == ReweightType::ToInitial;
if flags & PUSH_LABELS == 0 {
copy_fst(ifst, ofst);
if flags & PUSH_WEIGHTS != 0 {
let distance = distance_of(ofst, reverse, delta)?;
let total = (flags & PUSH_REMOVE_TOTAL_WEIGHT != 0)
.then(|| total_weight(ofst, &distance, reverse));
reweight_plain(ofst, &distance);
if let Some(total) = total {
remove_weight(ofst, &total, !reverse);
}
}
return Ok(());
}
let mut gfst: VectorFst<GallicArc<A, G>> = VectorFst::new();
arc_map_to(ifst, &mut gfst, &mut ToGallicMapper::<G>::new())?;
let gdistance = if flags & PUSH_WEIGHTS != 0 {
distance_of(&gfst, reverse, delta)?
} else {
let mut unweighted: VectorFst<A> = VectorFst::new();
arc_map_to(ifst, &mut unweighted, &mut RmWeightMapper)?;
let mut gunweighted: VectorFst<GallicArc<A, G>> = VectorFst::new();
arc_map_to(
&unweighted,
&mut gunweighted,
&mut ToGallicMapper::<G>::new(),
)?;
distance_of(&gunweighted, reverse, delta)?
};
let total = (flags & (PUSH_REMOVE_TOTAL_WEIGHT | PUSH_REMOVE_COMMON_AFFIX) != 0).then(|| {
let total = total_weight(&gfst, &gdistance, reverse);
GallicWeight::<A::Label, A::Weight, G>::from_parts(
if flags & PUSH_REMOVE_COMMON_AFFIX != 0 {
total.labels().clone()
} else {
StringWeight::one()
},
if flags & PUSH_REMOVE_TOTAL_WEIGHT != 0 {
total.weight().clone()
} else {
A::Weight::one()
},
)
});
reweight_gallic(&mut gfst, &gdistance);
if let Some(total) = total {
remove_weight(&mut gfst, &total, to == ReweightType::ToFinal);
}
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(
"Push: a weight came out that no single arc can carry".into(),
));
}
ofst.set_output_symbols(ifst.output_symbols());
Ok(())
}
fn distance_of<A, F>(fst: &F, reverse: bool, delta: f32) -> Result<Vec<A::Weight>, OpenFstError>
where
A: Arc,
F: Fst<A>,
<A::Weight as Weight>::ReverseWeight: Weight<ReverseWeight = A::Weight>,
{
if reverse {
shortest_distance_reverse(fst, delta)
} else {
shortest_distance_forward(fst, delta)
}
}
fn copy_fst<A, F1, F2>(ifst: &F1, ofst: &mut F2)
where
A: Arc,
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 mut nstates = 0usize;
for state in ifst.states() {
while nstates <= state.as_usize() {
ofst.add_state();
nstates += 1;
}
}
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);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::shortest_distance::SHORTEST_DELTA;
use crate::algorithms::test_support::{Rng, paths, random_acyclic_fst, sorted};
use crate::arc::StdArc;
use crate::fst::ExpandedFst as _;
use crate::fsts::vector_fst::StdVectorFst;
use crate::properties::K_FST_PROPERTIES;
use crate::weights::float_weight::TropicalWeight;
fn leaving(fst: &StdVectorFst, state: i32) -> TropicalWeight {
let mut sum = fst.final_weight(state);
for arc in fst.arcs(state) {
sum = sum.plus(arc.weight());
}
sum
}
fn branches() -> StdVectorFst {
let mut fst = StdVectorFst::new();
for _ in 0..4 {
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(3.0), 2));
fst.add_arc(1, StdArc::new(3, 3, TropicalWeight(2.0), 3));
fst.add_arc(2, StdArc::new(4, 4, TropicalWeight(2.0), 3));
fst.set_final(3, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
fst
}
fn pushed(to: ReweightType, remove_total: bool) -> StdVectorFst {
let mut fst = branches();
push_weights(&mut fst, to, SHORTEST_DELTA, remove_total).unwrap();
fst
}
#[test]
fn pushing_to_the_initial_state_leaves_one_behind_every_state() {
let fst = pushed(ReweightType::ToInitial, true);
for state in 1..fst.num_states() as i32 {
assert_eq!(leaving(&fst, state), TropicalWeight::one(), "state {state}");
}
let from_start: Vec<f32> = fst.arcs(0).map(|a| a.weight().value()).collect();
assert_eq!(from_start, vec![0.0, 2.0]);
}
#[test]
fn pushing_does_not_change_the_paths() {
let before = sorted(paths(&branches(), 12));
assert_eq!(
sorted(paths(&pushed(ReweightType::ToInitial, false), 12)),
before
);
assert_eq!(
sorted(paths(&pushed(ReweightType::ToFinal, false), 12)),
before
);
}
#[test]
fn removing_the_total_weight_makes_the_best_path_free() {
for to in [ReweightType::ToInitial, ReweightType::ToFinal] {
let fst = pushed(to, true);
let mut weights: Vec<f32> = paths(&fst, 12)
.into_iter()
.map(|(_, _, weight)| weight.value())
.collect();
weights.sort_by(f32::total_cmp);
assert_eq!(weights, vec![0.0, 2.0], "{to:?}");
}
}
#[test]
fn pushing_never_changes_what_a_path_costs_in_total() {
let mut rng = Rng::new(0x00F0_54ED_u64);
for round in 0..200 {
let fst = random_acyclic_fst(&mut rng, 6);
let before = sorted(paths(&fst, 12));
if before.is_empty() {
continue;
}
for to in [ReweightType::ToInitial, ReweightType::ToFinal] {
let mut copy = fst.clone();
push_weights(&mut copy, to, SHORTEST_DELTA, false).unwrap();
assert_eq!(sorted(paths(©, 12)), before, "round {round}, {to:?}");
}
}
}
#[test]
fn pushing_labels_moves_the_output_side_forward() {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 0, TropicalWeight::one(), 1));
fst.add_arc(1, StdArc::new(2, 7, TropicalWeight::one(), 2));
fst.set_final(2, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
let before = sorted(paths(&fst, 12));
let mut out = StdVectorFst::new();
push_to_initial(&fst, &mut out, PUSH_LABELS, SHORTEST_DELTA).unwrap();
let first: Vec<i32> = out.arcs(out.start().unwrap()).map(|a| a.olabel()).collect();
assert_eq!(first, vec![7]);
let visible =
|paths: Vec<(Vec<i32>, Vec<i32>, String)>| -> Vec<(Vec<i32>, Vec<i32>, String)> {
paths
.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(sorted(paths(&out, 12))), visible(before));
}
#[test]
fn pushing_labels_and_weights_keeps_the_transduction() {
let mut rng = Rng::new(0x001A_B315_u64);
let visible =
|paths: Vec<(Vec<i32>, Vec<i32>, String)>| -> Vec<(Vec<i32>, Vec<i32>, String)> {
paths
.into_iter()
.map(|(i, o, w)| {
(
i.into_iter().filter(|l| *l != 0).collect(),
o.into_iter().filter(|l| *l != 0).collect(),
w,
)
})
.collect()
};
for round in 0..100 {
let fst = random_acyclic_fst(&mut rng, 5);
let before = visible(sorted(paths(&fst, 12)));
if before.is_empty() {
continue;
}
let mut out = StdVectorFst::new();
push_to_initial(&fst, &mut out, PUSH_LABELS | PUSH_WEIGHTS, SHORTEST_DELTA).unwrap();
assert_eq!(visible(sorted(paths(&out, 12))), before, "round {round}");
}
}
#[test]
fn pushing_nothing_copies_the_fst() {
let fst = branches();
let mut out = StdVectorFst::new();
push_to_initial(&fst, &mut out, 0, SHORTEST_DELTA).unwrap();
assert_eq!(sorted(paths(&out, 12)), sorted(paths(&fst, 12)));
assert_eq!(out.num_states(), fst.num_states());
}
#[test]
fn the_total_weight_is_the_same_from_either_end() {
let fst = branches();
let forward = shortest_distance_forward(&fst, SHORTEST_DELTA).unwrap();
let backward = shortest_distance_reverse(&fst, SHORTEST_DELTA).unwrap();
assert_eq!(
total_weight(&fst, &forward, false),
total_weight(&fst, &backward, true)
);
assert_eq!(total_weight(&fst, &forward, false), TropicalWeight(3.0));
}
}