use std::collections::VecDeque;
use std::hash::Hash;
use rustc_hash::FxHashMap;
use super::{LazyComposition, ProductStateId};
use crate::semiring::Semiring;
use crate::wfst::{MutableWfst, StateId, VectorWfst, Wfst};
pub fn materialize<F1, F2, L, W>(mut lazy: LazyComposition<F1, F2, L, W>) -> VectorWfst<L, W>
where
F1: Wfst<L, W>,
F2: Wfst<L, W>,
L: Clone + Eq + Hash + Send + Sync,
W: Semiring,
{
let mut result: VectorWfst<L, W> = VectorWfst::new();
let mut state_map: FxHashMap<ProductStateId, StateId> = FxHashMap::default();
let mut queue: VecDeque<ProductStateId> = VecDeque::new();
let start_product = lazy.start();
let start_id = result.add_state();
result.set_start(start_id);
state_map.insert(start_product, start_id);
queue.push_back(start_product);
while let Some(product_state) = queue.pop_front() {
let current_id = *state_map
.get(&product_state)
.expect("state should be in map");
if lazy.is_final(product_state) {
let final_weight = lazy.final_weight(product_state);
result.set_final(current_id, final_weight);
}
let transitions = lazy.transitions(product_state);
for trans in transitions {
let target_id = if let Some(&id) = state_map.get(&trans.target) {
id
} else {
let new_id = result.add_state();
state_map.insert(trans.target, new_id);
queue.push_back(trans.target);
new_id
};
result.add_arc(
current_id,
trans.input.clone(),
trans.output.clone(),
target_id,
trans.weight,
);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::composition::compose;
use crate::semiring::TropicalWeight;
use crate::wfst::VectorWfstBuilder;
#[test]
fn test_materialize_simple() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::new(1.0))
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('x'), Some('b'), 1, TropicalWeight::new(0.5))
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
assert!(result.num_states() > 0);
assert_eq!(result.start(), 0);
assert!(!result.transitions(0).is_empty());
}
#[test]
fn test_materialize_chain() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(3)
.start(0)
.final_state(2, TropicalWeight::one())
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::new(1.0))
.arc(1, Some('b'), Some('y'), 2, TropicalWeight::new(1.0))
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(3)
.start(0)
.final_state(2, TropicalWeight::one())
.arc(0, Some('x'), Some('p'), 1, TropicalWeight::new(0.5))
.arc(1, Some('y'), Some('q'), 2, TropicalWeight::new(0.5))
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
assert!(result.num_states() >= 2);
let final_count = (0..result.num_states() as StateId)
.filter(|&s| result.is_final(s))
.count();
assert!(final_count >= 1);
}
#[test]
fn test_materialize_no_match() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::new(1.0))
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('z'), Some('b'), 1, TropicalWeight::new(0.5))
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
assert!(result.num_states() >= 1);
let has_final = (0..result.num_states() as StateId).any(|s| result.is_final(s));
assert!(!has_final);
}
#[test]
fn test_materialize_multiple_paths() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::new(1.0))
.arc(0, Some('b'), Some('x'), 1, TropicalWeight::new(2.0))
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::one())
.arc(0, Some('x'), Some('y'), 1, TropicalWeight::new(0.5))
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
assert!(result.num_states() >= 2);
assert_eq!(result.transitions(0).len(), 2);
}
#[test]
fn test_materialize_preserves_weights() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::new(0.1))
.arc(0, Some('a'), Some('x'), 1, TropicalWeight::new(1.0))
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(2)
.start(0)
.final_state(1, TropicalWeight::new(0.2))
.arc(0, Some('x'), Some('b'), 1, TropicalWeight::new(0.5))
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
let trans = result.transitions(0);
assert_eq!(trans.len(), 1);
assert_eq!(trans[0].weight.value(), 1.5);
let final_state = trans[0].to;
assert!(result.is_final(final_state));
assert!((result.final_weight(final_state).value() - 0.3).abs() < 1e-9);
}
#[test]
fn test_materialize_empty_composition() {
let fst1 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(1)
.start(0)
.final_state(0, TropicalWeight::one())
.build();
let fst2 = VectorWfstBuilder::<char, TropicalWeight>::new()
.add_states(1)
.start(0)
.final_state(0, TropicalWeight::one())
.build();
let lazy = compose(fst1, fst2);
let result = materialize(lazy);
assert_eq!(result.num_states(), 1);
assert!(result.is_final(0));
}
}