use crate::arc::Arc;
use crate::fst::{Fst, StateId};
use crate::semiring::Semiring;
use crate::Result;
pub trait LookaheadComposeFilter<W: Semiring> {
fn filter_arc(
&self,
fst1_state: StateId,
fst2_state: StateId,
arc1: &Arc<W>,
arc2: &Arc<W>,
) -> bool;
fn lookahead_path<F1: Fst<W>, F2: Fst<W>>(
&self,
fst1: &F1,
fst2: &F2,
next1: StateId,
next2: StateId,
) -> bool {
let _ = (fst1, fst2, next1, next2);
true }
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LabelLookaheadFilter;
#[derive(Debug, Clone)]
pub struct LabelPairLookaheadFilter {
pub max_depth: usize,
}
impl LabelPairLookaheadFilter {
pub fn new() -> Self {
Self { max_depth: 2 }
}
pub fn with_max_depth(max_depth: usize) -> Self {
Self { max_depth }
}
}
impl Default for LabelPairLookaheadFilter {
fn default() -> Self {
Self::new()
}
}
impl<W: Semiring> LookaheadComposeFilter<W> for LabelLookaheadFilter {
fn filter_arc(
&self,
_fst1_state: StateId,
_fst2_state: StateId,
arc1: &Arc<W>,
arc2: &Arc<W>,
) -> bool {
use crate::fst::NO_LABEL;
arc1.olabel == arc2.ilabel
|| arc1.olabel == NO_LABEL || arc2.ilabel == NO_LABEL }
}
impl LabelPairLookaheadFilter {
fn has_compatible_future_paths<W: Semiring, F1: Fst<W>, F2: Fst<W>>(
&self,
fst1: &F1,
fst2: &F2,
state1: StateId,
state2: StateId,
depth: usize,
) -> bool {
if depth >= self.max_depth {
return true; }
let mut fst1_outputs = std::collections::HashSet::new();
let mut fst2_inputs = std::collections::HashSet::new();
let mut fst1_has_epsilon_output = false;
let mut fst2_has_epsilon_input = false;
for arc in fst1.arcs(state1) {
if arc.is_epsilon() {
fst1_has_epsilon_output = true;
} else {
fst1_outputs.insert(arc.olabel);
}
}
for arc in fst2.arcs(state2) {
if arc.is_epsilon() {
fst2_has_epsilon_input = true;
} else {
fst2_inputs.insert(arc.ilabel);
}
}
let has_compatible = fst1_outputs.intersection(&fst2_inputs).next().is_some()
|| fst1_has_epsilon_output || fst2_has_epsilon_input;
if !has_compatible {
return false;
}
if depth + 1 < self.max_depth {
for arc1 in fst1.arcs(state1) {
for arc2 in fst2.arcs(state2) {
let is_compatible = if arc1.is_epsilon() {
true
} else if arc2.is_epsilon() {
true
} else {
arc1.olabel == arc2.ilabel
};
if is_compatible {
if fst1.final_weight(arc1.nextstate).is_some()
|| fst2.final_weight(arc2.nextstate).is_some()
{
return true;
}
if self.has_compatible_future_paths(
fst1,
fst2,
arc1.nextstate,
arc2.nextstate,
depth + 1,
) {
return true;
}
}
}
}
}
true
}
}
impl<W: Semiring> LookaheadComposeFilter<W> for LabelPairLookaheadFilter {
fn filter_arc(
&self,
_fst1_state: StateId,
_fst2_state: StateId,
arc1: &Arc<W>,
arc2: &Arc<W>,
) -> bool {
use crate::fst::NO_LABEL;
arc1.olabel == arc2.ilabel
|| arc1.olabel == NO_LABEL || arc2.ilabel == NO_LABEL }
fn lookahead_path<F1: Fst<W>, F2: Fst<W>>(
&self,
fst1: &F1,
fst2: &F2,
next1: StateId,
next2: StateId,
) -> bool {
if fst1.final_weight(next1).is_some() || fst2.final_weight(next2).is_some() {
return true;
}
self.has_compatible_future_paths(fst1, fst2, next1, next2, 0)
}
}
#[derive(Debug)]
pub struct MatcherLookaheadFilter<W: Semiring, F: Fn(&Arc<W>, &Arc<W>) -> bool> {
matcher: F,
_phantom: std::marker::PhantomData<W>,
}
impl<W: Semiring, F: Fn(&Arc<W>, &Arc<W>) -> bool> MatcherLookaheadFilter<W, F> {
pub fn new(matcher: F) -> Self {
Self {
matcher,
_phantom: std::marker::PhantomData,
}
}
}
impl<W: Semiring, F: Fn(&Arc<W>, &Arc<W>) -> bool> LookaheadComposeFilter<W>
for MatcherLookaheadFilter<W, F>
{
fn filter_arc(
&self,
_fst1_state: StateId,
_fst2_state: StateId,
arc1: &Arc<W>,
arc2: &Arc<W>,
) -> bool {
(self.matcher)(arc1, arc2)
}
}
pub fn compose_with_lookahead<W, F1, F2, M, Filter>(
fst1: &F1,
fst2: &F2,
filter: Filter,
) -> Result<M>
where
W: Semiring + Clone,
F1: Fst<W>,
F2: Fst<W>,
M: crate::fst::MutableFst<W> + Default,
Filter: LookaheadComposeFilter<W>,
{
use std::collections::HashMap;
let start1 = fst1
.start()
.ok_or_else(|| crate::Error::Algorithm("First FST has no start state".into()))?;
let start2 = fst2
.start()
.ok_or_else(|| crate::Error::Algorithm("Second FST has no start state".into()))?;
let mut result = M::default();
let mut state_map = HashMap::new();
let mut queue = Vec::new();
let start_state = result.add_state();
result.set_start(start_state);
state_map.insert((start1, start2), start_state);
queue.push((start1, start2, start_state));
while let Some((s1, s2, current)) = queue.pop() {
if let (Some(w1), Some(w2)) = (fst1.final_weight(s1), fst2.final_weight(s2)) {
result.set_final(current, w1.times(w2));
}
use crate::fst::NO_LABEL;
for arc1 in fst1.arcs(s1) {
for arc2 in fst2.arcs(s2) {
if filter.filter_arc(s1, s2, &arc1, &arc2) {
let next1 = arc1.nextstate;
let next2 = arc2.nextstate;
if !filter.lookahead_path(fst1, fst2, next1, next2) {
continue; }
let next_key = (next1, next2);
let next_state = match state_map.get(&next_key) {
Some(&state) => state,
None => {
let state = result.add_state();
state_map.insert(next_key, state);
queue.push((next1, next2, state));
state
}
};
let composed_ilabel = if arc1.olabel == NO_LABEL && arc2.ilabel == NO_LABEL {
NO_LABEL } else {
arc1.ilabel };
let composed_olabel = if arc1.olabel == NO_LABEL && arc2.ilabel == NO_LABEL {
NO_LABEL } else {
arc2.olabel };
let composed_arc = crate::arc::Arc::new(
composed_ilabel,
composed_olabel,
arc1.weight.times(&arc2.weight),
next_state,
);
result.add_arc(current, composed_arc);
}
}
}
for arc1 in fst1.arcs(s1) {
if arc1.is_epsilon() {
let next1 = arc1.nextstate;
let next_key = (next1, s2);
if !filter.lookahead_path(fst1, fst2, next1, s2) {
continue;
}
let next_state = match state_map.get(&next_key) {
Some(&state) => state,
None => {
let state = result.add_state();
state_map.insert(next_key, state);
queue.push((next1, s2, state));
state
}
};
let epsilon_arc = crate::arc::Arc::epsilon(arc1.weight.clone(), next_state);
result.add_arc(current, epsilon_arc);
}
}
for arc2 in fst2.arcs(s2) {
if arc2.is_epsilon() {
let next2 = arc2.nextstate;
let next_key = (s1, next2);
if !filter.lookahead_path(fst1, fst2, s1, next2) {
continue;
}
let next_state = match state_map.get(&next_key) {
Some(&state) => state,
None => {
let state = result.add_state();
state_map.insert(next_key, state);
queue.push((s1, next2, state));
state
}
};
let epsilon_arc = crate::arc::Arc::epsilon(arc2.weight.clone(), next_state);
result.add_arc(current, epsilon_arc);
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_label_lookahead_filter() {
let filter = LabelLookaheadFilter;
let arc1 = Arc::new(1, 1, TropicalWeight::one(), 0);
let arc2 = Arc::new(1, 1, TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &arc1, &arc2));
let arc3 = Arc::new(3, 2, TropicalWeight::one(), 0);
assert!(!filter.filter_arc(0, 0, &arc1, &arc3));
let arc4 = Arc::new(1, 2, TropicalWeight::one(), 0);
let arc5 = Arc::new(2, 3, TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &arc4, &arc5));
let epsilon_arc1 = Arc::epsilon(TropicalWeight::one(), 0);
let arc6 = Arc::new(1, 2, TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &epsilon_arc1, &arc6));
let arc7 = Arc::new(1, 2, TropicalWeight::one(), 0);
let epsilon_arc2 = Arc::epsilon(TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &arc7, &epsilon_arc2));
assert!(filter.filter_arc(0, 0, &epsilon_arc1, &epsilon_arc2));
}
#[test]
fn test_matcher_lookahead_filter() {
let filter: MatcherLookaheadFilter<TropicalWeight, _> = MatcherLookaheadFilter::new(
|arc1: &Arc<TropicalWeight>, arc2: &Arc<TropicalWeight>| {
arc1.olabel == arc2.ilabel && *arc1.weight.value() == *arc2.weight.value()
},
);
let arc1 = Arc::new(1, 1, TropicalWeight::new(1.0), 0);
let arc2 = Arc::new(1, 1, TropicalWeight::new(1.0), 0);
assert!(filter.filter_arc(0, 0, &arc1, &arc2));
let arc3 = Arc::new(1, 1, TropicalWeight::new(2.0), 0);
assert!(!filter.filter_arc(0, 0, &arc1, &arc3));
}
#[test]
fn test_compose_with_lookahead() {
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, 1, TropicalWeight::one(), s1));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let s0 = fst2.add_state();
let s1 = fst2.add_state();
fst2.set_start(s0);
fst2.set_final(s1, TropicalWeight::one());
fst2.add_arc(s0, Arc::new(1, 2, TropicalWeight::one(), s1));
let filter = LabelLookaheadFilter;
let result: VectorFst<TropicalWeight> =
compose_with_lookahead(&fst1, &fst2, filter).unwrap();
assert!(result.num_states() > 0);
}
#[test]
fn test_label_pair_lookahead_filter() {
let filter = LabelPairLookaheadFilter::new();
let arc1 = Arc::new(1, 1, TropicalWeight::one(), 0);
let arc2 = Arc::new(1, 1, TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &arc1, &arc2));
let arc3 = Arc::new(3, 2, TropicalWeight::one(), 0);
assert!(!filter.filter_arc(0, 0, &arc1, &arc3));
let epsilon_arc = Arc::epsilon(TropicalWeight::one(), 0);
assert!(filter.filter_arc(0, 0, &epsilon_arc, &arc1)); assert!(filter.filter_arc(0, 0, &arc1, &epsilon_arc)); }
#[test]
fn test_compose_with_label_pair_lookahead() {
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, 1, TropicalWeight::one(), s1));
fst1.add_arc(s1, Arc::new(2, 2, TropicalWeight::one(), s2));
let mut fst2 = VectorFst::<TropicalWeight>::new();
let s0 = fst2.add_state();
let s1 = fst2.add_state();
let s2 = fst2.add_state();
fst2.set_start(s0);
fst2.set_final(s2, TropicalWeight::one());
fst2.add_arc(s0, Arc::new(1, 3, TropicalWeight::one(), s1));
fst2.add_arc(s1, Arc::new(2, 4, TropicalWeight::one(), s2));
let filter = LabelPairLookaheadFilter::with_max_depth(1);
let result: VectorFst<TropicalWeight> =
compose_with_lookahead(&fst1, &fst2, filter).unwrap();
assert!(result.num_states() > 0);
}
}