1use std::marker::PhantomData;
17
18use sicada::AtomicRc;
19use sicada::arc::{Arc, ArcLabel, ArcStateId};
20use sicada::error::OpenFstError;
21use sicada::fst::{ExpandedFst, Fst};
22use sicada::properties::{
23 K_ACCEPTOR, K_ACCESSIBLE, K_ACYCLIC, K_CO_ACCESSIBLE, K_EPSILONS, K_EXPANDED,
24 K_I_DETERMINISTIC, K_I_EPSILONS, K_I_LABEL_SORTED, K_INITIAL_ACYCLIC, K_NO_EPSILONS,
25 K_NO_I_EPSILONS, K_NO_O_EPSILONS, K_NOT_STRING, K_O_DETERMINISTIC, K_O_EPSILONS,
26 K_O_LABEL_SORTED, K_STRING, K_TOP_SORTED, K_UNWEIGHTED, K_UNWEIGHTED_CYCLES, K_WEIGHTED,
27};
28use sicada::symbol_table::SymbolTable;
29use sicada::weight::Weight;
30
31pub trait FromScore: Weight {
39 fn from_cost(cost: f32) -> Self;
41
42 fn to_cost(&self) -> f32;
44}
45
46macro_rules! from_score {
47 ($weight:ty, $float:ty) => {
48 impl FromScore for $weight {
49 #[inline(always)]
50 fn from_cost(cost: f32) -> Self {
51 Self(cost as $float)
52 }
53 #[inline(always)]
54 fn to_cost(&self) -> f32 {
55 self.0 as f32
56 }
57 }
58 };
59}
60
61from_score!(sicada::weights::float_weight::TropicalWeight, f32);
62from_score!(sicada::weights::float_weight::TropicalWeight64, f64);
63from_score!(sicada::weights::float_weight::LogWeight, f32);
64from_score!(sicada::weights::float_weight::Log64Weight, f64);
65
66#[derive(Debug, Clone)]
70pub struct DenseFst<'a, A: Arc> {
71 scores: &'a [f32],
72 num_frames: usize,
73 num_symbols: usize,
74 label_offset: i64,
82 props: u64,
83 _arc: PhantomData<A>,
84}
85
86impl<'a, A: Arc> DenseFst<'a, A>
87where
88 A::Weight: FromScore,
89{
90 pub fn new(
93 scores: &'a [f32],
94 num_frames: usize,
95 num_symbols: usize,
96 ) -> Result<Self, OpenFstError> {
97 Self::with_label_offset(scores, num_frames, num_symbols, 1)
98 }
99
100 pub fn with_label_offset(
105 scores: &'a [f32],
106 num_frames: usize,
107 num_symbols: usize,
108 label_offset: i64,
109 ) -> Result<Self, OpenFstError> {
110 let expected = num_frames.checked_mul(num_symbols).ok_or_else(|| {
111 OpenFstError::InvalidOperation(format!(
112 "DenseFst: {num_frames} frames of {num_symbols} symbols overflows"
113 ))
114 })?;
115 if scores.len() != expected {
116 return Err(OpenFstError::InvalidOperation(format!(
117 "DenseFst: {} scores for {num_frames} frames of {num_symbols} symbols, expected {expected}",
118 scores.len()
119 )));
120 }
121 if num_symbols == 0 && num_frames > 0 {
122 return Err(OpenFstError::InvalidOperation(
123 "DenseFst: frames with no symbols leave the last frame unreachable".into(),
124 ));
125 }
126 if label_offset < 0 {
127 return Err(OpenFstError::InvalidOperation(
128 "DenseFst: a negative label offset would put a column on kNoLabel".into(),
129 ));
130 }
131 let last = label_offset + num_symbols as i64 - 1;
132 if num_symbols > 0 && A::Label::from_i64(last).is_none() {
133 return Err(OpenFstError::InvalidOperation(format!(
134 "DenseFst: label {last} does not fit the arc's label type"
135 )));
136 }
137
138 let props = Self::compute_properties(scores, num_frames, num_symbols, label_offset);
139 Ok(Self {
140 scores,
141 num_frames,
142 num_symbols,
143 label_offset,
144 props,
145 _arc: PhantomData,
146 })
147 }
148
149 #[inline(always)]
151 pub fn num_frames(&self) -> usize {
152 self.num_frames
153 }
154
155 #[inline(always)]
157 pub fn num_symbols(&self) -> usize {
158 self.num_symbols
159 }
160
161 #[inline(always)]
163 pub fn label_offset(&self) -> i64 {
164 self.label_offset
165 }
166
167 #[inline(always)]
173 pub fn frame(&self, t: usize) -> &'a [f32] {
174 &self.scores[t * self.num_symbols..(t + 1) * self.num_symbols]
175 }
176
177 #[inline(always)]
179 pub fn column_of(&self, label: A::Label) -> Option<usize> {
180 let index = label.to_i64()? - self.label_offset;
181 (index >= 0 && (index as u64) < self.num_symbols as u64).then_some(index as usize)
182 }
183
184 fn compute_properties(
185 scores: &[f32],
186 num_frames: usize,
187 num_symbols: usize,
188 label_offset: i64,
189 ) -> u64 {
190 let mut props = K_EXPANDED
193 | K_ACCEPTOR
194 | K_I_DETERMINISTIC
195 | K_O_DETERMINISTIC
196 | K_I_LABEL_SORTED
197 | K_O_LABEL_SORTED
198 | K_ACYCLIC
199 | K_INITIAL_ACYCLIC
200 | K_TOP_SORTED
201 | K_ACCESSIBLE
202 | K_CO_ACCESSIBLE
203 | K_UNWEIGHTED_CYCLES;
205
206 if label_offset == 0 && num_symbols > 0 && num_frames > 0 {
208 props |= K_EPSILONS | K_I_EPSILONS | K_O_EPSILONS;
209 } else {
210 props |= K_NO_EPSILONS | K_NO_I_EPSILONS | K_NO_O_EPSILONS;
211 }
212
213 if num_symbols <= 1 || num_frames == 0 {
216 props |= K_STRING;
217 } else {
218 props |= K_NOT_STRING;
219 }
220
221 if scores.iter().all(|&score| score == 0.0) {
224 props |= K_UNWEIGHTED;
225 } else {
226 props |= K_WEIGHTED;
227 }
228 props
229 }
230}
231
232#[derive(Debug, Clone)]
234pub struct DenseArcIter<'a, A: Arc> {
235 row: &'a [f32],
236 column: usize,
237 label_offset: i64,
238 nextstate: A::StateId,
239 _arc: PhantomData<A>,
240}
241
242impl<A: Arc> Iterator for DenseArcIter<'_, A>
243where
244 A::Weight: FromScore,
245{
246 type Item = A;
247
248 #[inline]
249 fn next(&mut self) -> Option<A> {
250 let &score = self.row.get(self.column)?;
251 let label = A::Label::from_i64(self.label_offset + self.column as i64)?;
253 self.column += 1;
254 Some(A::new(
255 label,
256 label,
257 A::Weight::from_cost(score),
258 self.nextstate,
259 ))
260 }
261
262 #[inline]
263 fn size_hint(&self) -> (usize, Option<usize>) {
264 let left = self.row.len() - self.column;
265 (left, Some(left))
266 }
267}
268
269impl<A: Arc> ExactSizeIterator for DenseArcIter<'_, A> where A::Weight: FromScore {}
270
271impl<A: Arc> Fst<A> for DenseFst<'_, A>
272where
273 A::Weight: FromScore,
274{
275 type StateIter<'s>
276 = DenseStateIter<A>
277 where
278 Self: 's;
279 type ArcIter<'s>
280 = DenseArcIter<'s, A>
281 where
282 Self: 's;
283
284 #[inline]
285 fn start(&self) -> Option<A::StateId> {
286 Some(A::StateId::from_usize(0))
287 }
288
289 #[inline]
290 fn final_weight(&self, state: A::StateId) -> A::Weight {
291 if state.as_usize() == self.num_frames {
292 A::Weight::one()
293 } else {
294 A::Weight::zero()
295 }
296 }
297
298 #[inline]
299 fn num_arcs(&self, state: A::StateId) -> usize {
300 if state.as_usize() < self.num_frames {
301 self.num_symbols
302 } else {
303 0
304 }
305 }
306
307 #[inline]
308 fn num_input_epsilons(&self, state: A::StateId) -> usize {
309 usize::from(self.label_offset == 0 && self.num_arcs(state) > 0)
310 }
311
312 #[inline]
313 fn num_output_epsilons(&self, state: A::StateId) -> usize {
314 self.num_input_epsilons(state)
315 }
316
317 #[inline]
318 fn num_states_if_known(&self) -> Option<usize> {
319 Some(self.num_frames + 1)
320 }
321
322 #[inline]
323 fn properties(&self, mask: u64, _test: bool) -> u64 {
324 self.props & mask
327 }
328
329 #[inline]
330 fn fst_type(&self) -> &str {
331 "dense"
332 }
333
334 fn input_symbols(&self) -> Option<AtomicRc<SymbolTable>> {
335 None
336 }
337
338 fn output_symbols(&self) -> Option<AtomicRc<SymbolTable>> {
339 None
340 }
341
342 #[inline]
343 fn states<'s>(&'s self) -> Self::StateIter<'s> {
344 DenseStateIter {
345 next: 0,
346 end: self.num_frames + 1,
347 _arc: PhantomData,
348 }
349 }
350
351 #[inline]
352 fn arcs<'s>(&'s self, state: A::StateId) -> Self::ArcIter<'s> {
353 let t = state.as_usize();
354 let row: &[f32] = if t < self.num_frames {
355 self.frame(t)
356 } else {
357 &[]
358 };
359 DenseArcIter {
360 row,
361 column: 0,
362 label_offset: self.label_offset,
363 nextstate: A::StateId::from_usize(t + 1),
364 _arc: PhantomData,
365 }
366 }
367}
368
369impl<A: Arc> ExpandedFst<A> for DenseFst<'_, A>
370where
371 A::Weight: FromScore,
372{
373 #[inline]
374 fn num_states(&self) -> usize {
375 self.num_frames + 1
376 }
377}
378
379#[derive(Debug, Clone)]
381pub struct DenseStateIter<A: Arc> {
382 next: usize,
383 end: usize,
384 _arc: PhantomData<A>,
385}
386
387impl<A: Arc> Iterator for DenseStateIter<A> {
388 type Item = A::StateId;
389
390 #[inline]
391 fn next(&mut self) -> Option<A::StateId> {
392 (self.next < self.end).then(|| {
393 let state = A::StateId::from_usize(self.next);
394 self.next += 1;
395 state
396 })
397 }
398
399 #[inline]
400 fn size_hint(&self) -> (usize, Option<usize>) {
401 let left = self.end - self.next;
402 (left, Some(left))
403 }
404}
405
406impl<A: Arc> ExactSizeIterator for DenseStateIter<A> {}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use sicada::arc::StdArc;
412 use sicada::fst::MutableFst;
413 use sicada::fsts::vector_fst::{StdVectorFst, VectorFst};
414 use sicada::properties::K_FST_PROPERTIES;
415 use sicada::weights::float_weight::TropicalWeight;
416
417 const SCORES: [f32; 6] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
419
420 fn dense() -> DenseFst<'static, StdArc> {
421 DenseFst::new(&SCORES, 2, 3).expect("a dense FST")
422 }
423
424 fn materialised() -> StdVectorFst {
427 let mut fst = VectorFst::new();
428 for _ in 0..3 {
429 fst.add_state();
430 }
431 fst.set_start(0);
432 fst.set_final(2, TropicalWeight::one());
433 for t in 0..2 {
434 for c in 0..3 {
435 let label = c as i32 + 1;
436 fst.add_arc(
437 t,
438 StdArc::new(
439 label,
440 label,
441 TropicalWeight(SCORES[t as usize * 3 + c]),
442 t + 1,
443 ),
444 );
445 }
446 }
447 fst
448 }
449
450 #[test]
451 fn it_reads_the_matrix_as_a_chain_of_frames() {
452 let dense = dense();
453 let expected = materialised();
454
455 assert_eq!(dense.start(), expected.start());
456 assert_eq!(dense.num_states(), 3);
457 assert_eq!(
458 dense.states().collect::<Vec<_>>(),
459 expected.states().collect::<Vec<_>>()
460 );
461 for state in expected.states() {
462 assert_eq!(dense.num_arcs(state), expected.num_arcs(state));
463 assert_eq!(
464 dense.arcs(state).collect::<Vec<_>>(),
465 expected.arcs(state).collect::<Vec<_>>(),
466 "state {state}"
467 );
468 assert_eq!(dense.final_weight(state), expected.final_weight(state));
469 }
470 }
471
472 #[test]
475 fn its_properties_are_the_ones_the_same_fst_stored_has() {
476 let dense = dense();
477 let expected = materialised();
478 let computed = expected.properties(K_FST_PROPERTIES, true);
479
480 let shared = K_FST_PROPERTIES & !sicada::properties::K_MUTABLE;
482 assert_eq!(
483 dense.properties(shared, true),
484 computed & shared,
485 "dense {:#x} vs vector {:#x}",
486 dense.properties(shared, true),
487 computed & shared
488 );
489 }
490
491 #[test]
492 fn one_symbol_per_frame_is_a_string() {
493 let scores = [0.5, 0.25];
494 let dense = DenseFst::<StdArc>::new(&scores, 2, 1).expect("a dense FST");
495 assert_ne!(dense.properties(K_STRING, true), 0);
496 assert_eq!(dense.properties(K_NOT_STRING, true), 0);
497 }
498
499 #[test]
500 fn no_frames_is_the_empty_string() {
501 let dense = DenseFst::<StdArc>::new(&[], 0, 5).expect("a dense FST");
502 assert_eq!(dense.num_states(), 1);
503 assert_eq!(dense.final_weight(0), TropicalWeight::one());
504 assert_eq!(dense.arcs(0).count(), 0);
505 }
506
507 #[test]
508 fn the_columns_move_off_epsilon_by_default() {
509 let dense = dense();
510 assert_ne!(dense.properties(K_NO_EPSILONS, true), 0);
511 assert!(dense.arcs(0).all(|arc| arc.ilabel() != 0));
512
513 let on_epsilon = DenseFst::<StdArc>::with_label_offset(&SCORES, 2, 3, 0).unwrap();
514 assert_ne!(on_epsilon.properties(K_EPSILONS, true), 0);
515 assert_eq!(on_epsilon.num_input_epsilons(0), 1);
516 }
517
518 #[test]
519 fn a_matrix_of_the_wrong_size_is_refused() {
520 let err = DenseFst::<StdArc>::new(&SCORES, 2, 4).unwrap_err();
521 assert!(format!("{err}").contains("expected 8"), "{err}");
522 }
523
524 #[test]
525 fn a_frame_lookup_is_the_same_score_the_arc_carries() {
526 let dense = dense();
527 for t in 0..dense.num_frames() {
528 for arc in dense.arcs(t as i32) {
529 let column = dense.column_of(arc.ilabel()).expect("a column");
530 assert_eq!(dense.frame(t)[column], arc.weight().to_cost());
531 }
532 }
533 assert_eq!(dense.column_of(0), None, "epsilon names no column");
534 assert_eq!(dense.column_of(4), None, "past the last column");
535 }
536}