use crate::fst::{Fst, MutableFst, StateId};
use crate::semiring::{NaturallyOrderedSemiring, Semiring};
use crate::Result;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[derive(Debug)]
pub struct StreamingShortestPath<W: Semiring> {
queue: BinaryHeap<PathState<W>>,
distances: Vec<Option<W>>,
pub max_states: Option<usize>,
processed_states: usize,
}
#[derive(Clone, Debug)]
struct PathState<W: Semiring> {
state: StateId,
weight: W,
}
impl<W: NaturallyOrderedSemiring> PartialEq for PathState<W> {
fn eq(&self, other: &Self) -> bool {
self.weight == other.weight
}
}
impl<W: NaturallyOrderedSemiring> Eq for PathState<W> {}
impl<W: NaturallyOrderedSemiring> PartialOrd for PathState<W> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<W: NaturallyOrderedSemiring> Ord for PathState<W> {
fn cmp(&self, other: &Self) -> Ordering {
other.weight.cmp(&self.weight)
}
}
impl<W: Semiring> StreamingShortestPath<W>
where
W: Clone + NaturallyOrderedSemiring,
{
pub fn new(max_states: Option<usize>) -> Self {
Self {
queue: BinaryHeap::new(),
distances: Vec::new(),
max_states,
processed_states: 0,
}
}
pub fn process_next<F: Fst<W>>(&mut self, fst: &F) -> Option<(StateId, W)>
where
W: crate::semiring::NaturallyOrderedSemiring,
{
let start = fst.start()?;
if self.distances.is_empty() {
self.distances.resize(fst.num_states(), None);
self.queue.push(PathState {
state: start,
weight: W::one(),
});
}
if let Some(max) = self.max_states {
if self.processed_states >= max {
return None;
}
}
while let Some(PathState {
state,
weight: dist,
}) = self.queue.pop()
{
if let Some(ref best_dist) = self.distances[state as usize] {
if dist >= *best_dist {
continue;
}
}
self.distances[state as usize] = Some(dist.clone());
for arc in fst.arcs(state) {
let new_dist = dist.clone().times(&arc.weight);
let next_state = arc.nextstate;
let should_update = match &self.distances[next_state as usize] {
None => true,
Some(old_dist) => new_dist < *old_dist,
};
if should_update {
self.distances[next_state as usize] = Some(new_dist.clone());
self.queue.push(PathState {
state: next_state,
weight: new_dist,
});
}
}
self.processed_states += 1;
return Some((state, dist));
}
None
}
}
pub fn compose_bounded<W, F1, F2>(
fst1: &F1,
fst2: &F2,
max_states: usize,
) -> Result<crate::fst::VectorFst<W>>
where
W: Semiring + Clone,
F1: Fst<W>,
F2: Fst<W>,
{
use crate::algorithms::compose::ComposeFilter;
use crate::algorithms::compose::DefaultComposeFilter;
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 = crate::fst::VectorFst::default();
let mut state_map = HashMap::new();
let mut queue = Vec::new();
let filter = DefaultComposeFilter;
let start_state = result.add_state();
if result.num_states() > max_states {
return Err(crate::Error::Algorithm(format!(
"Composition would exceed limit of {} states (start state alone exceeds limit)",
max_states
)));
}
result.set_start(start_state);
state_map.insert(
(
start1,
start2,
<DefaultComposeFilter as ComposeFilter<W>>::start(),
),
start_state,
);
queue.push((
start1,
start2,
<DefaultComposeFilter as ComposeFilter<W>>::start(),
start_state,
));
while let Some((s1, s2, fs, current)) = queue.pop() {
if let (Some(w1), Some(w2)) = (fst1.final_weight(s1), fst2.final_weight(s2)) {
result.set_final(current, w1.times(w2));
}
for arc1 in fst1.arcs(s1) {
for arc2 in fst2.arcs(s2) {
if let Some((mut arc, next_fs)) = filter.filter_arc(&arc1, &arc2, &fs) {
let next_key = (arc1.nextstate, arc2.nextstate, next_fs);
let next_state = match state_map.get(&next_key) {
Some(&state) => state,
None => {
let state = result.add_state();
if result.num_states() >= max_states {
return Err(crate::Error::Algorithm(format!(
"Composition exceeded limit of {} states (reached {})",
max_states,
result.num_states()
)));
}
state_map.insert(next_key, state);
queue.push((arc1.nextstate, arc2.nextstate, next_fs, state));
state
}
};
arc.nextstate = next_state;
result.add_arc(current, arc);
}
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_streaming_shortest_path_new() {
let processor = StreamingShortestPath::<TropicalWeight>::new(None);
assert!(processor.max_states.is_none());
let processor2 = StreamingShortestPath::<TropicalWeight>::new(Some(100));
assert_eq!(processor2.max_states, Some(100));
}
#[test]
fn test_streaming_shortest_path_process_next() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::one(), s1));
let mut processor = StreamingShortestPath::<TropicalWeight>::new(None);
let result = processor.process_next(&fst);
assert!(result.is_some());
let (state, weight) = result.unwrap();
assert_eq!(state, s0);
assert_eq!(*weight.value(), 0.0);
}
#[test]
fn test_compose_bounded() {
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 result = compose_bounded(&fst1, &fst2, 1000).unwrap();
assert!(result.num_states() > 0);
}
#[test]
fn test_compose_bounded_limit() {
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 result = compose_bounded(&fst1, &fst2, 10).unwrap();
assert!(result.num_states() <= 10);
}
}