use std::cell::Cell;
use std::collections::VecDeque;
use std::marker::PhantomData;
use crate::algorithms::cc_visitors::SccVisitor;
use crate::algorithms::dfs_visit::dfs_visit_any;
use crate::algorithms::topsort::TopOrderVisitor;
use crate::arc::{Arc, ArcStateId};
use crate::data_structures::bit_set::GrowableBitSet;
use crate::data_structures::indexed_heap::IndexedHeap;
use crate::fst::Fst;
use crate::properties::{K_ACYCLIC, K_TOP_SORTED, K_UNWEIGHTED};
use crate::weight::{IDEMPOTENT, PATH, Weight};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueueType {
Trivial,
Fifo,
Lifo,
ShortestFirst,
TopOrder,
StateOrder,
Scc,
Auto,
Other,
}
pub trait Queue<S> {
fn head(&self) -> Option<S>;
fn enqueue(&mut self, state: S);
fn dequeue(&mut self) -> Option<S>;
fn update(&mut self, _state: S) {}
fn is_empty(&self) -> bool {
self.head().is_none()
}
fn clear(&mut self);
fn queue_type(&self) -> QueueType {
QueueType::Other
}
}
#[derive(Debug, Clone, Default)]
pub struct TrivialQueue<S> {
front: Option<S>,
}
impl<S> TrivialQueue<S> {
pub fn new() -> Self {
Self { front: None }
}
}
impl<S: Copy> Queue<S> for TrivialQueue<S> {
fn head(&self) -> Option<S> {
self.front
}
fn enqueue(&mut self, state: S) {
self.front = Some(state);
}
fn dequeue(&mut self) -> Option<S> {
self.front.take()
}
fn clear(&mut self) {
self.front = None;
}
fn queue_type(&self) -> QueueType {
QueueType::Trivial
}
}
#[derive(Debug, Clone, Default)]
pub struct FifoQueue<S> {
states: VecDeque<S>,
}
impl<S> FifoQueue<S> {
pub fn new() -> Self {
Self {
states: VecDeque::new(),
}
}
}
impl<S: Copy> Queue<S> for FifoQueue<S> {
fn head(&self) -> Option<S> {
self.states.front().copied()
}
fn enqueue(&mut self, state: S) {
self.states.push_back(state);
}
fn dequeue(&mut self) -> Option<S> {
self.states.pop_front()
}
fn is_empty(&self) -> bool {
self.states.is_empty()
}
fn clear(&mut self) {
self.states.clear();
}
fn queue_type(&self) -> QueueType {
QueueType::Fifo
}
}
#[derive(Debug, Clone, Default)]
pub struct LifoQueue<S> {
states: Vec<S>,
}
impl<S> LifoQueue<S> {
pub fn new() -> Self {
Self { states: Vec::new() }
}
}
impl<S: Copy> Queue<S> for LifoQueue<S> {
fn head(&self) -> Option<S> {
self.states.last().copied()
}
fn enqueue(&mut self, state: S) {
self.states.push(state);
}
fn dequeue(&mut self) -> Option<S> {
self.states.pop()
}
fn is_empty(&self) -> bool {
self.states.is_empty()
}
fn clear(&mut self) {
self.states.clear();
}
fn queue_type(&self) -> QueueType {
QueueType::Lifo
}
}
pub struct ShortestFirstQueue<S, C, const UPDATE: bool = true> {
heap: IndexedHeap<S, C>,
keys: Vec<Option<usize>>,
}
impl<S, C> ShortestFirstQueue<S, C, true>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
{
pub fn new(comp: C) -> Self {
Self {
heap: IndexedHeap::new(comp),
keys: Vec::new(),
}
}
}
impl<S, C> ShortestFirstQueue<S, C, false>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
{
pub fn without_update(comp: C) -> Self {
Self {
heap: IndexedHeap::new(comp),
keys: Vec::new(),
}
}
}
impl<S, C, const UPDATE: bool> ShortestFirstQueue<S, C, UPDATE>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
{
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
}
impl<S, C, const UPDATE: bool> Queue<S> for ShortestFirstQueue<S, C, UPDATE>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
{
fn head(&self) -> Option<S> {
self.heap.top().copied()
}
fn enqueue(&mut self, state: S) {
let key = self.heap.insert(state);
if UPDATE {
let index = state.as_usize();
if self.keys.len() <= index {
self.keys.resize(index + 1, None);
}
self.keys[index] = Some(key);
}
}
fn dequeue(&mut self) -> Option<S> {
let state = self.heap.pop()?;
if UPDATE {
self.keys[state.as_usize()] = None;
}
Some(state)
}
fn update(&mut self, state: S) {
if !UPDATE {
return;
}
match self.keys.get(state.as_usize()).copied().flatten() {
Some(key) => self.heap.update(key, state),
None => self.enqueue(state),
}
}
fn is_empty(&self) -> bool {
self.heap.is_empty()
}
fn clear(&mut self) {
self.heap.clear();
self.keys.clear();
}
fn queue_type(&self) -> QueueType {
QueueType::ShortestFirst
}
}
#[inline]
pub fn natural_less_unchecked<W: Weight>(lhs: &W, rhs: &W) -> bool {
lhs != rhs && lhs.plus(rhs) == *lhs
}
pub fn state_weight_compare<S, W, L>(
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
less: L,
) -> impl Fn(&S, &S) -> bool + Clone
where
S: ArcStateId,
W: Weight,
L: Fn(&W, &W) -> bool + Clone,
{
move |x: &S, y: &S| {
let distance = distance.borrow();
match (distance.get(x.as_usize()), distance.get(y.as_usize())) {
(Some(wx), Some(wy)) => less(wx, wy),
(Some(_), None) => true,
_ => false,
}
}
}
pub fn natural_state_order<S, W>(
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
) -> impl Fn(&S, &S) -> bool + Clone
where
S: ArcStateId,
W: crate::weight::IdempotentWeight,
{
state_weight_compare(distance, |a: &W, b: &W| crate::weight::natural_less(a, b))
}
pub fn trivial_estimate<S, W: Weight>() -> impl Fn(&S) -> W + Clone {
|_: &S| W::one()
}
pub fn distance_estimate<S, W>(
beta: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
) -> impl Fn(&S) -> W + Clone
where
S: ArcStateId,
W: Weight,
{
move |s: &S| {
beta.borrow()
.get(s.as_usize())
.cloned()
.unwrap_or_else(W::zero)
}
}
pub fn a_star_compare<S, W, L, E>(
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
less: L,
estimate: E,
) -> impl Fn(&S, &S) -> bool + Clone
where
S: ArcStateId,
W: Weight,
L: Fn(&W, &W) -> bool + Clone,
E: Fn(&S) -> W + Clone,
{
move |x: &S, y: &S| {
let distance = distance.borrow();
match (distance.get(x.as_usize()), distance.get(y.as_usize())) {
(Some(wx), Some(wy)) => less(&wx.times(&estimate(x)), &wy.times(&estimate(y))),
(Some(_), None) => true,
_ => false,
}
}
}
pub struct PruneShortestFirstQueue<S, C, W> {
inner: ShortestFirstQueue<S, C>,
steps: Vec<usize>,
arc_threshold: Option<usize>,
state_limit: Option<usize>,
head_steps: Cell<usize>,
max_head_steps: Cell<usize>,
_marker: PhantomData<W>,
}
impl<S, C, W> PruneShortestFirstQueue<S, C, W>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
W: Weight,
{
pub fn new(comp: C, arc_threshold: Option<usize>, state_limit: Option<usize>) -> Self {
Self {
inner: ShortestFirstQueue::new(comp),
steps: Vec::new(),
arc_threshold,
state_limit,
head_steps: Cell::new(0),
max_head_steps: Cell::new(0),
_marker: PhantomData,
}
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<S, C, W> Queue<S> for PruneShortestFirstQueue<S, C, W>
where
S: ArcStateId,
C: Fn(&S, &S) -> bool,
W: Weight,
{
fn head(&self) -> Option<S> {
let head = self.inner.head()?;
if let Some(&steps) = self.steps.get(head.as_usize()) {
self.max_head_steps
.set(self.max_head_steps.get().max(steps));
self.head_steps.set(steps);
}
Some(head)
}
fn enqueue(&mut self, state: S) {
let state_steps = self.head_steps.get() + 1;
let index = state.as_usize();
if index >= self.steps.len() {
self.steps.resize(index + 1, state_steps);
}
self.steps[index] = state_steps;
let Some(arc_threshold) = self.arc_threshold else {
self.inner.enqueue(state);
return;
};
let adjusted = match self.state_limit {
Some(limit) if limit > 0 && self.inner.len() > limit => {
arc_threshold.saturating_sub(self.inner.len() / limit + 1)
}
_ => arc_threshold,
};
if state_steps > self.max_head_steps.get().saturating_sub(adjusted) {
if adjusted == 0 && self.state_limit.is_some_and(|limit| limit > 0) {
self.inner.clear();
}
self.inner.enqueue(state);
}
}
fn dequeue(&mut self) -> Option<S> {
self.head()?;
self.inner.dequeue()
}
fn update(&mut self, state: S) {
self.inner.update(state);
}
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
fn clear(&mut self) {
self.inner.clear();
self.steps.clear();
self.head_steps.set(0);
self.max_head_steps.set(0);
}
fn queue_type(&self) -> QueueType {
QueueType::ShortestFirst
}
}
pub struct TopOrderQueue<S> {
order: Vec<S>,
at: Vec<Option<S>>,
front: usize,
back: usize,
}
impl<S: ArcStateId> TopOrderQueue<S> {
pub fn with_order(order: Vec<S>) -> Self {
let len = order.len();
Self {
order,
at: vec![None; len],
front: 0,
back: 0,
}
}
pub fn new<A, F>(fst: &F) -> Option<Self>
where
A: Arc<StateId = S>,
F: Fst<A>,
{
let mut visitor = TopOrderVisitor::<A>::new();
dfs_visit_any(fst, &mut visitor);
visitor.order().map(Self::with_order)
}
}
impl<S: ArcStateId> Queue<S> for TopOrderQueue<S> {
fn head(&self) -> Option<S> {
if self.front < self.back {
self.at[self.front]
} else {
None
}
}
fn enqueue(&mut self, state: S) {
let position = self.order[state.as_usize()].as_usize();
if self.front >= self.back {
self.front = position;
self.back = position + 1;
} else {
self.front = self.front.min(position);
self.back = self.back.max(position + 1);
}
self.at[position] = Some(state);
}
fn dequeue(&mut self) -> Option<S> {
let state = self.head()?;
self.at[self.front] = None;
while self.front < self.back && self.at[self.front].is_none() {
self.front += 1;
}
Some(state)
}
fn is_empty(&self) -> bool {
self.front >= self.back
}
fn clear(&mut self) {
let back = self.back.min(self.at.len());
for slot in &mut self.at[self.front.min(back)..back] {
*slot = None;
}
self.front = 0;
self.back = 0;
}
fn queue_type(&self) -> QueueType {
QueueType::TopOrder
}
}
#[derive(Debug, Clone, Default)]
pub struct StateOrderQueue<S> {
queued: GrowableBitSet,
front: usize,
back: usize,
_marker: PhantomData<S>,
}
impl<S> StateOrderQueue<S> {
pub fn new() -> Self {
Self {
queued: GrowableBitSet::new(),
front: 0,
back: 0,
_marker: PhantomData,
}
}
}
impl<S: ArcStateId> Queue<S> for StateOrderQueue<S> {
fn head(&self) -> Option<S> {
(self.front < self.back).then(|| S::from_usize(self.front))
}
fn enqueue(&mut self, state: S) {
let index = state.as_usize();
if self.front >= self.back {
self.front = index;
self.back = index + 1;
} else {
self.front = self.front.min(index);
self.back = self.back.max(index + 1);
}
self.queued.insert(index);
}
fn dequeue(&mut self) -> Option<S> {
let state = self.head()?;
self.queued.remove(self.front);
while self.front < self.back && !self.queued.contains(self.front) {
self.front += 1;
}
Some(state)
}
fn is_empty(&self) -> bool {
self.front >= self.back
}
fn clear(&mut self) {
for index in self.front..self.back {
self.queued.remove(index);
}
self.front = 0;
self.back = 0;
}
fn queue_type(&self) -> QueueType {
QueueType::StateOrder
}
}
pub enum SccInnerQueue<S: ArcStateId, C> {
Trivial(TrivialQueue<S>),
Fifo(FifoQueue<S>),
Lifo(LifoQueue<S>),
ShortestFirst(ShortestFirstQueue<S, C, false>),
}
impl<S: ArcStateId, C: Fn(&S, &S) -> bool> Queue<S> for SccInnerQueue<S, C> {
fn head(&self) -> Option<S> {
match self {
Self::Trivial(q) => q.head(),
Self::Fifo(q) => q.head(),
Self::Lifo(q) => q.head(),
Self::ShortestFirst(q) => q.head(),
}
}
fn enqueue(&mut self, state: S) {
match self {
Self::Trivial(q) => q.enqueue(state),
Self::Fifo(q) => q.enqueue(state),
Self::Lifo(q) => q.enqueue(state),
Self::ShortestFirst(q) => q.enqueue(state),
}
}
fn dequeue(&mut self) -> Option<S> {
match self {
Self::Trivial(q) => q.dequeue(),
Self::Fifo(q) => q.dequeue(),
Self::Lifo(q) => q.dequeue(),
Self::ShortestFirst(q) => q.dequeue(),
}
}
fn update(&mut self, state: S) {
match self {
Self::Trivial(q) => q.update(state),
Self::Fifo(q) => q.update(state),
Self::Lifo(q) => q.update(state),
Self::ShortestFirst(q) => q.update(state),
}
}
fn is_empty(&self) -> bool {
match self {
Self::Trivial(q) => Queue::is_empty(q),
Self::Fifo(q) => Queue::is_empty(q),
Self::Lifo(q) => Queue::is_empty(q),
Self::ShortestFirst(q) => Queue::is_empty(q),
}
}
fn clear(&mut self) {
match self {
Self::Trivial(q) => q.clear(),
Self::Fifo(q) => q.clear(),
Self::Lifo(q) => q.clear(),
Self::ShortestFirst(q) => q.clear(),
}
}
fn queue_type(&self) -> QueueType {
match self {
Self::Trivial(_) => QueueType::Trivial,
Self::Fifo(_) => QueueType::Fifo,
Self::Lifo(_) => QueueType::Lifo,
Self::ShortestFirst(_) => QueueType::ShortestFirst,
}
}
}
pub struct SccQueue<S: ArcStateId, C> {
queues: Vec<SccInnerQueue<S, C>>,
scc: Vec<S>,
front: Cell<usize>,
back: usize,
}
impl<S: ArcStateId, C: Fn(&S, &S) -> bool> SccQueue<S, C> {
pub fn new(scc: Vec<S>, queues: Vec<SccInnerQueue<S, C>>) -> Self {
Self {
queues,
scc,
front: Cell::new(0),
back: 0,
}
}
fn skip_empty(&self) -> Option<usize> {
let mut front = self.front.get();
while front < self.back && self.queues[front].is_empty() {
front += 1;
}
self.front.set(front);
(front < self.back).then_some(front)
}
pub fn inner_types(&self) -> Vec<QueueType> {
self.queues.iter().map(|q| q.queue_type()).collect()
}
}
impl<S: ArcStateId, C: Fn(&S, &S) -> bool> Queue<S> for SccQueue<S, C> {
fn head(&self) -> Option<S> {
self.queues[self.skip_empty()?].head()
}
fn enqueue(&mut self, state: S) {
let scc = self.scc[state.as_usize()].as_usize();
let front = self.front.get();
if front >= self.back {
self.front.set(scc);
self.back = scc + 1;
} else {
self.front.set(front.min(scc));
self.back = self.back.max(scc + 1);
}
self.queues[scc].enqueue(state);
}
fn dequeue(&mut self) -> Option<S> {
let front = self.skip_empty()?;
self.queues[front].dequeue()
}
fn update(&mut self, state: S) {
let scc = self.scc[state.as_usize()].as_usize();
self.queues[scc].update(state);
}
fn is_empty(&self) -> bool {
self.skip_empty().is_none()
}
fn clear(&mut self) {
for queue in &mut self.queues[self.front.get().min(self.back)..self.back] {
queue.clear();
}
self.front.set(0);
self.back = 0;
}
fn queue_type(&self) -> QueueType {
QueueType::Scc
}
}
#[derive(Debug, Clone)]
pub struct SccAnalysis {
pub queue_types: Vec<QueueType>,
pub all_trivial: bool,
pub unweighted: bool,
}
pub fn scc_queue_types<A, F, L>(
fst: &F,
scc: &[A::StateId],
nscc: usize,
less: Option<&L>,
) -> SccAnalysis
where
A: Arc,
F: Fst<A>,
L: Fn(&A::Weight, &A::Weight) -> bool,
{
let mut queue_types = vec![QueueType::Trivial; nscc];
let mut all_trivial = true;
let idempotent = A::Weight::properties() & IDEMPOTENT != 0;
let mut unweighted = idempotent;
let (zero, one) = (A::Weight::zero(), A::Weight::one());
for state in fst.states() {
let Some(&from) = scc.get(state.as_usize()) else {
continue;
};
for arc in fst.arcs(state) {
let plain = idempotent && (*arc.weight() == zero || *arc.weight() == one);
if scc.get(arc.nextstate().as_usize()) == Some(&from) {
let ty = &mut queue_types[from.as_usize()];
match less {
None => *ty = QueueType::Fifo,
Some(less) if less(arc.weight(), &one) => *ty = QueueType::Fifo,
Some(_) if matches!(*ty, QueueType::Trivial | QueueType::Lifo) => {
*ty = if plain {
QueueType::Lifo
} else {
QueueType::ShortestFirst
};
}
Some(_) => {}
}
if *ty != QueueType::Trivial {
all_trivial = false;
}
}
if !plain {
unweighted = false;
}
}
}
SccAnalysis {
queue_types,
all_trivial,
unweighted,
}
}
pub fn components<A: Arc, F: Fst<A>>(fst: &F) -> Vec<A::StateId> {
let mut scc = Vec::new();
let mut props = 0;
let mut visitor =
SccVisitor::new(fst, Some(&mut scc), None, None, &mut props).without_coaccess();
dfs_visit_any(fst, &mut visitor);
drop(visitor);
scc
}
pub enum AutoQueue<S: ArcStateId, C> {
StateOrder(StateOrderQueue<S>),
TopOrder(TopOrderQueue<S>),
Lifo(LifoQueue<S>),
Scc(SccQueue<S, C>),
}
impl<S: ArcStateId, C: Fn(&S, &S) -> bool + Clone> AutoQueue<S, C> {
pub fn new<A, F>(fst: &F, comp: Option<C>) -> Self
where
A: Arc<StateId = S>,
F: Fst<A>,
{
let props = fst.properties(K_ACYCLIC | K_TOP_SORTED | K_UNWEIGHTED, false);
if props & K_TOP_SORTED != 0 || fst.start().is_none() {
return Self::StateOrder(StateOrderQueue::new());
}
if props & K_ACYCLIC != 0
&& let Some(queue) = TopOrderQueue::new(fst)
{
return Self::TopOrder(queue);
}
let idempotent = A::Weight::properties() & IDEMPOTENT != 0;
if props & K_UNWEIGHTED != 0 && idempotent {
return Self::Lifo(LifoQueue::new());
}
let scc = components(fst);
let Some(nscc) = scc.iter().map(|s| s.as_usize() + 1).max() else {
return Self::StateOrder(StateOrderQueue::new());
};
let less = (A::Weight::properties() & PATH != 0 && comp.is_some())
.then_some(natural_less_unchecked::<A::Weight>);
let analysis = scc_queue_types(fst, &scc, nscc, less.as_ref());
if analysis.unweighted {
return Self::Lifo(LifoQueue::new());
}
if analysis.all_trivial {
return Self::TopOrder(TopOrderQueue::with_order(scc));
}
let queues = analysis
.queue_types
.iter()
.map(|ty| match ty {
QueueType::Trivial => SccInnerQueue::Trivial(TrivialQueue::new()),
QueueType::Lifo => SccInnerQueue::Lifo(LifoQueue::new()),
QueueType::ShortestFirst => match comp.clone() {
Some(comp) => {
SccInnerQueue::ShortestFirst(ShortestFirstQueue::without_update(comp))
}
None => SccInnerQueue::Fifo(FifoQueue::new()),
},
_ => SccInnerQueue::Fifo(FifoQueue::new()),
})
.collect();
Self::Scc(SccQueue::new(scc, queues))
}
pub fn chosen(&self) -> QueueType {
match self {
Self::StateOrder(_) => QueueType::StateOrder,
Self::TopOrder(_) => QueueType::TopOrder,
Self::Lifo(_) => QueueType::Lifo,
Self::Scc(_) => QueueType::Scc,
}
}
pub fn inner_types(&self) -> Option<Vec<QueueType>> {
match self {
Self::Scc(queue) => Some(queue.inner_types()),
_ => None,
}
}
}
impl<S: ArcStateId, C: Fn(&S, &S) -> bool> Queue<S> for AutoQueue<S, C> {
fn head(&self) -> Option<S> {
match self {
Self::StateOrder(q) => q.head(),
Self::TopOrder(q) => q.head(),
Self::Lifo(q) => q.head(),
Self::Scc(q) => q.head(),
}
}
fn enqueue(&mut self, state: S) {
match self {
Self::StateOrder(q) => q.enqueue(state),
Self::TopOrder(q) => q.enqueue(state),
Self::Lifo(q) => q.enqueue(state),
Self::Scc(q) => q.enqueue(state),
}
}
fn dequeue(&mut self) -> Option<S> {
match self {
Self::StateOrder(q) => q.dequeue(),
Self::TopOrder(q) => q.dequeue(),
Self::Lifo(q) => q.dequeue(),
Self::Scc(q) => q.dequeue(),
}
}
fn update(&mut self, state: S) {
match self {
Self::StateOrder(q) => q.update(state),
Self::TopOrder(q) => q.update(state),
Self::Lifo(q) => q.update(state),
Self::Scc(q) => q.update(state),
}
}
fn is_empty(&self) -> bool {
match self {
Self::StateOrder(q) => q.is_empty(),
Self::TopOrder(q) => q.is_empty(),
Self::Lifo(q) => Queue::is_empty(q),
Self::Scc(q) => q.is_empty(),
}
}
fn clear(&mut self) {
match self {
Self::StateOrder(q) => q.clear(),
Self::TopOrder(q) => q.clear(),
Self::Lifo(q) => q.clear(),
Self::Scc(q) => q.clear(),
}
}
fn queue_type(&self) -> QueueType {
QueueType::Auto
}
}
pub struct PruneQueue<S, Q, W, L, F> {
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
queue: Q,
less: L,
class_fnc: F,
threshold: W,
class_distance: Vec<W>,
_marker: PhantomData<S>,
}
impl<S, Q, W, L, F> PruneQueue<S, Q, W, L, F>
where
S: ArcStateId,
Q: Queue<S>,
W: Weight,
L: Fn(&W, &W) -> bool,
F: Fn(S) -> usize,
{
pub fn new(
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
queue: Q,
less: L,
class_fnc: F,
threshold: W,
) -> Self {
Self {
distance,
queue,
less,
class_fnc,
threshold,
class_distance: Vec::new(),
_marker: PhantomData,
}
}
pub fn inner(&self) -> &Q {
&self.queue
}
fn note(&mut self, state: S) -> Option<W> {
let class = (self.class_fnc)(state);
if class >= self.class_distance.len() {
self.class_distance.resize(class + 1, W::zero());
}
let distance = self.distance.borrow().get(state.as_usize()).cloned()?;
if (self.less)(&distance, &self.class_distance[class]) {
self.class_distance[class] = distance.clone();
}
Some(distance)
}
}
impl<S, Q, W, L, F> Queue<S> for PruneQueue<S, Q, W, L, F>
where
S: ArcStateId,
Q: Queue<S>,
W: Weight,
L: Fn(&W, &W) -> bool,
F: Fn(S) -> usize,
{
fn head(&self) -> Option<S> {
self.queue.head()
}
fn enqueue(&mut self, state: S) {
let Some(distance) = self.note(state) else {
return;
};
let class = (self.class_fnc)(state);
let limit = self.class_distance[class].times(&self.threshold);
if (self.less)(&distance, &limit) {
self.queue.enqueue(state);
}
}
fn dequeue(&mut self) -> Option<S> {
self.queue.dequeue()
}
fn update(&mut self, state: S) {
self.note(state);
self.queue.update(state);
}
fn is_empty(&self) -> bool {
self.queue.is_empty()
}
fn clear(&mut self) {
self.queue.clear();
}
}
impl<S, Q, W, F> PruneQueue<S, Q, W, fn(&W, &W) -> bool, F>
where
S: ArcStateId,
Q: Queue<S>,
W: crate::weight::IdempotentWeight,
F: Fn(S) -> usize,
{
pub fn natural(
distance: std::rc::Rc<std::cell::RefCell<Vec<W>>>,
queue: Q,
class_fnc: F,
threshold: W,
) -> Self {
Self::new(
distance,
queue,
crate::weight::natural_less::<W> as fn(&W, &W) -> bool,
class_fnc,
threshold,
)
}
}
pub struct FilterQueue<S, Q, F> {
queue: Q,
filter: F,
_marker: PhantomData<S>,
}
impl<S, Q, F> FilterQueue<S, Q, F>
where
S: ArcStateId,
Q: Queue<S>,
F: Fn(S) -> bool,
{
pub fn new(queue: Q, filter: F) -> Self {
Self {
queue,
filter,
_marker: PhantomData,
}
}
pub fn inner(&self) -> &Q {
&self.queue
}
}
impl<S, Q, F> Queue<S> for FilterQueue<S, Q, F>
where
S: ArcStateId,
Q: Queue<S>,
F: Fn(S) -> bool,
{
fn head(&self) -> Option<S> {
self.queue.head()
}
fn enqueue(&mut self, state: S) {
if (self.filter)(state) {
self.queue.enqueue(state);
}
}
fn dequeue(&mut self) -> Option<S> {
self.queue.dequeue()
}
fn is_empty(&self) -> bool {
self.queue.is_empty()
}
fn clear(&mut self) {
self.queue.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arc::StdArc;
use crate::fst::{ExpandedFst as _, MutableFst};
use crate::fsts::vector_fst::StdVectorFst;
use crate::properties::K_FST_PROPERTIES;
use crate::weight::natural_less;
use crate::weights::float_weight::TropicalWeight;
use std::cell::RefCell;
use std::rc::Rc;
type Distance = Rc<RefCell<Vec<TropicalWeight>>>;
fn drain<Q: Queue<i32>>(queue: &mut Q) -> Vec<i32> {
let mut out = Vec::new();
while let Some(state) = queue.dequeue() {
out.push(state);
}
out
}
fn distance_of(weights: &[f32]) -> Distance {
Rc::new(RefCell::new(
weights.iter().copied().map(TropicalWeight).collect(),
))
}
fn natural(distance: &Distance) -> impl Fn(&i32, &i32) -> bool + Clone + use<> {
natural_state_order::<i32, TropicalWeight>(Rc::clone(distance))
}
#[test]
fn fifo_and_lifo_are_the_two_obvious_orders() {
let mut fifo = FifoQueue::new();
let mut lifo = LifoQueue::new();
for s in [3, 1, 2] {
fifo.enqueue(s);
lifo.enqueue(s);
}
assert_eq!(drain(&mut fifo), vec![3, 1, 2]);
assert_eq!(drain(&mut lifo), vec![2, 1, 3]);
}
#[test]
fn a_trivial_queue_holds_one_state() {
let mut queue = TrivialQueue::new();
assert!(Queue::is_empty(&queue));
queue.enqueue(5);
assert_eq!(queue.head(), Some(5));
queue.enqueue(7);
assert_eq!(queue.head(), Some(7), "the second replaces the first");
assert_eq!(drain(&mut queue), vec![7]);
}
#[test]
fn state_order_hands_states_back_by_id() {
let mut queue = StateOrderQueue::new();
for s in [4, 1, 7, 2] {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![1, 2, 4, 7]);
}
#[test]
fn state_order_holds_each_state_once() {
let mut queue = StateOrderQueue::new();
for s in [3, 1, 3, 1, 3] {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![1, 3]);
}
#[test]
fn shortest_first_hands_back_the_lightest() {
let distance = distance_of(&[5.0, 2.0, 9.0, 1.0]);
let mut queue = ShortestFirstQueue::new(natural(&distance));
for s in 0..4 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![3, 1, 0, 2]);
}
#[test]
fn shortest_first_moves_a_state_whose_weight_improves() {
let distance = distance_of(&[5.0, 2.0, 9.0]);
let mut queue = ShortestFirstQueue::new(natural(&distance));
for s in 0..3 {
queue.enqueue(s);
}
distance.borrow_mut()[2] = TropicalWeight(0.5);
queue.update(2);
assert_eq!(drain(&mut queue), vec![2, 1, 0]);
}
#[test]
fn updating_a_state_that_is_not_queued_adds_it() {
let distance = distance_of(&[1.0, 2.0]);
let mut queue = ShortestFirstQueue::new(natural(&distance));
queue.enqueue(0);
assert_eq!(queue.dequeue(), Some(0));
assert!(Queue::is_empty(&queue));
queue.update(1);
assert_eq!(drain(&mut queue), vec![1]);
}
#[test]
fn without_key_tracking_a_state_cannot_be_moved() {
let distance = distance_of(&[5.0, 2.0, 9.0]);
let mut tracking = ShortestFirstQueue::new(natural(&distance));
let mut untracking = ShortestFirstQueue::without_update(natural(&distance));
for s in 0..3 {
tracking.enqueue(s);
untracking.enqueue(s);
}
assert_eq!(tracking.head(), Some(1));
assert_eq!(untracking.head(), Some(1));
distance.borrow_mut()[2] = TropicalWeight(0.5);
tracking.update(2);
untracking.update(2);
assert_eq!(tracking.head(), Some(2), "the improvement moved it");
assert_eq!(
untracking.head(),
Some(1),
"nothing knows where state 2 sits, so it stays where its old weight put it"
);
}
fn zigzag() -> StdVectorFst {
let mut fst = StdVectorFst::new();
for _ in 0..3 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight::one(), 2));
fst.add_arc(2, StdArc::new(1, 1, TropicalWeight::one(), 1));
fst.set_final(1, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
fst
}
#[test]
fn top_order_follows_the_arcs_rather_than_the_numbering() {
let fst = zigzag();
let mut queue = TopOrderQueue::new(&fst).expect("acyclic");
for s in 0..3 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![0, 2, 1]);
}
#[test]
fn there_is_no_topological_order_for_a_cyclic_fst() {
let mut fst = zigzag();
fst.add_arc(1, StdArc::new(1, 1, TropicalWeight::one(), 0));
assert!(TopOrderQueue::<i32>::new(&fst).is_none());
}
fn two_components() -> StdVectorFst {
let mut fst = StdVectorFst::new();
for _ in 0..4 {
fst.add_state();
}
fst.set_start(0);
fst.add_arc(0, StdArc::new(1, 1, TropicalWeight(1.0), 1));
fst.add_arc(1, StdArc::new(1, 1, TropicalWeight(1.0), 0));
fst.add_arc(1, StdArc::new(2, 2, TropicalWeight(1.0), 2));
fst.add_arc(2, StdArc::new(1, 1, TropicalWeight(1.0), 3));
fst.add_arc(3, StdArc::new(1, 1, TropicalWeight(1.0), 2));
fst.set_final(3, TropicalWeight::one());
fst.properties(K_FST_PROPERTIES, true);
fst
}
#[test]
fn components_are_numbered_in_topological_order() {
let scc = components(&two_components());
assert_eq!(scc[0], scc[1]);
assert_eq!(scc[2], scc[3]);
assert!(scc[0] < scc[2], "the upstream component comes first");
}
#[test]
fn each_component_gets_the_discipline_its_arcs_call_for() {
let fst = two_components();
let scc = components(&fst);
let nscc = scc.iter().map(|s| *s as usize + 1).max().unwrap();
let less = |a: &TropicalWeight, b: &TropicalWeight| natural_less(a, b);
let with = scc_queue_types(&fst, &scc, nscc, Some(&less));
assert_eq!(
with.queue_types,
vec![QueueType::ShortestFirst, QueueType::ShortestFirst]
);
assert!(!with.all_trivial);
assert!(!with.unweighted);
type Less = fn(&TropicalWeight, &TropicalWeight) -> bool;
let without = scc_queue_types::<StdArc, _, Less>(&fst, &scc, nscc, None);
assert_eq!(without.queue_types, vec![QueueType::Fifo, QueueType::Fifo]);
let acyclic = zigzag();
let scc = components(&acyclic);
let nscc = scc.iter().map(|s| *s as usize + 1).max().unwrap();
let trivial = scc_queue_types(&acyclic, &scc, nscc, Some(&less));
assert!(trivial.all_trivial);
assert!(
trivial.queue_types.iter().all(|t| *t == QueueType::Trivial),
"{:?}",
trivial.queue_types
);
}
#[test]
fn the_meta_queue_finishes_a_component_before_moving_on() {
let fst = two_components();
let scc = components(&fst);
let distance = distance_of(&[0.0, 1.0, 2.0, 3.0]);
let queues = vec![
SccInnerQueue::ShortestFirst(ShortestFirstQueue::without_update(natural(&distance))),
SccInnerQueue::ShortestFirst(ShortestFirstQueue::without_update(natural(&distance))),
];
let mut queue = SccQueue::new(scc.clone(), queues);
for s in [3, 2, 1, 0] {
queue.enqueue(s);
}
let order = drain(&mut queue);
assert_eq!(order, vec![0, 1, 2, 3]);
let positions: Vec<usize> = order.iter().map(|s| scc[*s as usize] as usize).collect();
assert!(
positions.windows(2).all(|w| w[0] <= w[1]),
"components have to come out in topological order: {positions:?}"
);
}
#[test]
fn the_automatic_choice_follows_the_shape_of_the_fst() {
use crate::algorithms::topsort::top_sort;
let distance = distance_of(&[0.0; 8]);
let comp = || Some(natural(&distance));
let mut sorted = zigzag();
top_sort(&mut sorted).unwrap();
assert_eq!(
AutoQueue::new(&sorted, comp()).chosen(),
QueueType::StateOrder
);
assert_eq!(
AutoQueue::new(&zigzag(), comp()).chosen(),
QueueType::TopOrder
);
let mut cyclic = zigzag();
cyclic.add_arc(1, StdArc::new(1, 1, TropicalWeight::one(), 0));
cyclic.properties(K_FST_PROPERTIES, true);
assert_eq!(AutoQueue::new(&cyclic, comp()).chosen(), QueueType::Lifo);
let weighted = two_components();
let auto = AutoQueue::new(&weighted, comp());
assert_eq!(auto.chosen(), QueueType::Scc);
assert_eq!(
auto.inner_types(),
Some(vec![QueueType::ShortestFirst, QueueType::ShortestFirst])
);
}
#[test]
fn components_that_are_all_trivial_give_the_topological_order() {
let mut fst = zigzag();
fst.set_properties(0, K_ACYCLIC | K_TOP_SORTED | K_UNWEIGHTED);
fst.add_arc(0, StdArc::new(3, 3, TropicalWeight(2.0), 1));
fst.set_properties(0, K_ACYCLIC | K_TOP_SORTED | K_UNWEIGHTED);
let distance = distance_of(&[0.0; 3]);
let auto = AutoQueue::new(&fst, Some(natural(&distance)));
assert_eq!(auto.chosen(), QueueType::TopOrder);
}
#[test]
fn clearing_empties_every_discipline() {
let mut fifo = FifoQueue::new();
let mut lifo = LifoQueue::new();
let mut order = StateOrderQueue::new();
let mut top = TopOrderQueue::new(&zigzag()).unwrap();
for s in [1, 2, 0] {
fifo.enqueue(s);
lifo.enqueue(s);
order.enqueue(s);
top.enqueue(s);
}
fifo.clear();
lifo.clear();
order.clear();
top.clear();
assert!(Queue::is_empty(&fifo));
assert!(Queue::is_empty(&lifo));
assert!(Queue::is_empty(&order));
assert!(Queue::is_empty(&top));
order.enqueue(5);
assert_eq!(drain(&mut order), vec![5]);
top.enqueue(1);
assert_eq!(drain(&mut top), vec![1]);
}
#[test]
fn a_filter_queue_refuses_what_the_filter_rejects() {
let mut queue = FilterQueue::new(FifoQueue::new(), |s: i32| s % 2 == 0);
for s in 0..6 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![0, 2, 4]);
}
#[test]
fn a_prune_queue_refuses_what_is_far_behind_its_class() {
let distance = distance_of(&[0.0, 1.0, 5.0, 2.0]);
let mut queue = PruneQueue::natural(
Rc::clone(&distance),
FifoQueue::new(),
|_: i32| 0,
TropicalWeight(2.0),
);
for s in 0..4 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![0, 1]);
}
#[test]
fn the_pruning_queue_drops_a_path_that_has_taken_too_many_arcs() {
let distance = distance_of(&[0.0, 1.0, 2.0, 3.0, 100.0, 101.0]);
let mut queue: PruneShortestFirstQueue<i32, _, TropicalWeight> =
PruneShortestFirstQueue::new(natural(&distance), Some(1), None);
queue.enqueue(0);
queue.enqueue(4);
for s in 1..4 {
assert_eq!(queue.dequeue(), Some(s - 1));
queue.enqueue(s);
}
assert_eq!(queue.dequeue(), Some(3), "the chain is four arcs long");
assert_eq!(queue.dequeue(), Some(4));
queue.enqueue(5);
assert!(
Queue::is_empty(&queue),
"two arcs in is three behind the four-arc path, and the threshold is one"
);
}
#[test]
fn the_pruning_queue_keeps_everything_without_a_threshold() {
let distance = distance_of(&[0.0, 1.0, 2.0, 3.0]);
let mut queue: PruneShortestFirstQueue<i32, _, TropicalWeight> =
PruneShortestFirstQueue::new(natural(&distance), None, None);
for s in 0..4 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![0, 1, 2, 3]);
}
#[test]
fn an_a_star_estimate_reorders_the_queue() {
let distance = distance_of(&[1.0, 3.0]);
let plain = ShortestFirstQueue::new(state_weight_compare(
Rc::clone(&distance),
|a: &TropicalWeight, b: &TropicalWeight| natural_less(a, b),
));
let mut plain = plain;
plain.enqueue(0);
plain.enqueue(1);
assert_eq!(drain(&mut plain), vec![0, 1]);
let beta = distance_of(&[10.0, 1.0]);
let mut astar = ShortestFirstQueue::new(a_star_compare(
Rc::clone(&distance),
|a: &TropicalWeight, b: &TropicalWeight| natural_less(a, b),
distance_estimate::<i32, TropicalWeight>(Rc::clone(&beta)),
));
astar.enqueue(0);
astar.enqueue(1);
assert_eq!(drain(&mut astar), vec![1, 0]);
}
#[test]
fn a_trivial_a_star_estimate_is_dijkstra() {
let distance = distance_of(&[5.0, 2.0, 9.0]);
let mut queue = ShortestFirstQueue::new(a_star_compare(
Rc::clone(&distance),
|a: &TropicalWeight, b: &TropicalWeight| natural_less(a, b),
trivial_estimate::<i32, TropicalWeight>(),
));
for s in 0..3 {
queue.enqueue(s);
}
assert_eq!(drain(&mut queue), vec![1, 0, 2]);
}
fn shortest_distance_with<Q: Queue<i32>>(
fst: &StdVectorFst,
queue: &mut Q,
distance: &Distance,
) -> Vec<TropicalWeight> {
let nstates = fst.num_states();
{
let mut d = distance.borrow_mut();
d.clear();
d.resize(nstates, TropicalWeight::zero());
}
let Some(start) = fst.start() else {
return distance.borrow().clone();
};
let mut residual = vec![TropicalWeight::zero(); nstates];
distance.borrow_mut()[start as usize] = TropicalWeight::one();
residual[start as usize] = TropicalWeight::one();
let mut enqueued = vec![false; nstates];
enqueued[start as usize] = true;
queue.enqueue(start);
let mut steps = 0;
while let Some(state) = queue.dequeue() {
steps += 1;
assert!(steps < 200_000, "the queue is not draining");
enqueued[state as usize] = false;
let r = residual[state as usize];
residual[state as usize] = TropicalWeight::zero();
for arc in fst.arcs(state) {
let next = arc.nextstate() as usize;
let contribution = r.times(arc.weight());
let old = distance.borrow()[next];
let new = old.plus(&contribution);
if old == new {
continue;
}
distance.borrow_mut()[next] = new;
residual[next] = residual[next].plus(&contribution);
if enqueued[next] {
queue.update(arc.nextstate());
} else {
enqueued[next] = true;
queue.enqueue(arc.nextstate());
}
}
}
distance.borrow().clone()
}
fn reference_distance(fst: &StdVectorFst) -> Vec<TropicalWeight> {
let nstates = fst.num_states();
let mut d = vec![TropicalWeight::zero(); nstates];
let Some(start) = fst.start() else { return d };
d[start as usize] = TropicalWeight::one();
for _ in 0..nstates {
let mut changed = false;
for s in 0..nstates {
for arc in fst.arcs(s as i32) {
let relaxed = d[s].times(arc.weight());
let next = arc.nextstate() as usize;
let new = d[next].plus(&relaxed);
if new != d[next] {
d[next] = new;
changed = true;
}
}
}
if !changed {
break;
}
}
d
}
fn random_fst(
next: &mut impl FnMut(usize) -> usize,
acyclic: bool,
weighted: bool,
) -> StdVectorFst {
let nstates = 2 + next(7);
let mut fst = StdVectorFst::new();
for _ in 0..nstates {
fst.add_state();
}
fst.set_start(0);
for s in 0..nstates {
for _ in 0..next(4) {
let target = if acyclic {
if s + 1 >= nstates {
continue;
}
s + 1 + next(nstates - s - 1)
} else {
next(nstates)
};
let label = 1 + next(3) as i32;
let weight = if weighted {
TropicalWeight(next(6) as f32)
} else {
TropicalWeight::one()
};
fst.add_arc(s as i32, StdArc::new(label, label, weight, target as i32));
}
if next(3) == 0 {
fst.set_final(s as i32, TropicalWeight::one());
}
}
fst.properties(K_FST_PROPERTIES, true);
fst
}
#[test]
fn the_discipline_never_changes_the_answer() {
let mut rng = 0x0EEDF00Du64;
let mut next = |bound: usize| {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
((rng >> 33) as usize) % bound.max(1)
};
for round in 0..300 {
let acyclic = round % 3 == 0;
let weighted = round % 5 != 0;
let fst = random_fst(&mut next, acyclic, weighted);
let want = reference_distance(&fst);
let distance: Distance = Rc::new(RefCell::new(Vec::new()));
let mut got: Vec<(&str, Vec<TropicalWeight>)> = vec![
(
"fifo",
shortest_distance_with(&fst, &mut FifoQueue::new(), &distance),
),
(
"lifo",
shortest_distance_with(&fst, &mut LifoQueue::new(), &distance),
),
(
"state-order",
shortest_distance_with(&fst, &mut StateOrderQueue::new(), &distance),
),
(
"shortest-first",
shortest_distance_with(
&fst,
&mut ShortestFirstQueue::new(natural(&distance)),
&distance,
),
),
(
"auto",
shortest_distance_with(
&fst,
&mut AutoQueue::new(&fst, Some(natural(&distance))),
&distance,
),
),
];
if acyclic && let Some(mut top) = TopOrderQueue::new(&fst) {
got.push((
"top-order",
shortest_distance_with(&fst, &mut top, &distance),
));
}
for (name, distances) in got {
assert_eq!(distances, want, "round {round}, {name} discipline");
}
}
}
#[test]
fn the_meta_queue_settles_the_same_distances() {
let mut rng = 0x5CC_0EEDu64;
let mut next = |bound: usize| {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
((rng >> 33) as usize) % bound.max(1)
};
let mut reached = 0;
for round in 0..200 {
let fst = random_fst(&mut next, false, true);
let distance: Distance = Rc::new(RefCell::new(Vec::new()));
let mut auto = AutoQueue::new(&fst, Some(natural(&distance)));
if auto.chosen() != QueueType::Scc {
continue;
}
reached += 1;
let got = shortest_distance_with(&fst, &mut auto, &distance);
assert_eq!(got, reference_distance(&fst), "round {round}");
}
assert!(reached > 20, "only {reached} FSTs reached the meta-queue");
}
}