use std::cell::{Cell, UnsafeCell};
use rustc_hash::FxHashMap;
use crate::arc::{Arc, ArcLabel, ArcStateId};
use crate::memory::{ArcArena, ArcRun};
use crate::weight::Weight;
pub trait Expander<A: Arc> {
fn start(&self) -> A::StateId;
fn num_states(&self) -> usize;
fn expand(&self, state_id: A::StateId, builder: &mut StateBuilder<A>);
}
pub struct StateBuilder<A: Arc> {
final_weight: A::Weight,
niepsilons: u32,
noepsilons: u32,
arcs: Vec<A>,
}
impl<A: Arc> Default for StateBuilder<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Arc> StateBuilder<A> {
pub fn new() -> Self {
Self {
final_weight: A::Weight::zero(),
niepsilons: 0,
noepsilons: 0,
arcs: Vec::new(),
}
}
#[inline]
pub fn set_final(&mut self, weight: A::Weight) {
self.final_weight = weight;
}
#[inline]
pub fn reserve_arcs(&mut self, n: usize) {
self.arcs.reserve(n);
}
#[inline]
pub fn add_arc(&mut self, arc: A) {
self.niepsilons += u32::from(arc.ilabel() == A::Label::epsilon());
self.noepsilons += u32::from(arc.olabel() == A::Label::epsilon());
self.arcs.push(arc);
}
#[inline]
pub fn final_weight(&self) -> &A::Weight {
&self.final_weight
}
#[inline]
pub fn arcs(&self) -> &[A] {
&self.arcs
}
#[inline]
pub fn num_arcs(&self) -> usize {
self.arcs.len()
}
fn reset(&mut self) {
self.final_weight = A::Weight::zero();
self.niepsilons = 0;
self.noepsilons = 0;
self.arcs.clear();
}
}
#[derive(Debug)]
pub struct StateView<'a, A: Arc> {
final_weight: &'a A::Weight,
niepsilons: u32,
noepsilons: u32,
arcs: &'a [A],
}
impl<A: Arc> Clone for StateView<'_, A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: Arc> Copy for StateView<'_, A> {}
impl<'a, A: Arc> StateView<'a, A> {
#[inline]
pub fn final_weight(&self) -> &'a A::Weight {
self.final_weight
}
#[inline]
pub fn arcs(&self) -> &'a [A] {
self.arcs
}
#[inline]
pub fn num_arcs(&self) -> usize {
self.arcs.len()
}
#[inline]
pub fn num_input_epsilons(&self) -> usize {
self.niepsilons as usize
}
#[inline]
pub fn num_output_epsilons(&self) -> usize {
self.noepsilons as usize
}
}
pub trait ExpanderCache<A: Arc>: Clone {
fn find_or_expand<E: Expander<A>>(
&self,
expander: &E,
state_id: A::StateId,
) -> StateView<'_, A>;
fn find(&self, state_id: A::StateId) -> Option<StateView<'_, A>>;
}
fn expand_with<A, E, S>(
scratch: &Cell<StateBuilder<A>>,
expander: &E,
state_id: A::StateId,
finish: impl FnOnce(&StateBuilder<A>) -> S,
) -> S
where
A: Arc,
E: Expander<A>,
{
let mut builder = scratch.take();
builder.reset();
expander.expand(state_id, &mut builder);
let state = finish(&builder);
builder.reset();
scratch.set(builder);
state
}
#[derive(Debug, Clone)]
pub struct SimpleVectorCacheState<A: Arc> {
final_weight: A::Weight,
niepsilons: u32,
noepsilons: u32,
arcs: Box<[A]>,
}
impl<A: Arc> SimpleVectorCacheState<A> {
fn from_builder(builder: &StateBuilder<A>) -> Self {
Self {
final_weight: builder.final_weight.clone(),
niepsilons: builder.niepsilons,
noepsilons: builder.noepsilons,
arcs: builder.arcs.as_slice().into(),
}
}
fn view(&self) -> StateView<'_, A> {
StateView {
final_weight: &self.final_weight,
niepsilons: self.niepsilons,
noepsilons: self.noepsilons,
arcs: &self.arcs,
}
}
}
pub struct VectorExpanderCache<A: Arc> {
states: UnsafeCell<Vec<Option<Box<SimpleVectorCacheState<A>>>>>,
scratch: Cell<StateBuilder<A>>,
}
impl<A: Arc> Default for VectorExpanderCache<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Arc> VectorExpanderCache<A> {
pub fn new() -> Self {
Self {
states: UnsafeCell::new(Vec::new()),
scratch: Cell::new(StateBuilder::new()),
}
}
#[inline]
fn get(&self, index: usize) -> Option<&SimpleVectorCacheState<A>> {
let states = unsafe { &*self.states.get() };
states.get(index)?.as_deref()
}
}
impl<A: Arc> Clone for VectorExpanderCache<A> {
fn clone(&self) -> Self {
let states = unsafe { &*self.states.get() };
Self {
states: UnsafeCell::new(states.clone()),
scratch: Cell::new(StateBuilder::new()),
}
}
}
impl<A: Arc> ExpanderCache<A> for VectorExpanderCache<A> {
fn find_or_expand<E: Expander<A>>(
&self,
expander: &E,
state_id: A::StateId,
) -> StateView<'_, A> {
let index = state_id.as_usize();
if let Some(state) = self.get(index) {
return state.view();
}
let state = expand_with(
&self.scratch,
expander,
state_id,
SimpleVectorCacheState::from_builder,
);
unsafe {
let states = &mut *self.states.get();
if states.len() <= index {
states.resize_with(index + 1, || None);
}
if states[index].is_none() {
states[index] = Some(Box::new(state));
}
}
self.get(index).expect("the slot was just filled").view()
}
#[inline]
fn find(&self, state_id: A::StateId) -> Option<StateView<'_, A>> {
Some(self.get(state_id.as_usize())?.view())
}
}
pub struct HashExpanderCache<A: Arc> {
states: UnsafeCell<FxHashMap<A::StateId, Box<SimpleVectorCacheState<A>>>>,
scratch: Cell<StateBuilder<A>>,
}
impl<A: Arc> Default for HashExpanderCache<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Arc> HashExpanderCache<A> {
pub fn new() -> Self {
Self {
states: UnsafeCell::new(FxHashMap::default()),
scratch: Cell::new(StateBuilder::new()),
}
}
#[inline]
fn get(&self, state_id: A::StateId) -> Option<&SimpleVectorCacheState<A>> {
let states = unsafe { &*self.states.get() };
states.get(&state_id).map(Box::as_ref)
}
}
impl<A: Arc> Clone for HashExpanderCache<A> {
fn clone(&self) -> Self {
let states = unsafe { &*self.states.get() };
Self {
states: UnsafeCell::new(states.clone()),
scratch: Cell::new(StateBuilder::new()),
}
}
}
impl<A: Arc> ExpanderCache<A> for HashExpanderCache<A> {
fn find_or_expand<E: Expander<A>>(
&self,
expander: &E,
state_id: A::StateId,
) -> StateView<'_, A> {
if let Some(state) = self.get(state_id) {
return state.view();
}
let state = expand_with(
&self.scratch,
expander,
state_id,
SimpleVectorCacheState::from_builder,
);
unsafe {
(*self.states.get())
.entry(state_id)
.or_insert_with(|| Box::new(state))
};
self.get(state_id)
.expect("the entry was just filled")
.view()
}
#[inline]
fn find(&self, state_id: A::StateId) -> Option<StateView<'_, A>> {
Some(self.get(state_id)?.view())
}
}
struct ArenaState<A: Arc> {
final_weight: A::Weight,
niepsilons: u32,
noepsilons: u32,
run: ArcRun,
}
pub struct ArcArenaStateStore<A: Arc> {
states: UnsafeCell<FxHashMap<A::StateId, Box<ArenaState<A>>>>,
arena: UnsafeCell<ArcArena<A>>,
scratch: Cell<StateBuilder<A>>,
}
const ARENA_BLOCK_SIZE: usize = 64 * 1024;
impl<A: Arc> Default for ArcArenaStateStore<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Arc> ArcArenaStateStore<A> {
pub fn new() -> Self {
Self {
states: UnsafeCell::new(FxHashMap::default()),
arena: UnsafeCell::new(ArcArena::with_block_size(ARENA_BLOCK_SIZE)),
scratch: Cell::new(StateBuilder::new()),
}
}
fn view<'a>(&'a self, state: &'a ArenaState<A>) -> StateView<'a, A> {
let arena = unsafe { &*self.arena.get() };
let arcs = arena.arcs(state.run);
StateView {
final_weight: &state.final_weight,
niepsilons: state.niepsilons,
noepsilons: state.noepsilons,
arcs,
}
}
#[inline]
fn get(&self, state_id: A::StateId) -> Option<&ArenaState<A>> {
let states = unsafe { &*self.states.get() };
states.get(&state_id).map(Box::as_ref)
}
}
impl<A: Arc> Clone for ArcArenaStateStore<A> {
fn clone(&self) -> Self {
let clone = Self::new();
unsafe {
let states = &*self.states.get();
let source = &*self.arena.get();
let arena = &mut *clone.arena.get();
let cloned_states = &mut *clone.states.get();
cloned_states.reserve(states.len());
for (&state_id, state) in states {
let arcs = source.arcs(state.run);
arena.reserve_arcs(arcs.len());
for arc in arcs {
arena.push_arc(arc.clone());
}
cloned_states.insert(
state_id,
Box::new(ArenaState {
final_weight: state.final_weight.clone(),
niepsilons: state.niepsilons,
noepsilons: state.noepsilons,
run: arena.commit_arcs(),
}),
);
}
}
clone
}
}
impl<A: Arc> ExpanderCache<A> for ArcArenaStateStore<A> {
fn find_or_expand<E: Expander<A>>(
&self,
expander: &E,
state_id: A::StateId,
) -> StateView<'_, A> {
if let Some(state) = self.get(state_id) {
return self.view(state);
}
let state = expand_with(&self.scratch, expander, state_id, |builder| {
let arena = unsafe { &mut *self.arena.get() };
arena.reserve_arcs(builder.num_arcs());
for arc in builder.arcs() {
arena.push_arc(arc.clone());
}
ArenaState {
final_weight: builder.final_weight().clone(),
niepsilons: builder.niepsilons,
noepsilons: builder.noepsilons,
run: arena.commit_arcs(),
}
});
unsafe {
(*self.states.get())
.entry(state_id)
.or_insert_with(|| Box::new(state))
};
self.view(self.get(state_id).expect("the entry was just filled"))
}
#[inline]
fn find(&self, state_id: A::StateId) -> Option<StateView<'_, A>> {
Some(self.view(self.get(state_id)?))
}
}
pub type DefaultExpanderCache<A> = VectorExpanderCache<A>;
#[cfg(test)]
mod tests {
use super::*;
use crate::arc::StdArc;
use crate::weights::float_weight::TropicalWeight;
use std::cell::RefCell;
#[derive(Default)]
struct Counting {
expansions: RefCell<Vec<i32>>,
}
impl Counting {
fn count(&self, state: i32) -> usize {
self.expansions
.borrow()
.iter()
.filter(|&&s| s == state)
.count()
}
}
impl Expander<StdArc> for Counting {
fn start(&self) -> i32 {
0
}
fn num_states(&self) -> usize {
0
}
fn expand(&self, state_id: i32, builder: &mut StateBuilder<StdArc>) {
self.expansions.borrow_mut().push(state_id);
if state_id % 3 == 0 {
builder.set_final(TropicalWeight(state_id as f32));
}
let narcs = state_id % 5;
builder.reserve_arcs(narcs as usize);
for i in 0..narcs {
let ilabel = if i % 2 == 0 { 0 } else { state_id + i };
builder.add_arc(StdArc::new(
ilabel,
state_id + i,
TropicalWeight(i as f32),
state_id + i,
));
}
}
}
fn expected_final(state: i32) -> TropicalWeight {
if state % 3 == 0 {
TropicalWeight(state as f32)
} else {
TropicalWeight::zero()
}
}
fn check_state(view: StateView<'_, StdArc>, state: i32) {
assert_eq!(*view.final_weight(), expected_final(state));
let narcs = (state % 5) as usize;
assert_eq!(view.num_arcs(), narcs);
assert_eq!(view.num_input_epsilons(), narcs.div_ceil(2));
assert_eq!(view.num_output_epsilons(), 0);
for (i, arc) in view.arcs().iter().enumerate() {
assert_eq!(arc.olabel(), state + i as i32);
assert_eq!(arc.nextstate(), state + i as i32);
}
}
macro_rules! for_each_cache {
($(#[$meta:meta])* $name:ident, |$cache:ident: $ty:ident| $body:block) => {
$(#[$meta])*
mod $name {
use super::*;
fn run<$ty: ExpanderCache<StdArc> + Default>() {
let $cache = <$ty>::default();
$body
}
#[test]
fn vector() {
run::<VectorExpanderCache<StdArc>>();
}
#[test]
fn hash() {
run::<HashExpanderCache<StdArc>>();
}
#[test]
fn arc_arena() {
run::<ArcArenaStateStore<StdArc>>();
}
}
};
}
for_each_cache!(a_state_is_expanded_once, |cache: C| {
let expander = Counting::default();
for _ in 0..3 {
for state in 0..20 {
check_state(cache.find_or_expand(&expander, state), state);
}
}
for state in 0..20 {
assert_eq!(expander.count(state), 1, "state {state}");
}
});
for_each_cache!(find_does_not_expand, |cache: C| {
let expander = Counting::default();
assert!(cache.find(7).is_none());
check_state(cache.find_or_expand(&expander, 7), 7);
check_state(cache.find(7).expect("expanded"), 7);
assert!(cache.find(8).is_none());
assert_eq!(expander.expansions.borrow().len(), 1);
});
for_each_cache!(
views_survive_later_expansions, |cache: C| {
let expander = Counting::default();
let mut views = Vec::new();
for state in 0..2000 {
views.push((state, cache.find_or_expand(&expander, state)));
}
for (state, view) in views {
check_state(view, state);
}
});
for_each_cache!(
sparse_state_ids_are_cached_where_they_belong, |cache: C| {
let expander = Counting::default();
let mut views = Vec::new();
for state in [900, 3, 5000, 17, 1, 4999] {
views.push((state, cache.find_or_expand(&expander, state)));
}
assert!(cache.find(4).is_none());
assert!(cache.find(4998).is_none());
for (state, view) in views {
check_state(view, state);
}
assert_eq!(expander.expansions.borrow().len(), 6);
});
for_each_cache!(
a_clone_shares_nothing, |cache: C| {
let expander = Counting::default();
for state in 0..50 {
cache.find_or_expand(&expander, state);
}
let clone = cache.clone();
for state in 0..50 {
let original = cache.find(state).expect("cached").arcs();
let copied = clone.find(state).expect("copied").arcs();
assert_eq!(original, copied);
if !original.is_empty() {
assert!(
!std::ptr::eq(original.as_ptr(), copied.as_ptr()),
"state {state} shares its arcs with the copy"
);
}
}
clone.find_or_expand(&expander, 50);
assert!(cache.find(50).is_none());
cache.find_or_expand(&expander, 51);
assert!(clone.find(51).is_none());
for state in 0..50 {
check_state(clone.find(state).expect("copied"), state);
}
});
for_each_cache!(
a_re_entrant_expander_keeps_both_states, |cache: C| {
struct Nested<'a, C: ExpanderCache<StdArc>> {
cache: &'a C,
inner: Counting,
}
impl<C: ExpanderCache<StdArc>> Expander<StdArc> for Nested<'_, C> {
fn start(&self) -> i32 {
0
}
fn num_states(&self) -> usize {
0
}
fn expand(&self, state_id: i32, builder: &mut StateBuilder<StdArc>) {
self.inner.expand(state_id, builder);
if state_id == 1 {
check_state(self.cache.find_or_expand(self, 9), 9);
builder.add_arc(StdArc::new(4, 4, TropicalWeight(4.0), 4));
}
}
}
let expander = Nested {
cache: &cache,
inner: Counting::default(),
};
let view = expander.cache.find_or_expand(&expander, 1);
check_state(expander.cache.find(9).expect("cached"), 9);
assert_eq!(view.num_arcs(), 2);
assert_eq!(view.arcs()[0].olabel(), 1);
assert_eq!(view.arcs()[1].olabel(), 4);
assert_eq!(view.arcs(), expander.cache.find(1).expect("cached").arcs());
});
#[test]
fn a_builder_counts_epsilons_on_each_side() {
let mut builder = StateBuilder::<StdArc>::new();
assert_eq!(*builder.final_weight(), TropicalWeight::zero());
assert_eq!(builder.num_arcs(), 0);
builder.set_final(TropicalWeight(2.5));
builder.add_arc(StdArc::new(0, 0, TropicalWeight::one(), 1));
builder.add_arc(StdArc::new(0, 7, TropicalWeight::one(), 2));
builder.add_arc(StdArc::new(7, 0, TropicalWeight::one(), 3));
builder.add_arc(StdArc::new(7, 7, TropicalWeight::one(), 4));
assert_eq!(*builder.final_weight(), TropicalWeight(2.5));
assert_eq!(builder.num_arcs(), 4);
assert_eq!(builder.niepsilons, 2);
assert_eq!(builder.noepsilons, 2);
builder.reset();
assert_eq!(*builder.final_weight(), TropicalWeight::zero());
assert_eq!(builder.num_arcs(), 0);
assert_eq!(builder.niepsilons, 0);
assert_eq!(builder.noepsilons, 0);
}
#[test]
fn the_arena_store_packs_states_into_shared_blocks() {
let cache = ArcArenaStateStore::<StdArc>::new();
let expander = Counting::default();
let views: Vec<_> = (1..5)
.map(|state| cache.find_or_expand(&expander, state).arcs())
.collect();
let first = views[0].as_ptr();
for (i, arcs) in views.iter().enumerate() {
let offset = (0..i).map(|j| views[j].len()).sum::<usize>();
assert_eq!(
arcs.as_ptr(),
unsafe { first.add(offset) },
"run {i} is not where the previous one ended"
);
}
}
}