1use sicada::algorithms::shortest_path::{ShortestPathOptions, shortest_path};
16use sicada::arc::{Arc, ArcLabel, ArcStateId, ArcTpl};
17use sicada::error::OpenFstError;
18use sicada::fst::{ExpandedFst, Fst, MutableFst};
19use sicada::fsts::vector_fst::VectorFst;
20use sicada::weight::Weight;
21
22use crate::compact_lattice_weight::CompactLatticeWeight;
23use crate::lattice_weight::LatticeWeight;
24
25#[derive(Debug, Clone, PartialEq)]
27pub struct Hypothesis<L: ArcLabel> {
28 pub words: Vec<L>,
30 pub weight: CompactLatticeWeight<L>,
32}
33
34impl<L: ArcLabel> Hypothesis<L> {
35 #[inline]
37 pub fn alignment(&self) -> &[L] {
38 self.weight.alignment()
39 }
40
41 #[inline]
43 pub fn cost(&self) -> f32 {
44 self.weight.weight().total()
45 }
46}
47
48pub fn n_best<L, S>(
56 lattice: &VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>,
57 n: usize,
58) -> Result<Vec<Hypothesis<L>>, OpenFstError>
59where
60 L: ArcLabel,
61 S: ArcStateId,
62{
63 if n == 0 || lattice.start().is_none() {
64 return Ok(Vec::new());
65 }
66
67 let mut best: VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>> = VectorFst::new();
68 shortest_path(
69 lattice,
70 &mut best,
71 &ShortestPathOptions {
72 nshortest: n,
73 ..ShortestPathOptions::default()
74 },
75 )?;
76
77 let mut found = enumerate(&best);
78 found.sort_by(|a, b| a.cost().total_cmp(&b.cost()));
80 found.truncate(n);
81 Ok(found)
82}
83
84fn enumerate<L, S>(fst: &VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>) -> Vec<Hypothesis<L>>
89where
90 L: ArcLabel,
91 S: ArcStateId,
92{
93 let mut found = Vec::new();
94 let Some(start) = fst.start() else {
95 return found;
96 };
97 let zero = CompactLatticeWeight::<L>::zero();
98 let mut stack = vec![(start, Vec::new(), CompactLatticeWeight::<L>::one())];
99 while let Some((state, words, weight)) = stack.pop() {
100 let final_weight = fst.final_weight(state);
101 if final_weight.is_member() && final_weight != zero {
102 found.push(Hypothesis {
103 words: words.clone(),
104 weight: weight.times(&final_weight),
105 });
106 }
107 for arc in fst.arcs(state) {
108 let mut next = words.clone();
109 if arc.olabel() != L::epsilon() {
110 next.push(arc.olabel());
111 }
112 stack.push((arc.nextstate(), next, weight.times(arc.weight())));
113 }
114 }
115 found
116}
117
118pub fn scale<L, S>(
129 lattice: &mut VectorFst<ArcTpl<CompactLatticeWeight<L>, L, S>>,
130 acoustic: f32,
131 graph: f32,
132) where
133 L: ArcLabel,
134 S: ArcStateId,
135{
136 let rescale = |weight: &CompactLatticeWeight<L>| {
137 CompactLatticeWeight::new(
138 LatticeWeight::new(
139 graph * weight.weight().graph,
140 acoustic * weight.weight().acoustic,
141 ),
142 weight.alignment().iter().copied().collect(),
143 )
144 };
145
146 for state in 0..lattice.num_states() {
147 let state = S::from_usize(state);
148 let final_weight = lattice.final_weight(state);
149 if final_weight.is_member() && final_weight != CompactLatticeWeight::zero() {
150 lattice.set_final(state, rescale(&final_weight));
151 }
152 for arc in lattice.arcs_mut(state) {
153 arc.weight = rescale(&arc.weight);
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use sicada::arc::StdArc;
162 use sicada::fsts::vector_fst::StdVectorFst;
163 use sicada::properties::K_FST_PROPERTIES;
164 use sicada::weights::float_weight::TropicalWeight;
165
166 use crate::compact::{DeterminizeLatticeOptions, determinize_lattice};
167 use crate::dense::DenseFst;
168 use crate::lattice::{LatticeDecodeOptions, lattice_decode};
169
170 type Compact = VectorFst<ArcTpl<CompactLatticeWeight<i32>, i32, i32>>;
171
172 fn graph() -> StdVectorFst {
174 let mut fst: StdVectorFst = VectorFst::new();
175 fst.add_state();
176 fst.set_start(0);
177 fst.set_final(0, TropicalWeight::one());
178 for label in 1..=3 {
179 fst.add_arc(0, StdArc::new(label, label * 10, TropicalWeight::one(), 0));
180 }
181 fst.properties(K_FST_PROPERTIES, true);
182 fst
183 }
184
185 fn compact_of(scores: &[f32], frames: usize) -> Compact {
186 let dense = DenseFst::<StdArc>::new(scores, frames, 3).unwrap();
187 let lattice = lattice_decode(&graph(), &dense, &LatticeDecodeOptions::exhaustive())
188 .unwrap()
189 .expect("a lattice");
190 determinize_lattice(&lattice, &DeterminizeLatticeOptions::default()).unwrap()
191 }
192
193 const SCORES: [f32; 6] = [
196 0.0, 1.0, 2.0, 0.0, 0.5, 3.0,
198 ];
199
200 #[test]
201 fn it_returns_distinct_word_sequences_cheapest_first() {
202 let compact = compact_of(&SCORES, 2);
203 let best = n_best(&compact, 4).expect("four answers");
204
205 assert_eq!(best.len(), 4);
206 let words: Vec<&[i32]> = best.iter().map(|h| h.words.as_slice()).collect();
207 assert_eq!(
208 words,
209 vec![
210 &[10, 10][..], &[10, 20][..], &[20, 10][..], &[20, 20][..], ]
215 );
216 for pair in best.windows(2) {
217 assert!(pair[0].cost() <= pair[1].cost(), "not sorted");
218 }
219 assert!((best[0].cost() - 0.0).abs() < 1e-6);
220 assert!((best[3].cost() - 1.5).abs() < 1e-6);
221 }
222
223 #[test]
226 fn no_two_answers_say_the_same_thing() {
227 let compact = compact_of(&SCORES, 2);
228 let best = n_best(&compact, 9).expect("nine answers");
229 assert_eq!(best.len(), 9, "three symbols over two frames");
230
231 let mut seen: Vec<&[i32]> = best.iter().map(|h| h.words.as_slice()).collect();
232 seen.sort_unstable();
233 let before = seen.len();
234 seen.dedup();
235 assert_eq!(seen.len(), before, "an answer was repeated");
236 }
237
238 #[test]
239 fn asking_for_more_than_there_are_returns_what_there_is() {
240 let compact = compact_of(&SCORES, 2);
241 assert_eq!(n_best(&compact, 100).unwrap().len(), 9);
242 assert!(n_best(&compact, 0).unwrap().is_empty());
243 }
244
245 #[test]
246 fn every_answer_carries_the_frames_it_used() {
247 let compact = compact_of(&SCORES, 2);
248 for hypothesis in n_best(&compact, 9).unwrap() {
249 assert_eq!(
250 hypothesis.alignment().len(),
251 2,
252 "two frames were decoded: {hypothesis:?}"
253 );
254 let from_alignment: Vec<i32> = hypothesis
256 .alignment()
257 .iter()
258 .map(|label| label * 10)
259 .collect();
260 assert_eq!(from_alignment, hypothesis.words);
261 }
262 }
263
264 #[test]
267 fn rescaling_the_acoustic_half_changes_which_answer_wins() {
268 let mut fst: StdVectorFst = VectorFst::new();
271 fst.add_state();
272 fst.set_start(0);
273 fst.set_final(0, TropicalWeight::one());
274 fst.add_arc(0, StdArc::new(1, 10, TropicalWeight(1.0), 0));
275 fst.add_arc(0, StdArc::new(2, 20, TropicalWeight(0.0), 0));
276 fst.properties(K_FST_PROPERTIES, true);
277
278 let scores = [0.0, 1.2, 9.0];
280 let dense = DenseFst::<StdArc>::new(&scores, 1, 3).unwrap();
281 let lattice = lattice_decode(&fst, &dense, &LatticeDecodeOptions::exhaustive())
282 .unwrap()
283 .unwrap();
284 let compact = determinize_lattice(&lattice, &DeterminizeLatticeOptions::default()).unwrap();
285
286 assert_eq!(n_best(&compact, 1).unwrap()[0].words, vec![10]);
288
289 let mut quieter = compact.clone();
291 scale(&mut quieter, 0.5, 1.0);
292 assert_eq!(n_best(&quieter, 1).unwrap()[0].words, vec![20]);
293
294 assert_eq!(n_best(&quieter, 1).unwrap()[0].alignment(), &[2]);
296 }
297
298 #[test]
299 fn scaling_leaves_the_alignments_alone() {
300 let mut compact = compact_of(&SCORES, 2);
301 let before: Vec<Vec<i32>> = n_best(&compact, 9)
302 .unwrap()
303 .iter()
304 .map(|h| h.alignment().to_vec())
305 .collect();
306 scale(&mut compact, 3.0, 2.0);
307 let after: Vec<Vec<i32>> = n_best(&compact, 9)
308 .unwrap()
309 .iter()
310 .map(|h| h.alignment().to_vec())
311 .collect();
312 assert_eq!(before.len(), after.len());
313 assert_eq!(before, after);
315 }
316}