use crate::arc::{Arc, ArcIterator};
use crate::fst::{Fst, Label, StateId};
use crate::semiring::Semiring;
use rustc_hash::FxHashMap;
use std::sync::RwLock;
const EPSILON: Label = 0;
#[derive(Debug, Clone)]
struct CachedComposedState<W: Semiring> {
final_weight: Option<W>,
arcs: Vec<Arc<W>>,
}
pub struct LazyComposeFst<'a, W, F1, F2>
where
W: Semiring,
F1: Fst<W>,
F2: Fst<W>,
{
fst1: &'a F1,
fst2: &'a F2,
state_map: RwLock<FxHashMap<(StateId, StateId), StateId>>,
state_pairs: RwLock<Vec<(StateId, StateId)>>,
state_cache: RwLock<Vec<Option<CachedComposedState<W>>>>,
start: Option<StateId>,
}
impl<'a, W, F1, F2> LazyComposeFst<'a, W, F1, F2>
where
W: Semiring,
F1: Fst<W>,
F2: Fst<W>,
{
pub fn new(fst1: &'a F1, fst2: &'a F2) -> Self {
let start = match (fst1.start(), fst2.start()) {
(Some(s1), Some(s2)) => {
let mut state_map = FxHashMap::default();
state_map.insert((s1, s2), 0);
Some(0)
}
_ => None,
};
let state_pairs = if let (Some(s1), Some(s2)) = (fst1.start(), fst2.start()) {
vec![(s1, s2)]
} else {
Vec::new()
};
let state_cache = if start.is_some() {
vec![None]
} else {
Vec::new()
};
let mut state_map_init = FxHashMap::default();
if let (Some(s1), Some(s2)) = (fst1.start(), fst2.start()) {
state_map_init.insert((s1, s2), 0);
}
Self {
fst1,
fst2,
state_map: RwLock::new(state_map_init),
state_pairs: RwLock::new(state_pairs),
state_cache: RwLock::new(state_cache),
start,
}
}
fn get_or_create_state(&self, s1: StateId, s2: StateId) -> StateId {
{
let state_map = self.state_map.read().unwrap();
if let Some(&state) = state_map.get(&(s1, s2)) {
return state;
}
}
let mut state_map = self.state_map.write().unwrap();
if let Some(&state) = state_map.get(&(s1, s2)) {
return state;
}
let mut state_pairs = self.state_pairs.write().unwrap();
let mut state_cache = self.state_cache.write().unwrap();
let new_state = state_pairs.len() as StateId;
state_map.insert((s1, s2), new_state);
state_pairs.push((s1, s2));
state_cache.push(None);
new_state
}
fn compute_arcs(&self, state: StateId) -> CachedComposedState<W> {
let state_pairs = self.state_pairs.read().unwrap();
let (s1, s2) = state_pairs[state as usize];
drop(state_pairs);
let mut arcs = Vec::new();
let arcs1: Vec<Arc<W>> = self.fst1.arcs(s1).collect();
let arcs2: Vec<Arc<W>> = self.fst2.arcs(s2).collect();
let mut sorted_arcs1 = arcs1.clone();
let mut sorted_arcs2 = arcs2.clone();
sorted_arcs1.sort_by_key(|a| a.olabel);
sorted_arcs2.sort_by_key(|a| a.ilabel);
for arc1 in arcs1.iter().filter(|a| a.olabel == EPSILON) {
let next_state = self.get_or_create_state(arc1.nextstate, s2);
arcs.push(Arc::new(
arc1.ilabel,
EPSILON,
arc1.weight.clone(),
next_state,
));
}
for arc2 in arcs2.iter().filter(|a| a.ilabel == EPSILON) {
let next_state = self.get_or_create_state(s1, arc2.nextstate);
arcs.push(Arc::new(
EPSILON,
arc2.olabel,
arc2.weight.clone(),
next_state,
));
}
let non_eps1: Vec<_> = sorted_arcs1
.into_iter()
.filter(|a| a.olabel != EPSILON)
.collect();
let non_eps2: Vec<_> = sorted_arcs2
.into_iter()
.filter(|a| a.ilabel != EPSILON)
.collect();
let mut i = 0;
let mut j = 0;
while i < non_eps1.len() && j < non_eps2.len() {
let arc1 = &non_eps1[i];
let arc2 = &non_eps2[j];
match arc1.olabel.cmp(&arc2.ilabel) {
std::cmp::Ordering::Less => {
i += 1;
}
std::cmp::Ordering::Greater => {
j += 1;
}
std::cmp::Ordering::Equal => {
let label = arc1.olabel;
let mut end_i = i;
let mut end_j = j;
while end_i < non_eps1.len() && non_eps1[end_i].olabel == label {
end_i += 1;
}
while end_j < non_eps2.len() && non_eps2[end_j].ilabel == label {
end_j += 1;
}
for a1 in non_eps1.iter().take(end_i).skip(i) {
for a2 in non_eps2.iter().take(end_j).skip(j) {
let next_state = self.get_or_create_state(a1.nextstate, a2.nextstate);
arcs.push(Arc::new(
a1.ilabel,
a2.olabel,
a1.weight.times(&a2.weight),
next_state,
));
}
}
i = end_i;
j = end_j;
}
}
}
let final_weight = match (self.fst1.final_weight(s1), self.fst2.final_weight(s2)) {
(Some(w1), Some(w2)) => Some(w1.times(w2)),
_ => None,
};
CachedComposedState { final_weight, arcs }
}
fn get_state_data(&self, state: StateId) -> CachedComposedState<W> {
{
let cache = self.state_cache.read().unwrap();
if let Some(Some(data)) = cache.get(state as usize) {
return data.clone();
}
}
let data = self.compute_arcs(state);
{
let mut cache = self.state_cache.write().unwrap();
if let Some(slot) = cache.get_mut(state as usize) {
*slot = Some(data.clone());
}
}
data
}
pub fn num_computed_states(&self) -> usize {
self.state_pairs.read().unwrap().len()
}
pub fn num_cached_states(&self) -> usize {
self.state_cache
.read()
.unwrap()
.iter()
.filter(|s| s.is_some())
.count()
}
pub fn state_pair(&self, state: StateId) -> Option<(StateId, StateId)> {
self.state_pairs
.read()
.unwrap()
.get(state as usize)
.copied()
}
pub fn clear_arc_cache(&self) {
let mut cache = self.state_cache.write().unwrap();
for slot in cache.iter_mut() {
*slot = None;
}
}
}
impl<'a, W, F1, F2> std::fmt::Debug for LazyComposeFst<'a, W, F1, F2>
where
W: Semiring,
F1: Fst<W>,
F2: Fst<W>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LazyComposeFst")
.field("num_computed_states", &self.num_computed_states())
.field("num_cached_states", &self.num_cached_states())
.field("start", &self.start)
.finish()
}
}
#[derive(Debug)]
pub struct LazyComposeArcIterator<W: Semiring> {
arcs: Vec<Arc<W>>,
index: usize,
}
impl<W: Semiring> Iterator for LazyComposeArcIterator<W> {
type Item = Arc<W>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.arcs.len() {
let arc = self.arcs[self.index].clone();
self.index += 1;
Some(arc)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.arcs.len() - self.index;
(remaining, Some(remaining))
}
}
impl<W: Semiring> ExactSizeIterator for LazyComposeArcIterator<W> {}
impl<W: Semiring> ArcIterator<W> for LazyComposeArcIterator<W> {
fn reset(&mut self) {
self.index = 0;
}
}
impl<'a, W, F1, F2> Fst<W> for LazyComposeFst<'a, W, F1, F2>
where
W: Semiring,
F1: Fst<W>,
F2: Fst<W>,
{
type ArcIter<'b>
= LazyComposeArcIterator<W>
where
Self: 'b;
fn start(&self) -> Option<StateId> {
self.start
}
fn final_weight(&self, _state: StateId) -> Option<&W> {
None
}
fn num_arcs(&self, state: StateId) -> usize {
self.get_state_data(state).arcs.len()
}
fn num_states(&self) -> usize {
self.state_pairs.read().unwrap().len()
}
fn properties(&self) -> crate::properties::FstProperties {
crate::properties::FstProperties::default()
}
fn arcs(&self, state: StateId) -> Self::ArcIter<'_> {
let data = self.get_state_data(state);
LazyComposeArcIterator {
arcs: data.arcs,
index: 0,
}
}
fn is_final(&self, state: StateId) -> bool {
self.get_state_data(state).final_weight.is_some()
}
}
unsafe impl<'a, W, F1, F2> Send for LazyComposeFst<'a, W, F1, F2>
where
W: Semiring + Send,
F1: Fst<W> + Sync,
F2: Fst<W> + Sync,
{
}
unsafe impl<'a, W, F1, F2> Sync for LazyComposeFst<'a, W, F1, F2>
where
W: Semiring + Send + Sync,
F1: Fst<W> + Sync,
F2: Fst<W> + Sync,
{
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fst::{MutableFst, VectorFst};
use crate::semiring::TropicalWeight;
use num_traits::One;
#[test]
fn test_lazy_compose_basic() {
let mut fst1 = VectorFst::<TropicalWeight>::new();
let s0 = fst1.add_state();
let s1 = fst1.add_state();
fst1.set_start(s0);
fst1.set_final(s1, TropicalWeight::one());
fst1.add_arc(s0, Arc::new(1, 2, TropicalWeight::new(0.5), s1));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let t0 = fst2.add_state();
let t1 = fst2.add_state();
fst2.set_start(t0);
fst2.set_final(t1, TropicalWeight::one());
fst2.add_arc(t0, Arc::new(2, 3, TropicalWeight::new(0.3), t1));
let lazy = LazyComposeFst::new(&fst1, &fst2);
assert_eq!(lazy.start(), Some(0));
assert_eq!(lazy.num_computed_states(), 1);
let arcs: Vec<_> = lazy.arcs(0).collect();
assert_eq!(arcs.len(), 1);
assert_eq!(arcs[0].ilabel, 1);
assert_eq!(arcs[0].olabel, 3);
assert_eq!(lazy.num_computed_states(), 2);
assert!(lazy.is_final(arcs[0].nextstate));
}
#[test]
fn test_lazy_compose_no_match() {
let mut fst1 = VectorFst::<TropicalWeight>::new();
let s0 = fst1.add_state();
let s1 = fst1.add_state();
fst1.set_start(s0);
fst1.set_final(s1, TropicalWeight::one());
fst1.add_arc(s0, Arc::new(1, 2, TropicalWeight::one(), s1));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let t0 = fst2.add_state();
let t1 = fst2.add_state();
fst2.set_start(t0);
fst2.set_final(t1, TropicalWeight::one());
fst2.add_arc(t0, Arc::new(3, 4, TropicalWeight::one(), t1));
let lazy = LazyComposeFst::new(&fst1, &fst2);
let arcs: Vec<_> = lazy.arcs(0).collect();
assert_eq!(arcs.len(), 0); }
#[test]
fn test_lazy_compose_epsilon_handling() {
let mut fst1 = VectorFst::<TropicalWeight>::new();
let s0 = fst1.add_state();
let s1 = fst1.add_state();
let s2 = fst1.add_state();
fst1.set_start(s0);
fst1.set_final(s2, TropicalWeight::one());
fst1.add_arc(s0, Arc::new(1, 0, TropicalWeight::one(), s1)); fst1.add_arc(s1, Arc::new(0, 2, TropicalWeight::one(), s2));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let t0 = fst2.add_state();
let t1 = fst2.add_state();
fst2.set_start(t0);
fst2.set_final(t1, TropicalWeight::one());
fst2.add_arc(t0, Arc::new(2, 3, TropicalWeight::one(), t1));
let lazy = LazyComposeFst::new(&fst1, &fst2);
let arcs: Vec<_> = lazy.arcs(0).collect();
assert!(!arcs.is_empty());
}
#[test]
fn test_lazy_compose_multiple_matches() {
let mut fst1 = VectorFst::<TropicalWeight>::new();
let s0 = fst1.add_state();
let s1 = fst1.add_state();
let s2 = fst1.add_state();
fst1.set_start(s0);
fst1.set_final(s1, TropicalWeight::one());
fst1.set_final(s2, TropicalWeight::one());
fst1.add_arc(s0, Arc::new(1, 2, TropicalWeight::new(0.5), s1));
fst1.add_arc(s0, Arc::new(1, 2, TropicalWeight::new(0.7), s2));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let t0 = fst2.add_state();
let t1 = fst2.add_state();
let t2 = fst2.add_state();
fst2.set_start(t0);
fst2.set_final(t1, TropicalWeight::one());
fst2.set_final(t2, TropicalWeight::one());
fst2.add_arc(t0, Arc::new(2, 3, TropicalWeight::new(0.3), t1));
fst2.add_arc(t0, Arc::new(2, 4, TropicalWeight::new(0.4), t2));
let lazy = LazyComposeFst::new(&fst1, &fst2);
let arcs: Vec<_> = lazy.arcs(0).collect();
assert_eq!(arcs.len(), 4);
}
#[test]
fn test_lazy_compose_state_pair() {
let mut fst1 = VectorFst::<TropicalWeight>::new();
let s0 = fst1.add_state();
let s1 = fst1.add_state();
fst1.set_start(s0);
fst1.set_final(s1, TropicalWeight::one());
fst1.add_arc(s0, Arc::new(1, 2, TropicalWeight::one(), s1));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let t0 = fst2.add_state();
let t1 = fst2.add_state();
fst2.set_start(t0);
fst2.set_final(t1, TropicalWeight::one());
fst2.add_arc(t0, Arc::new(2, 3, TropicalWeight::one(), t1));
let lazy = LazyComposeFst::new(&fst1, &fst2);
assert_eq!(lazy.state_pair(0), Some((0, 0)));
let arcs: Vec<_> = lazy.arcs(0).collect();
let next_state = arcs[0].nextstate;
assert_eq!(lazy.state_pair(next_state), Some((1, 1)));
}
}