use crate::arc::Arc;
use crate::fst::{Fst, Label, MutableFst, StateId};
use crate::properties::PropertyFlags;
use crate::semiring::{NaturallyOrderedSemiring, Semiring};
use crate::{Error, Result};
use core::cmp::Ordering;
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::BinaryHeap;
#[derive(Debug, Clone)]
pub struct ShortestPathConfig {
pub nshortest: usize,
pub unique: bool,
}
impl Default for ShortestPathConfig {
fn default() -> Self {
Self {
nshortest: 1,
unique: false,
}
}
}
#[derive(Clone, Debug)]
struct Path<W: Semiring> {
states: Vec<StateId>,
arcs: Vec<Arc<W>>,
weight: W,
final_state: StateId,
}
impl<W: NaturallyOrderedSemiring> PartialEq for Path<W> {
fn eq(&self, other: &Self) -> bool {
self.weight == other.weight
}
}
impl<W: NaturallyOrderedSemiring> Eq for Path<W> {}
impl<W: NaturallyOrderedSemiring> PartialOrd for Path<W> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<W: NaturallyOrderedSemiring> Ord for Path<W> {
fn cmp(&self, other: &Self) -> Ordering {
other.weight.cmp(&self.weight)
}
}
#[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)
}
}
pub fn shortest_path<W, F, M>(fst: &F, config: ShortestPathConfig) -> Result<M>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
M: MutableFst<W> + Default,
{
if config.nshortest == 0 {
return Ok(M::default());
}
let start = fst
.start()
.ok_or_else(|| Error::Algorithm("FST has no start state".into()))?;
if config.nshortest == 1 && !config.unique {
if let Some(path) = shortest_path_single_optimized(fst, start)? {
return build_paths_fst(fst, &[path]);
} else {
return Ok(M::default());
}
}
let paths = yen_k_shortest_paths(fst, start, config.nshortest, config.unique)?;
build_paths_fst(fst, &paths)
}
fn yen_k_shortest_paths<W, F>(
fst: &F,
start: StateId,
k: usize,
unique: bool,
) -> Result<Vec<Path<W>>>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
{
let first_path = match dijkstra_shortest_path(fst, start, &FxHashSet::default())? {
Some(path) => path,
None => return Ok(Vec::new()), };
let mut result_paths = vec![first_path];
let mut candidate_paths = BinaryHeap::<Path<W>>::new();
let mut seen_sequences = FxHashSet::default();
if unique {
let seq = extract_io_sequence(&result_paths[0]);
seen_sequences.insert(seq);
}
for _ in 1..k {
let prev_path = &result_paths[result_paths.len() - 1];
for spur_index in 0..prev_path.states.len() {
let spur_node = prev_path.states[spur_index];
let root_path_states = &prev_path.states[0..=spur_index];
let mut excluded_edges = FxHashSet::default();
for existing_path in &result_paths {
if existing_path.states.len() > spur_index
&& existing_path.states[0..=spur_index] == root_path_states[..]
{
if existing_path.arcs.len() > spur_index {
let arc = &existing_path.arcs[spur_index];
excluded_edges.insert((spur_node, arc.ilabel, arc.olabel, arc.nextstate));
}
}
}
let excluded_nodes: FxHashSet<StateId> = root_path_states.iter().copied().collect();
if let Some(spur_path) =
dijkstra_shortest_path_from_node(fst, spur_node, &excluded_edges, &excluded_nodes)?
{
let candidate = if spur_index == 0 {
spur_path
} else {
let root_weight = prev_path.states[0..spur_index]
.iter()
.zip(&prev_path.arcs[0..spur_index])
.fold(W::one(), |w, (_, arc)| w.times(&arc.weight));
Path {
states: [&prev_path.states[0..spur_index], &spur_path.states[..]].concat(),
arcs: [&prev_path.arcs[0..spur_index], &spur_path.arcs[..]].concat(),
weight: root_weight.times(&spur_path.weight),
final_state: spur_path.final_state,
}
};
if unique {
let seq = extract_io_sequence(&candidate);
if seen_sequences.contains(&seq) {
continue;
}
}
let mut is_duplicate = false;
for existing in &result_paths {
if paths_equal(&candidate, existing) {
is_duplicate = true;
break;
}
}
if !is_duplicate {
candidate_paths.push(candidate);
}
}
}
match candidate_paths.pop() {
Some(best) => {
if unique {
let seq = extract_io_sequence(&best);
seen_sequences.insert(seq);
}
result_paths.push(best);
}
None => break, }
}
Ok(result_paths)
}
fn dijkstra_shortest_path<W, F>(
fst: &F,
start: StateId,
excluded_edges: &FxHashSet<(StateId, Label, Label, StateId)>,
) -> Result<Option<Path<W>>>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
{
dijkstra_shortest_path_from_node(fst, start, excluded_edges, &FxHashSet::default())
}
fn dijkstra_shortest_path_from_node<W, F>(
fst: &F,
start_node: StateId,
excluded_edges: &FxHashSet<(StateId, Label, Label, StateId)>,
excluded_nodes: &FxHashSet<StateId>,
) -> Result<Option<Path<W>>>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
{
let mut distance = vec![W::zero(); fst.num_states()];
let mut parent: Vec<Option<(StateId, Arc<W>)>> = vec![None; fst.num_states()];
let mut heap = BinaryHeap::new();
let mut visited = FxHashSet::default();
distance[start_node as usize] = W::one();
heap.push(PathState {
state: start_node,
weight: W::one(),
});
let mut best_final: Option<(StateId, W)> = None;
while let Some(PathState { state, weight, .. }) = heap.pop() {
if weight > distance[state as usize] {
continue;
}
if visited.contains(&state) {
continue;
}
visited.insert(state);
if let Some(final_weight) = fst.final_weight(state) {
let total = weight.times(final_weight);
match &best_final {
None => best_final = Some((state, total)),
Some((_, best_w)) if total < *best_w => best_final = Some((state, total)),
_ => {}
}
}
for arc in fst.arcs(state) {
let next_state = arc.nextstate;
if excluded_edges.contains(&(state, arc.ilabel, arc.olabel, next_state)) {
continue;
}
if excluded_nodes.contains(&next_state) && next_state != start_node {
continue;
}
let next_weight = weight.times(&arc.weight);
if <W as num_traits::Zero>::is_zero(&distance[next_state as usize])
|| next_weight < distance[next_state as usize]
{
distance[next_state as usize] = next_weight.clone();
parent[next_state as usize] = Some((state, arc.clone()));
heap.push(PathState {
state: next_state,
weight: next_weight,
});
}
}
}
if let Some((final_state, final_weight)) = best_final {
let mut states = vec![final_state];
let mut arcs = Vec::new();
let mut current = final_state;
while let Some((prev_state, arc)) = &parent[current as usize] {
states.push(*prev_state);
arcs.push(arc.clone());
current = *prev_state;
}
states.reverse();
arcs.reverse();
Ok(Some(Path {
states,
arcs,
weight: final_weight,
final_state,
}))
} else {
Ok(None)
}
}
fn compute_topological_order<W, F>(fst: &F) -> Option<Vec<StateId>>
where
W: Semiring,
F: Fst<W>,
{
let num_states = fst.num_states();
let mut visited = vec![false; num_states];
let mut rec_stack = vec![false; num_states];
let mut order = Vec::with_capacity(num_states);
fn dfs<W2: Semiring, F2: Fst<W2>>(
fst: &F2,
state: StateId,
visited: &mut [bool],
rec_stack: &mut [bool],
order: &mut Vec<StateId>,
) -> bool {
let idx = state as usize;
if rec_stack[idx] {
return false; }
if visited[idx] {
return true;
}
visited[idx] = true;
rec_stack[idx] = true;
for arc in fst.arcs(state) {
if !dfs(fst, arc.nextstate, visited, rec_stack, order) {
return false;
}
}
rec_stack[idx] = false;
order.push(state);
true
}
if let Some(start) = fst.start() {
if !dfs(fst, start, &mut visited, &mut rec_stack, &mut order) {
return None; }
}
for state in 0..num_states as StateId {
if !visited[state as usize] && !dfs(fst, state, &mut visited, &mut rec_stack, &mut order) {
return None; }
}
order.reverse();
Some(order)
}
fn shortest_path_acyclic<W, F>(fst: &F, start: StateId) -> Result<Option<Path<W>>>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
{
let topo_order = match compute_topological_order(fst) {
Some(order) => order,
None => return Err(Error::Algorithm("FST has cycles".into())),
};
let num_states = fst.num_states();
let mut distance = vec![W::zero(); num_states];
let mut parent: Vec<Option<(StateId, Arc<W>)>> = vec![None; num_states];
distance[start as usize] = W::one();
let start_pos = topo_order.iter().position(|&s| s == start).unwrap_or(0);
for &state in &topo_order[start_pos..] {
let state_dist = distance[state as usize].clone();
if <W as num_traits::Zero>::is_zero(&state_dist) && state != start {
continue;
}
for arc in fst.arcs(state) {
let next_state = arc.nextstate;
let next_weight = state_dist.times(&arc.weight);
if <W as num_traits::Zero>::is_zero(&distance[next_state as usize])
|| next_weight < distance[next_state as usize]
{
distance[next_state as usize] = next_weight;
parent[next_state as usize] = Some((state, arc.clone()));
}
}
}
let mut best_final: Option<(StateId, W)> = None;
for state in 0..num_states as StateId {
if !<W as num_traits::Zero>::is_zero(&distance[state as usize]) {
if let Some(final_weight) = fst.final_weight(state) {
let total = distance[state as usize].times(final_weight);
match &best_final {
None => best_final = Some((state, total)),
Some((_, best_w)) if total < *best_w => best_final = Some((state, total)),
_ => {}
}
}
}
}
if let Some((final_state, final_weight)) = best_final {
let mut states = vec![final_state];
let mut arcs = Vec::new();
let mut current = final_state;
while let Some((prev_state, arc)) = &parent[current as usize] {
states.push(*prev_state);
arcs.push(arc.clone());
current = *prev_state;
}
states.reverse();
arcs.reverse();
Ok(Some(Path {
states,
arcs,
weight: final_weight,
final_state,
}))
} else {
Ok(None)
}
}
fn shortest_path_single_optimized<W, F>(fst: &F, start: StateId) -> Result<Option<Path<W>>>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
{
let props = fst.properties();
if props.has_property(PropertyFlags::ACYCLIC) {
shortest_path_acyclic(fst, start)
} else {
if compute_topological_order(fst).is_some() {
shortest_path_acyclic(fst, start)
} else {
dijkstra_shortest_path(fst, start, &FxHashSet::default())
}
}
}
fn build_paths_fst<W, F, M>(fst: &F, paths: &[Path<W>]) -> Result<M>
where
W: Semiring,
F: Fst<W>,
M: MutableFst<W> + Default,
{
let mut result = M::default();
if paths.is_empty() {
return Ok(result);
}
let mut state_map: FxHashMap<StateId, StateId> = FxHashMap::default();
fn get_or_create_state<W2: Semiring, M2: MutableFst<W2>>(
result: &mut M2,
s: StateId,
map: &mut FxHashMap<StateId, StateId>,
) -> StateId {
if let Some(&new_s) = map.get(&s) {
new_s
} else {
let new_s = result.add_state();
map.insert(s, new_s);
new_s
}
}
let start = paths[0].states[0];
let new_start = get_or_create_state(&mut result, start, &mut state_map);
result.set_start(new_start);
for path in paths {
for i in 0..path.arcs.len() {
let from_state = path.states[i];
let to_state = path.states[i + 1];
let arc = &path.arcs[i];
let new_from = get_or_create_state(&mut result, from_state, &mut state_map);
let new_to = get_or_create_state(&mut result, to_state, &mut state_map);
result.add_arc(
new_from,
Arc::new(arc.ilabel, arc.olabel, arc.weight.clone(), new_to),
);
}
if let Some(final_weight) = fst.final_weight(path.final_state) {
let new_final = get_or_create_state(&mut result, path.final_state, &mut state_map);
result.set_final(new_final, final_weight.clone());
}
}
Ok(result)
}
fn paths_equal<W: Semiring + PartialEq>(p1: &Path<W>, p2: &Path<W>) -> bool {
if p1.states.len() != p2.states.len() {
return false;
}
if p1.arcs.len() != p2.arcs.len() {
return false;
}
for i in 0..p1.states.len() {
if p1.states[i] != p2.states[i] {
return false;
}
}
for i in 0..p1.arcs.len() {
if p1.arcs[i].ilabel != p2.arcs[i].ilabel
|| p1.arcs[i].olabel != p2.arcs[i].olabel
|| p1.arcs[i].nextstate != p2.arcs[i].nextstate
|| p1.arcs[i].weight != p2.arcs[i].weight
{
return false;
}
}
true
}
fn extract_io_sequence<W: Semiring>(path: &Path<W>) -> Vec<(Label, Label)> {
path.arcs
.iter()
.map(|arc| (arc.ilabel, arc.olabel))
.collect()
}
pub fn shortest_path_single<W, F, M>(fst: &F) -> Result<M>
where
W: NaturallyOrderedSemiring,
F: Fst<W>,
M: MutableFst<W> + Default,
{
shortest_path(fst, ShortestPathConfig::default())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use num_traits::One;
#[test]
fn test_shortest_path_single() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(2.0), s2));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(5.0), s2));
let shortest: VectorFst<TropicalWeight> = shortest_path_single(&fst).unwrap();
assert!(shortest.start().is_some());
assert!(shortest.num_states() >= 2);
}
#[test]
fn test_k_shortest_paths_simple() {
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::new(1.0), s1));
fst.add_arc(s0, Arc::new(2, 2, TropicalWeight::new(2.0), s1));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(3.0), s1));
let config = ShortestPathConfig {
nshortest: 3,
unique: false,
};
let k_best: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(k_best.start().is_some());
assert!(k_best.num_states() >= 2);
}
#[test]
fn test_k_shortest_paths_complex() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s3));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(2.0), s2));
fst.add_arc(s2, Arc::new(4, 4, TropicalWeight::new(1.0), s3));
let config = ShortestPathConfig {
nshortest: 2,
unique: false,
};
let k_best: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(k_best.start().is_some());
assert!(k_best.num_states() >= 2);
}
#[test]
fn test_shortest_path_empty() {
let fst = VectorFst::<TropicalWeight>::new();
if let Ok(shortest) = shortest_path_single::<
TropicalWeight,
VectorFst<TropicalWeight>,
VectorFst<TropicalWeight>,
>(&fst)
{
assert!(shortest.is_empty());
}
}
#[test]
fn test_shortest_path_config_default() {
let config = ShortestPathConfig::default();
assert_eq!(config.nshortest, 1);
assert!(!config.unique);
}
#[test]
fn test_shortest_path_config_custom() {
let config = ShortestPathConfig {
nshortest: 5,
unique: true,
};
assert_eq!(config.nshortest, 5);
assert!(config.unique);
}
#[test]
fn test_shortest_path_single_state() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, TropicalWeight::new(2.0));
let shortest: VectorFst<TropicalWeight> = shortest_path_single(&fst).unwrap();
assert_eq!(shortest.num_states(), 1);
assert_eq!(shortest.start(), Some(0));
assert!(shortest.is_final(0));
}
#[test]
fn test_shortest_path_no_final_states() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_start(s0);
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::one(), s1));
let shortest: VectorFst<TropicalWeight> = shortest_path_single(&fst).unwrap();
assert_eq!(shortest.num_states(), 0);
}
#[test]
fn test_shortest_path_zero_nshortest() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
fst.set_start(s0);
fst.set_final(s0, TropicalWeight::one());
let config = ShortestPathConfig {
nshortest: 0,
unique: false,
};
let shortest: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert_eq!(shortest.num_states(), 0);
}
#[test]
fn test_shortest_path_with_weights() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(0.1), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(0.2), s3));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(1.0), s2));
fst.add_arc(s2, Arc::new(4, 4, TropicalWeight::new(2.0), s3));
let shortest: VectorFst<TropicalWeight> = shortest_path_single(&fst).unwrap();
assert!(shortest.start().is_some());
assert!(shortest.num_states() > 0);
}
#[test]
fn test_shortest_path_linear_chain() {
let mut fst = VectorFst::<TropicalWeight>::new();
let states: Vec<_> = (0..5).map(|_| fst.add_state()).collect();
fst.set_start(states[0]);
fst.set_final(states[4], TropicalWeight::one());
for i in 0..4 {
fst.add_arc(
states[i],
Arc::new(
(i + 1) as u32,
(i + 1) as u32,
TropicalWeight::new(i as f32 * 0.1),
states[i + 1],
),
);
}
let shortest: VectorFst<TropicalWeight> = shortest_path_single(&fst).unwrap();
assert_eq!(shortest.start(), Some(0));
assert!(shortest.num_states() > 0);
}
#[test]
fn test_k_shortest_avoids_cycles() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s0));
fst.add_arc(s1, Arc::new(3, 3, TropicalWeight::new(1.0), s2));
let config = ShortestPathConfig {
nshortest: 5,
unique: false,
};
let k_best: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(k_best.start().is_some());
}
#[test]
fn test_unique_paths() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s2));
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.5), s1));
let config = ShortestPathConfig {
nshortest: 5,
unique: true,
};
let unique_result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(unique_result.start().is_some());
}
#[test]
fn test_k_shortest_more_than_available() {
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::new(1.0), s1));
fst.add_arc(s0, Arc::new(2, 2, TropicalWeight::new(2.0), s1));
let config = ShortestPathConfig {
nshortest: 10,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
}
#[test]
fn test_k_shortest_paths_ordering() {
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::new(5.0), s1));
fst.add_arc(s0, Arc::new(2, 2, TropicalWeight::new(1.0), s1));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(3.0), s1));
fst.add_arc(s0, Arc::new(4, 4, TropicalWeight::new(2.0), s1));
fst.add_arc(s0, Arc::new(5, 5, TropicalWeight::new(4.0), s1));
let config = ShortestPathConfig {
nshortest: 5,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
assert!(result.num_states() >= 2);
let start = result.start().unwrap();
let arc_count = result.num_arcs(start);
assert_eq!(arc_count, 5, "Should have all 5 paths");
}
#[test]
fn test_k_shortest_complex_network() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
let s4 = fst.add_state();
fst.set_start(s0);
fst.set_final(s4, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s4));
fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(1.5), s2));
fst.add_arc(s2, Arc::new(4, 4, TropicalWeight::new(1.0), s4));
fst.add_arc(s1, Arc::new(5, 5, TropicalWeight::new(0.5), s3));
fst.add_arc(s3, Arc::new(6, 6, TropicalWeight::new(1.0), s4));
fst.add_arc(s2, Arc::new(7, 7, TropicalWeight::new(0.5), s3));
let config = ShortestPathConfig {
nshortest: 4,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
assert!(result.num_states() >= 2);
}
#[test]
fn test_k_shortest_with_final_weights() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s1, TropicalWeight::new(0.5)); fst.set_final(s2, TropicalWeight::new(2.0));
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s0, Arc::new(2, 2, TropicalWeight::new(1.0), s2));
let config = ShortestPathConfig {
nshortest: 2,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
let start = result.start().unwrap();
assert_eq!(result.num_arcs(start), 2);
}
#[test]
fn test_yen_loopless_property() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s2));
fst.add_arc(s1, Arc::new(3, 3, TropicalWeight::new(0.1), s0));
let config = ShortestPathConfig {
nshortest: 10,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
assert!(result.num_states() <= 10, "Should not explode with cycles");
}
#[test]
fn test_k_shortest_multiple_finals() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s2, TropicalWeight::one());
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s2)); fst.add_arc(s1, Arc::new(3, 3, TropicalWeight::new(0.5), s3));
let config = ShortestPathConfig {
nshortest: 2,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
assert!(
result.is_final(2) || result.is_final(3),
"Should have at least one final state"
);
}
#[test]
fn test_unique_filtering_actually_works() {
let mut fst = VectorFst::<TropicalWeight>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
let s3 = fst.add_state();
fst.set_start(s0);
fst.set_final(s3, TropicalWeight::one());
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(1.0), s1));
fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(2.0), s3));
fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(2.0), s2));
fst.add_arc(s2, Arc::new(2, 2, TropicalWeight::new(3.0), s3));
let config_all = ShortestPathConfig {
nshortest: 5,
unique: false,
};
let result_all: VectorFst<TropicalWeight> = shortest_path(&fst, config_all).unwrap();
let start = result_all.start().unwrap();
let arc_count_all = result_all.num_arcs(start);
assert_eq!(
arc_count_all, 2,
"Should have 2 paths without unique filtering"
);
let config_unique = ShortestPathConfig {
nshortest: 5,
unique: true,
};
let result_unique: VectorFst<TropicalWeight> = shortest_path(&fst, config_unique).unwrap();
let start_unique = result_unique.start().unwrap();
let arc_count_unique = result_unique.num_arcs(start_unique);
assert_eq!(
arc_count_unique, 1,
"Should have only 1 path with unique filtering"
);
}
#[test]
fn test_k_shortest_paths_stress() {
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());
for i in 0..20 {
fst.add_arc(
s0,
Arc::new(i as u32, i as u32, TropicalWeight::new(i as f32), s1),
);
}
let config = ShortestPathConfig {
nshortest: 20,
unique: false,
};
let result: VectorFst<TropicalWeight> = shortest_path(&fst, config).unwrap();
assert!(result.start().is_some());
let start = result.start().unwrap();
assert_eq!(result.num_arcs(start), 20, "Should find all 20 paths");
}
}