use sicada::algorithms::shortest_path::{ShortestPathOptions, shortest_path};
use sicada::arc::{Arc, ArcLabel, ArcStateId, ArcTpl};
use sicada::error::OpenFstError;
use sicada::fst::{ExpandedFst, Fst, MutableFst};
use sicada::fsts::vector_fst::VectorFst;
use sicada::weight::Weight;
use crate::compact_lattice_weight::CompactLatticeWeight;
use crate::lattice_weight::LatticeWeight;
#[derive(Debug, Clone, PartialEq)]
pub struct Hypothesis<L: ArcLabel> {
pub words: Vec<L>,
pub weight: CompactLatticeWeight<L>,
}
impl<L: ArcLabel> Hypothesis<L> {
#[inline]
pub fn alignment(&self) -> &[L] {
self.weight.alignment()
}
#[inline]
pub fn cost(&self) -> f32 {
self.weight.weight().total()
}
}
pub fn n_best<L, S>(
lattice: &VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>,
n: usize,
) -> Result<Vec<Hypothesis<L>>, OpenFstError>
where
L: ArcLabel,
S: ArcStateId,
{
if n == 0 || lattice.start().is_none() {
return Ok(Vec::new());
}
let mut best: VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>> = VectorFst::new();
shortest_path(
lattice,
&mut best,
&ShortestPathOptions {
nshortest: n,
..ShortestPathOptions::default()
},
)?;
let mut found = enumerate(&best);
found.sort_by(|a, b| a.cost().total_cmp(&b.cost()));
found.truncate(n);
Ok(found)
}
fn enumerate<L, S>(fst: &VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>) -> Vec<Hypothesis<L>>
where
L: ArcLabel,
S: ArcStateId,
{
let mut found = Vec::new();
let Some(start) = fst.start() else {
return found;
};
let zero = CompactLatticeWeight::<L>::zero();
let mut stack = vec![(start, Vec::new(), CompactLatticeWeight::<L>::one())];
while let Some((state, words, weight)) = stack.pop() {
let final_weight = fst.final_weight(state);
if final_weight.is_member() && final_weight != zero {
found.push(Hypothesis {
words: words.clone(),
weight: weight.times(&final_weight),
});
}
for arc in fst.arcs(state) {
let mut next = words.clone();
if arc.olabel() != L::epsilon() {
next.push(arc.olabel());
}
stack.push((arc.nextstate(), next, weight.times(arc.weight())));
}
}
found
}
pub fn scale<L, S>(
lattice: &mut VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>,
acoustic: f32,
graph: f32,
) where
L: ArcLabel,
S: ArcStateId,
{
let rescale = |weight: &CompactLatticeWeight<L>| {
CompactLatticeWeight::new(
LatticeWeight::new(
graph * weight.weight().graph,
acoustic * weight.weight().acoustic,
),
weight.alignment().iter().copied().collect(),
)
};
for state in 0..lattice.num_states() {
let state = S::from_usize(state);
let final_weight = lattice.final_weight(state);
if final_weight.is_member() && final_weight != CompactLatticeWeight::zero() {
lattice.set_final(state, rescale(&final_weight));
}
for arc in lattice.arcs_mut(state) {
arc.weight = rescale(&arc.weight);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sicada::arc::StdArc;
use sicada::fsts::vector_fst::StdVectorFst;
use sicada::properties::K_FST_PROPERTIES;
use sicada::weights::float_weight::TropicalWeight;
use crate::compact::{DeterminizeLatticeOptions, determinize_lattice};
use crate::dense::DenseFst;
use crate::lattice::{LatticeDecodeOptions, lattice_decode};
type Compact = VectorFst<ArcTpl<CompactLatticeWeight<i32>, i32, i32>>;
fn graph() -> StdVectorFst {
let mut fst: StdVectorFst = VectorFst::new();
fst.add_state();
fst.set_start(0);
fst.set_final(0, TropicalWeight::one());
for label in 1..=3 {
fst.add_arc(0, StdArc::new(label, label * 10, TropicalWeight::one(), 0));
}
fst.properties(K_FST_PROPERTIES, true);
fst
}
fn compact_of(scores: &[f32], frames: usize) -> Compact {
let dense = DenseFst::<StdArc>::new(scores, frames, 3).unwrap();
let lattice = lattice_decode(&graph(), &dense, &LatticeDecodeOptions::exhaustive())
.unwrap()
.expect("a lattice");
determinize_lattice(&lattice, &DeterminizeLatticeOptions::default()).unwrap()
}
const SCORES: [f32; 6] = [
0.0, 1.0, 2.0, 0.0, 0.5, 3.0,
];
#[test]
fn it_returns_distinct_word_sequences_cheapest_first() {
let compact = compact_of(&SCORES, 2);
let best = n_best(&compact, 4).expect("four answers");
assert_eq!(best.len(), 4);
let words: Vec<&[i32]> = best.iter().map(|h| h.words.as_slice()).collect();
assert_eq!(
words,
vec![
&[10, 10][..], &[10, 20][..], &[20, 10][..], &[20, 20][..], ]
);
for pair in best.windows(2) {
assert!(pair[0].cost() <= pair[1].cost(), "not sorted");
}
assert!((best[0].cost() - 0.0).abs() < 1e-6);
assert!((best[3].cost() - 1.5).abs() < 1e-6);
}
#[test]
fn no_two_answers_say_the_same_thing() {
let compact = compact_of(&SCORES, 2);
let best = n_best(&compact, 9).expect("nine answers");
assert_eq!(best.len(), 9, "three symbols over two frames");
let mut seen: Vec<&[i32]> = best.iter().map(|h| h.words.as_slice()).collect();
seen.sort_unstable();
let before = seen.len();
seen.dedup();
assert_eq!(seen.len(), before, "an answer was repeated");
}
#[test]
fn asking_for_more_than_there_are_returns_what_there_is() {
let compact = compact_of(&SCORES, 2);
assert_eq!(n_best(&compact, 100).unwrap().len(), 9);
assert!(n_best(&compact, 0).unwrap().is_empty());
}
#[test]
fn every_answer_carries_the_frames_it_used() {
let compact = compact_of(&SCORES, 2);
for hypothesis in n_best(&compact, 9).unwrap() {
assert_eq!(
hypothesis.alignment().len(),
2,
"two frames were decoded: {hypothesis:?}"
);
let from_alignment: Vec<i32> = hypothesis
.alignment()
.iter()
.map(|label| label * 10)
.collect();
assert_eq!(from_alignment, hypothesis.words);
}
}
#[test]
fn rescaling_the_acoustic_half_changes_which_answer_wins() {
let mut fst: StdVectorFst = VectorFst::new();
fst.add_state();
fst.set_start(0);
fst.set_final(0, TropicalWeight::one());
fst.add_arc(0, StdArc::new(1, 10, TropicalWeight(1.0), 0));
fst.add_arc(0, StdArc::new(2, 20, TropicalWeight(0.0), 0));
fst.properties(K_FST_PROPERTIES, true);
let scores = [0.0, 1.2, 9.0];
let dense = DenseFst::<StdArc>::new(&scores, 1, 3).unwrap();
let lattice = lattice_decode(&fst, &dense, &LatticeDecodeOptions::exhaustive())
.unwrap()
.unwrap();
let compact = determinize_lattice(&lattice, &DeterminizeLatticeOptions::default()).unwrap();
assert_eq!(n_best(&compact, 1).unwrap()[0].words, vec![10]);
let mut quieter = compact.clone();
scale(&mut quieter, 0.5, 1.0);
assert_eq!(n_best(&quieter, 1).unwrap()[0].words, vec![20]);
assert_eq!(n_best(&quieter, 1).unwrap()[0].alignment(), &[2]);
}
#[test]
fn scaling_leaves_the_alignments_alone() {
let mut compact = compact_of(&SCORES, 2);
let before: Vec<Vec<i32>> = n_best(&compact, 9)
.unwrap()
.iter()
.map(|h| h.alignment().to_vec())
.collect();
scale(&mut compact, 3.0, 2.0);
let after: Vec<Vec<i32>> = n_best(&compact, 9)
.unwrap()
.iter()
.map(|h| h.alignment().to_vec())
.collect();
assert_eq!(before.len(), after.len());
assert_eq!(before, after);
}
}