use super::traits::*;
use crate::arc::{Arc, ArcIterator};
use crate::properties::FstProperties;
use crate::semiring::Semiring;
use core::fmt::Debug;
use core::marker::PhantomData;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CompactFst<W: Semiring, C: Compactor<W>> {
states: Vec<CompactState>,
data: Vec<C::Element>,
final_weights: Vec<Option<W>>,
start: Option<StateId>,
properties: FstProperties,
compactor: C,
_phantom: PhantomData<W>,
}
#[derive(Debug, Clone)]
struct CompactState {
#[allow(dead_code)]
final_weight_idx: Option<u32>,
arcs_start: u32,
num_arcs: u32,
}
pub trait Compactor<W: Semiring>: Debug + Send + Sync + 'static {
type Element: Clone + Debug + Send + Sync;
fn compact(&self, arc: &Arc<W>) -> Self::Element;
fn expand(&self, element: &Self::Element) -> Arc<W>;
fn compact_weight(&self, weight: &W) -> Self::Element;
fn expand_weight(&self, element: &Self::Element) -> W;
}
#[derive(Debug)]
pub struct DefaultCompactor<W: Semiring> {
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for DefaultCompactor<W> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Compactor<W> for DefaultCompactor<W> {
type Element = CompactElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
CompactElement::Arc {
ilabel: arc.ilabel,
olabel: arc.olabel,
weight: arc.weight.clone(),
nextstate: arc.nextstate,
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
match element {
CompactElement::Arc {
ilabel,
olabel,
weight,
nextstate,
} => Arc::new(*ilabel, *olabel, weight.clone(), *nextstate),
_ => panic!("Expected arc element"),
}
}
fn compact_weight(&self, weight: &W) -> Self::Element {
CompactElement::Weight(weight.clone())
}
fn expand_weight(&self, element: &Self::Element) -> W {
match element {
CompactElement::Weight(w) => w.clone(),
_ => panic!("Expected weight element"),
}
}
}
#[derive(Clone, Debug)]
pub enum CompactElement<W: Semiring> {
Arc {
ilabel: Label,
olabel: Label,
weight: W,
nextstate: StateId,
},
Weight(W),
}
#[derive(Debug, Clone)]
pub struct BitPackCompactor<W: Semiring> {
ilabel_bits: u8,
olabel_bits: u8,
state_bits: u8,
weight_bits: u8,
_phantom: PhantomData<W>,
}
impl<W: Semiring> BitPackCompactor<W> {
pub fn new(ilabel_bits: u8, olabel_bits: u8, state_bits: u8) -> Self {
let total_bits = ilabel_bits as u32 + olabel_bits as u32 + state_bits as u32;
assert!(
total_bits <= 48,
"Label and state bits must fit in 48 bits, leaving 16 for weight"
);
assert!(ilabel_bits <= 32 && olabel_bits <= 32 && state_bits <= 32);
Self {
ilabel_bits,
olabel_bits,
state_bits,
weight_bits: 64 - total_bits as u8,
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Default for BitPackCompactor<W> {
fn default() -> Self {
Self::new(16, 16, 16)
}
}
pub trait WeightConverter<T> {
fn to_f64(value: &T) -> f64;
fn from_f64(value: f64) -> T;
}
impl WeightConverter<f32> for f32 {
fn to_f64(value: &f32) -> f64 {
*value as f64
}
fn from_f64(value: f64) -> f32 {
value as f32
}
}
impl WeightConverter<f64> for f64 {
fn to_f64(value: &f64) -> f64 {
*value
}
fn from_f64(value: f64) -> f64 {
value
}
}
impl<W: Semiring> Compactor<W> for BitPackCompactor<W>
where
W::Value: WeightConverter<W::Value> + Copy,
{
type Element = u64;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
let ilabel_bits = self.ilabel_bits;
let olabel_bits = self.olabel_bits;
let state_bits = self.state_bits;
let weight_bits = self.weight_bits;
let ilabel = arc.ilabel & ((1u32 << ilabel_bits) - 1);
let olabel = arc.olabel & ((1u32 << olabel_bits) - 1);
let nextstate = arc.nextstate & ((1u32 << state_bits) - 1);
let weight_val = W::Value::to_f64(arc.weight.value());
let quantized_weight = if weight_val.is_infinite() {
(1u64 << weight_bits) - 1 } else {
let max_weight = (1u64 << weight_bits) - 2;
let clamped = weight_val.max(0.0).min(max_weight as f64);
clamped as u64
};
((ilabel as u64) << (olabel_bits + state_bits + weight_bits))
| ((olabel as u64) << (state_bits + weight_bits))
| ((nextstate as u64) << weight_bits)
| quantized_weight
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
let ilabel_bits = self.ilabel_bits;
let olabel_bits = self.olabel_bits;
let state_bits = self.state_bits;
let weight_bits = self.weight_bits;
let weight_mask = (1u64 << weight_bits) - 1;
let state_mask = (1u64 << state_bits) - 1;
let olabel_mask = (1u64 << olabel_bits) - 1;
let ilabel_mask = (1u64 << ilabel_bits) - 1;
let quantized_weight = element & weight_mask;
let nextstate = ((element >> weight_bits) & state_mask) as u32;
let olabel = ((element >> (weight_bits + state_bits)) & olabel_mask) as u32;
let ilabel = ((element >> (weight_bits + state_bits + olabel_bits)) & ilabel_mask) as u32;
let weight = if quantized_weight == ((1u64 << weight_bits) - 1) {
W::zero() } else {
let weight_val = W::Value::from_f64(quantized_weight as f64);
W::new(weight_val)
};
Arc::new(ilabel, olabel, weight, nextstate)
}
fn compact_weight(&self, weight: &W) -> Self::Element {
let weight_val = W::Value::to_f64(weight.value());
if weight_val.is_infinite() {
u64::MAX
} else {
let clamped = weight_val.max(0.0).min((u64::MAX - 1) as f64);
clamped as u64
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
if *element == u64::MAX {
W::zero() } else {
let weight_val = W::Value::from_f64(*element as f64);
W::new(weight_val)
}
}
}
#[derive(Debug, Clone)]
pub struct QuantizedCompactor<W: Semiring> {
mode: QuantizationMode,
levels: u32,
_phantom: PhantomData<W>,
}
#[derive(Debug, Clone)]
pub enum QuantizationMode {
Linear {
min: f64,
max: f64,
},
Logarithmic {
min: f64,
max: f64,
},
}
impl<W: Semiring> QuantizedCompactor<W> {
pub fn new(mode: QuantizationMode, levels: u32) -> Self {
assert!(
levels > 1 && levels <= 65_536,
"Levels must be between 2 and 65536"
);
Self {
mode,
levels,
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Default for QuantizedCompactor<W> {
fn default() -> Self {
Self::new(
QuantizationMode::Linear {
min: 0.0,
max: 100.0,
},
256,
)
}
}
impl<W: Semiring> Compactor<W> for QuantizedCompactor<W>
where
W::Value: WeightConverter<W::Value> + Copy,
{
type Element = QuantizedArc;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
let weight_val = W::Value::to_f64(arc.weight.value());
let quantized_weight = Self::quantize_weight_value(weight_val, &self.mode, self.levels);
QuantizedArc {
ilabel: arc.ilabel,
olabel: arc.olabel,
quantized_weight,
nextstate: arc.nextstate,
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
let weight_val =
Self::dequantize_weight_value(element.quantized_weight, &self.mode, self.levels);
let weight = W::new(W::Value::from_f64(weight_val));
Arc::new(element.ilabel, element.olabel, weight, element.nextstate)
}
fn compact_weight(&self, weight: &W) -> Self::Element {
let weight_val = W::Value::to_f64(weight.value());
let quantized_weight = Self::quantize_weight_value(weight_val, &self.mode, self.levels);
QuantizedArc {
ilabel: 0,
olabel: 0,
quantized_weight,
nextstate: 0,
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
let weight_val =
Self::dequantize_weight_value(element.quantized_weight, &self.mode, self.levels);
W::new(W::Value::from_f64(weight_val))
}
}
impl<W: Semiring> QuantizedCompactor<W>
where
W::Value: WeightConverter<W::Value> + Copy,
{
fn quantize_weight_value(weight: f64, mode: &QuantizationMode, levels: u32) -> u16 {
if weight.is_infinite() {
return (levels - 1) as u16; }
match mode {
QuantizationMode::Linear { min, max } => {
if weight <= *min {
0
} else if weight >= *max {
(levels - 2) as u16 } else {
let normalized = (weight - min) / (max - min);
let quantized = (normalized * (levels - 2) as f64).round();
quantized.max(0.0).min((levels - 2) as f64) as u16
}
}
QuantizationMode::Logarithmic { min, max } => {
if weight <= *min {
0
} else if weight >= *max {
(levels - 2) as u16
} else {
let log_normalized = (weight / min).ln() / (max / min).ln();
let quantized = (log_normalized * (levels - 2) as f64).round();
quantized.max(0.0).min((levels - 2) as f64) as u16
}
}
}
}
fn dequantize_weight_value(quantized: u16, mode: &QuantizationMode, levels: u32) -> f64 {
if quantized as u32 == levels - 1 {
return f64::INFINITY; }
match mode {
QuantizationMode::Linear { min, max } => {
if quantized == 0 {
*min
} else {
let normalized = quantized as f64 / (levels - 2) as f64;
min + normalized * (max - min)
}
}
QuantizationMode::Logarithmic { min, max } => {
if quantized == 0 {
*min
} else {
let normalized = quantized as f64 / (levels - 2) as f64;
let log_weight = normalized * (max / min).ln();
min * log_weight.exp()
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct QuantizedArc {
ilabel: u32,
olabel: u32,
quantized_weight: u16,
nextstate: u32,
}
#[derive(Debug)]
pub struct DeltaCompactor<W: Semiring> {
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for DeltaCompactor<W> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Compactor<W> for DeltaCompactor<W> {
type Element = DeltaElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
DeltaElement::Absolute {
ilabel: arc.ilabel,
olabel: arc.olabel,
weight: arc.weight.clone(),
nextstate: arc.nextstate,
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
match element {
DeltaElement::Absolute {
ilabel,
olabel,
weight,
nextstate,
} => Arc::new(*ilabel, *olabel, weight.clone(), *nextstate),
DeltaElement::Delta {
ilabel_delta,
olabel_delta,
weight,
nextstate_delta,
} => {
let ilabel = if *ilabel_delta >= 0 {
*ilabel_delta as u32
} else {
0 };
let olabel = if *olabel_delta >= 0 {
*olabel_delta as u32
} else {
0
};
let nextstate = if *nextstate_delta >= 0 {
*nextstate_delta as u32
} else {
0
};
Arc::new(ilabel, olabel, weight.clone(), nextstate)
}
}
}
fn compact_weight(&self, weight: &W) -> Self::Element {
DeltaElement::Absolute {
ilabel: 0,
olabel: 0,
weight: weight.clone(),
nextstate: 0,
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
match element {
DeltaElement::Absolute { weight, .. } => weight.clone(),
DeltaElement::Delta { weight, .. } => weight.clone(),
}
}
}
impl<W: Semiring> DeltaCompactor<W> {
pub fn compute_delta(current_arc: &Arc<W>, previous_arc: &Arc<W>) -> DeltaElement<W> {
let ilabel_delta = current_arc.ilabel as i64 - previous_arc.ilabel as i64;
let olabel_delta = current_arc.olabel as i64 - previous_arc.olabel as i64;
let nextstate_delta = current_arc.nextstate as i64 - previous_arc.nextstate as i64;
if ilabel_delta >= i16::MIN as i64
&& ilabel_delta <= i16::MAX as i64
&& olabel_delta >= i16::MIN as i64
&& olabel_delta <= i16::MAX as i64
&& nextstate_delta >= i16::MIN as i64
&& nextstate_delta <= i16::MAX as i64
{
DeltaElement::Delta {
ilabel_delta: ilabel_delta as i16,
olabel_delta: olabel_delta as i16,
weight: current_arc.weight.clone(),
nextstate_delta: nextstate_delta as i16,
}
} else {
DeltaElement::Absolute {
ilabel: current_arc.ilabel,
olabel: current_arc.olabel,
weight: current_arc.weight.clone(),
nextstate: current_arc.nextstate,
}
}
}
pub fn apply_delta(base_arc: &Arc<W>, delta: &DeltaElement<W>) -> Arc<W> {
match delta {
DeltaElement::Absolute {
ilabel,
olabel,
weight,
nextstate,
} => Arc::new(*ilabel, *olabel, weight.clone(), *nextstate),
DeltaElement::Delta {
ilabel_delta,
olabel_delta,
weight,
nextstate_delta,
} => {
let new_ilabel = (base_arc.ilabel as i64 + *ilabel_delta as i64).max(0) as u32;
let output_label = (base_arc.olabel as i64 + *olabel_delta as i64).max(0) as u32;
let new_nextstate =
(base_arc.nextstate as i64 + *nextstate_delta as i64).max(0) as u32;
Arc::new(new_ilabel, output_label, weight.clone(), new_nextstate)
}
}
}
}
#[derive(Debug, Clone)]
pub enum DeltaElement<W: Semiring> {
Absolute {
ilabel: u32,
olabel: u32,
weight: W,
nextstate: u32,
},
Delta {
ilabel_delta: i16,
olabel_delta: i16,
weight: W,
nextstate_delta: i16,
},
}
#[derive(Debug, Clone)]
pub struct VarIntCompactor<W: Semiring> {
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for VarIntCompactor<W> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Compactor<W> for VarIntCompactor<W> {
type Element = VarIntElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
VarIntElement {
encoded_ilabel: encode_varint(arc.ilabel),
encoded_olabel: encode_varint(arc.olabel),
weight: arc.weight.clone(),
encoded_nextstate: encode_varint(arc.nextstate),
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
Arc::new(
decode_varint(&element.encoded_ilabel),
decode_varint(&element.encoded_olabel),
element.weight.clone(),
decode_varint(&element.encoded_nextstate),
)
}
fn compact_weight(&self, weight: &W) -> Self::Element {
VarIntElement {
encoded_ilabel: vec![0],
encoded_olabel: vec![0],
weight: weight.clone(),
encoded_nextstate: vec![0],
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
element.weight.clone()
}
}
#[derive(Debug, Clone)]
pub struct VarIntElement<W: Semiring> {
encoded_ilabel: Vec<u8>,
encoded_olabel: Vec<u8>,
weight: W,
encoded_nextstate: Vec<u8>,
}
fn encode_varint(value: u32) -> Vec<u8> {
let mut result = Vec::new();
let mut val = value;
while val >= 0x80 {
result.push((val & 0x7F) as u8 | 0x80);
val >>= 7;
}
result.push(val as u8);
result
}
fn decode_varint(bytes: &[u8]) -> u32 {
let mut result = 0u32;
let mut shift = 0;
for &byte in bytes {
result |= ((byte & 0x7F) as u32) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
result
}
#[derive(Debug)]
pub struct RunLengthCompactor<W: Semiring> {
#[allow(dead_code)]
similarity_threshold: f32,
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for RunLengthCompactor<W> {
fn default() -> Self {
Self::new(0.1) }
}
impl<W: Semiring> RunLengthCompactor<W> {
pub fn new(similarity_threshold: f32) -> Self {
Self {
similarity_threshold,
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Compactor<W> for RunLengthCompactor<W> {
type Element = RunLengthElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
RunLengthElement::Single(arc.clone())
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
match element {
RunLengthElement::Single(arc) => arc.clone(),
RunLengthElement::Run { base_arc, .. } => base_arc.clone(),
RunLengthElement::WeightRun { .. } => {
panic!("Cannot expand weight run element as arc")
}
}
}
fn compact_weight(&self, weight: &W) -> Self::Element {
RunLengthElement::WeightRun {
weight: weight.clone(),
count: 1,
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
match element {
RunLengthElement::Single(arc) => arc.weight.clone(),
RunLengthElement::Run { base_arc, .. } => base_arc.weight.clone(),
RunLengthElement::WeightRun { weight, .. } => weight.clone(),
}
}
}
#[derive(Debug, Clone)]
pub enum RunLengthElement<W: Semiring> {
Single(Arc<W>),
Run { base_arc: Arc<W>, count: u32 },
WeightRun { weight: W, count: u32 },
}
#[derive(Debug)]
pub struct HuffmanCompactor<W: Semiring> {
ilabel_frequencies: HashMap<u32, u32>,
olabel_frequencies: HashMap<u32, u32>,
ilabel_codes: HashMap<u32, Vec<u8>>,
olabel_codes: HashMap<u32, Vec<u8>>,
ilabel_decode: HashMap<Vec<u8>, u32>,
olabel_decode: HashMap<Vec<u8>, u32>,
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for HuffmanCompactor<W> {
fn default() -> Self {
Self::new()
}
}
impl<W: Semiring> HuffmanCompactor<W> {
pub fn new() -> Self {
Self {
ilabel_frequencies: HashMap::new(),
olabel_frequencies: HashMap::new(),
ilabel_codes: HashMap::new(),
olabel_codes: HashMap::new(),
ilabel_decode: HashMap::new(),
olabel_decode: HashMap::new(),
_phantom: PhantomData,
}
}
pub fn analyze_fst<F: Fst<W>>(&mut self, fst: &F) {
for state in fst.states() {
for arc in fst.arcs(state) {
*self.ilabel_frequencies.entry(arc.ilabel).or_insert(0) += 1;
*self.olabel_frequencies.entry(arc.olabel).or_insert(0) += 1;
}
}
self.ilabel_codes = build_huffman_codes(&self.ilabel_frequencies);
self.olabel_codes = build_huffman_codes(&self.olabel_frequencies);
for (label, code) in &self.ilabel_codes {
self.ilabel_decode.insert(code.clone(), *label);
}
for (label, code) in &self.olabel_codes {
self.olabel_decode.insert(code.clone(), *label);
}
}
}
impl<W: Semiring> Compactor<W> for HuffmanCompactor<W> {
type Element = HuffmanElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
let encoded_ilabel = self
.ilabel_codes
.get(&arc.ilabel)
.cloned()
.unwrap_or_else(|| encode_varint(arc.ilabel));
let encoded_olabel = self
.olabel_codes
.get(&arc.olabel)
.cloned()
.unwrap_or_else(|| encode_varint(arc.olabel));
HuffmanElement {
encoded_ilabel,
encoded_olabel,
weight: arc.weight.clone(),
nextstate: arc.nextstate,
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
let ilabel = self
.ilabel_decode
.get(&element.encoded_ilabel)
.copied()
.unwrap_or_else(|| decode_varint(&element.encoded_ilabel));
let olabel = self
.olabel_decode
.get(&element.encoded_olabel)
.copied()
.unwrap_or_else(|| decode_varint(&element.encoded_olabel));
Arc::new(ilabel, olabel, element.weight.clone(), element.nextstate)
}
fn compact_weight(&self, weight: &W) -> Self::Element {
HuffmanElement {
encoded_ilabel: vec![0],
encoded_olabel: vec![0],
weight: weight.clone(),
nextstate: 0,
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
element.weight.clone()
}
}
#[derive(Debug, Clone)]
pub struct HuffmanElement<W: Semiring> {
encoded_ilabel: Vec<u8>,
encoded_olabel: Vec<u8>,
weight: W,
nextstate: StateId,
}
#[derive(Debug)]
pub struct LZ4Compactor<W: Semiring> {
#[allow(dead_code)]
dictionary_size: usize,
#[allow(dead_code)]
min_match_length: usize,
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for LZ4Compactor<W> {
fn default() -> Self {
Self::new(1024, 4) }
}
impl<W: Semiring> LZ4Compactor<W> {
pub fn new(dictionary_size: usize, min_match_length: usize) -> Self {
Self {
dictionary_size,
min_match_length,
_phantom: PhantomData,
}
}
}
impl<W: Semiring> Compactor<W> for LZ4Compactor<W> {
type Element = LZ4Element<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
LZ4Element::Literal(arc.clone())
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
match element {
LZ4Element::Literal(arc) => arc.clone(),
LZ4Element::Reference { base_arc, .. } => base_arc.clone(),
LZ4Element::WeightLiteral(_) => {
panic!("Cannot expand weight literal element as arc")
}
LZ4Element::WeightReference { .. } => {
panic!("Cannot expand weight reference element as arc")
}
}
}
fn compact_weight(&self, weight: &W) -> Self::Element {
LZ4Element::WeightLiteral(weight.clone())
}
fn expand_weight(&self, element: &Self::Element) -> W {
match element {
LZ4Element::Literal(arc) => arc.weight.clone(),
LZ4Element::Reference { base_arc, .. } => base_arc.weight.clone(),
LZ4Element::WeightLiteral(weight) => weight.clone(),
LZ4Element::WeightReference { weight, .. } => weight.clone(),
}
}
}
#[derive(Debug, Clone)]
pub enum LZ4Element<W: Semiring> {
Literal(Arc<W>),
Reference {
offset: u16,
length: u16,
base_arc: Arc<W>, },
WeightLiteral(W),
WeightReference {
offset: u16,
weight: W, },
}
#[derive(Debug)]
pub struct ContextCompactor<W: Semiring> {
#[allow(dead_code)]
context_size: usize,
#[allow(dead_code)]
adaptation_threshold: f32,
#[allow(dead_code)]
context_patterns: HashMap<Vec<u32>, CompressionMode>,
_phantom: PhantomData<W>,
}
impl<W: Semiring> Default for ContextCompactor<W> {
fn default() -> Self {
Self::new(4, 0.2) }
}
impl<W: Semiring> ContextCompactor<W> {
pub fn new(context_size: usize, adaptation_threshold: f32) -> Self {
Self {
context_size,
adaptation_threshold,
context_patterns: HashMap::new(),
_phantom: PhantomData,
}
}
#[allow(dead_code)]
fn analyze_context(&self, _context: &[Arc<W>]) -> CompressionMode {
CompressionMode::VarInt
}
}
impl<W: Semiring> Compactor<W> for ContextCompactor<W> {
type Element = ContextElement<W>;
fn compact(&self, arc: &Arc<W>) -> Self::Element {
ContextElement {
mode: CompressionMode::VarInt,
data: ContextData::VarInt {
encoded_ilabel: encode_varint(arc.ilabel),
encoded_olabel: encode_varint(arc.olabel),
weight: arc.weight.clone(),
encoded_nextstate: encode_varint(arc.nextstate),
},
}
}
fn expand(&self, element: &Self::Element) -> Arc<W> {
match &element.data {
ContextData::VarInt {
encoded_ilabel,
encoded_olabel,
weight,
encoded_nextstate,
} => Arc::new(
decode_varint(encoded_ilabel),
decode_varint(encoded_olabel),
weight.clone(),
decode_varint(encoded_nextstate),
),
ContextData::Delta { base_arc, .. } => base_arc.clone(),
ContextData::RunLength { base_arc, .. } => base_arc.clone(),
}
}
fn compact_weight(&self, weight: &W) -> Self::Element {
ContextElement {
mode: CompressionMode::VarInt,
data: ContextData::VarInt {
encoded_ilabel: vec![0],
encoded_olabel: vec![0],
weight: weight.clone(),
encoded_nextstate: vec![0],
},
}
}
fn expand_weight(&self, element: &Self::Element) -> W {
match &element.data {
ContextData::VarInt { weight, .. } => weight.clone(),
ContextData::Delta { base_arc, .. } => base_arc.weight.clone(),
ContextData::RunLength { base_arc, .. } => base_arc.weight.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct ContextElement<W: Semiring> {
#[allow(dead_code)]
mode: CompressionMode,
data: ContextData<W>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum CompressionMode {
VarInt,
Delta,
RunLength,
Huffman,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum ContextData<W: Semiring> {
VarInt {
encoded_ilabel: Vec<u8>,
encoded_olabel: Vec<u8>,
weight: W,
encoded_nextstate: Vec<u8>,
},
Delta {
base_arc: Arc<W>,
deltas: Vec<i16>,
},
RunLength {
base_arc: Arc<W>,
count: u32,
},
}
fn build_huffman_codes(frequencies: &HashMap<u32, u32>) -> HashMap<u32, Vec<u8>> {
let mut codes = HashMap::new();
let mut sorted_items: Vec<_> = frequencies.iter().collect();
sorted_items.sort_by(|a, b| b.1.cmp(a.1));
for (i, (&label, _)) in sorted_items.iter().enumerate() {
let code_length = (i / 2 + 1).min(8); let mut code = vec![0u8; code_length];
let mut val = i;
for bit in code.iter_mut().take(code_length) {
*bit = (val & 1) as u8;
val >>= 1;
}
codes.insert(label, code);
}
codes
}
impl<W: Semiring, C: Compactor<W> + Default> Default for CompactFst<W, C> {
fn default() -> Self {
Self::new()
}
}
impl<W: Semiring, C: Compactor<W>> CompactFst<W, C> {
pub fn new() -> Self
where
C: Default,
{
Self {
states: Vec::new(),
data: Vec::new(),
final_weights: Vec::new(),
start: None,
properties: FstProperties::default(),
compactor: C::default(),
_phantom: PhantomData,
}
}
pub fn with_compactor(compactor: C) -> Self {
Self {
states: Vec::new(),
data: Vec::new(),
final_weights: Vec::new(),
start: None,
properties: FstProperties::default(),
compactor,
_phantom: PhantomData,
}
}
pub fn from_fst<F: Fst<W>>(fst: &F) -> Self
where
C: Default,
{
let mut compact_fst = Self::new();
for _ in 0..fst.num_states() {
compact_fst.add_state();
}
compact_fst.start = fst.start();
for state_idx in 0..fst.num_states() {
let state = state_idx as StateId;
if let Some(weight) = fst.final_weight(state) {
compact_fst.set_final_weight(state, Some(weight.clone()));
}
}
let mut data_offset = 0u32;
for state_idx in 0..fst.num_states() {
let state = state_idx as StateId;
let arcs: Vec<_> = fst.arcs(state).collect();
let num_arcs = arcs.len() as u32;
compact_fst.states[state_idx].arcs_start = data_offset;
compact_fst.states[state_idx].num_arcs = num_arcs;
for arc in arcs {
let compressed_arc = compact_fst.compactor.compact(&arc);
compact_fst.data.push(compressed_arc);
}
data_offset += num_arcs;
}
compact_fst
}
pub fn set_final_weight(&mut self, state: StateId, weight: Option<W>) {
let state_idx = state as usize;
if self.final_weights.len() <= state_idx {
self.final_weights.resize(state_idx + 1, None);
}
self.final_weights[state_idx] = weight;
}
pub fn add_state(&mut self) -> StateId {
let state_id = self.states.len() as StateId;
self.states.push(CompactState {
final_weight_idx: None,
arcs_start: 0,
num_arcs: 0,
});
self.final_weights.push(None);
state_id
}
}
#[derive(Debug)]
pub struct CompactArcIterator<'a, W: Semiring, C: Compactor<W>> {
data: &'a [C::Element],
compactor: &'a C,
pos: usize,
end: usize,
_phantom: PhantomData<W>,
}
impl<W: Semiring, C: Compactor<W>> ArcIterator<W> for CompactArcIterator<'_, W, C> {
fn reset(&mut self) {
self.pos = 0;
}
}
impl<W: Semiring, C: Compactor<W>> Iterator for CompactArcIterator<'_, W, C> {
type Item = Arc<W>;
fn next(&mut self) -> Option<Self::Item> {
if self.pos < self.end {
let arc = self.compactor.expand(&self.data[self.pos]);
self.pos += 1;
Some(arc)
} else {
None
}
}
}
impl<W: Semiring, C: Compactor<W>> Fst<W> for CompactFst<W, C> {
type ArcIter<'a>
= CompactArcIterator<'a, W, C>
where
W: 'a,
C: 'a;
fn start(&self) -> Option<StateId> {
self.start
}
fn final_weight(&self, state: StateId) -> Option<&W> {
self.final_weights
.get(state as usize)
.and_then(|weight| weight.as_ref())
}
fn num_arcs(&self, state: StateId) -> usize {
self.states
.get(state as usize)
.map(|s| s.num_arcs as usize)
.unwrap_or(0)
}
fn num_states(&self) -> usize {
self.states.len()
}
fn properties(&self) -> FstProperties {
self.properties
}
fn arcs(&self, state: StateId) -> Self::ArcIter<'_> {
if let Some(s) = self.states.get(state as usize) {
let start = s.arcs_start as usize;
let end = start + s.num_arcs as usize;
CompactArcIterator {
data: &self.data,
compactor: &self.compactor,
pos: start,
end,
_phantom: PhantomData,
}
} else {
CompactArcIterator {
data: &self.data,
compactor: &self.compactor,
pos: 0,
end: 0,
_phantom: PhantomData,
}
}
}
}
impl<W: Semiring, C: Compactor<W>> MutableFst<W> for CompactFst<W, C> {
fn add_state(&mut self) -> StateId {
let new_state_id = self.states.len() as StateId;
self.states.push(CompactState {
final_weight_idx: None,
arcs_start: self.data.len() as u32,
num_arcs: 0,
});
self.final_weights.push(None);
if self.states.len() % 1000 == 0 {
self.maybe_recompress();
}
new_state_id
}
fn add_arc(&mut self, state: StateId, arc: Arc<W>) {
let state_idx = state as usize;
if state_idx >= self.states.len() {
return; }
let compact_arc = self.compactor.compact(&arc);
let arcs_start = self.states[state_idx].arcs_start as usize;
let num_arcs = self.states[state_idx].num_arcs as usize;
let insert_pos = arcs_start + num_arcs;
self.data.insert(insert_pos, compact_arc);
self.states[state_idx].num_arcs += 1;
for i in 0..self.states.len() {
if self.states[i].arcs_start as usize > insert_pos {
self.states[i].arcs_start += 1;
}
}
if self.data.len() > self.states.len() * 10 {
self.maybe_recompress();
}
}
fn set_start(&mut self, state: StateId) {
if (state as usize) < self.states.len() {
self.start = Some(state);
}
}
fn set_final(&mut self, state: StateId, weight: W) {
let state_idx = state as usize;
if state_idx < self.final_weights.len() {
self.final_weights[state_idx] = Some(weight);
if state_idx < self.states.len() {
self.states[state_idx].final_weight_idx = Some(state_idx as u32);
}
}
}
fn delete_arcs(&mut self, state: StateId) {
let state_idx = state as usize;
if state_idx >= self.states.len() {
return;
}
let arcs_start = self.states[state_idx].arcs_start as usize;
let num_arcs = self.states[state_idx].num_arcs as usize;
if num_arcs == 0 {
return;
}
self.data.drain(arcs_start..arcs_start + num_arcs);
self.states[state_idx].num_arcs = 0;
for i in 0..self.states.len() {
if self.states[i].arcs_start as usize > arcs_start {
self.states[i].arcs_start -= num_arcs as u32;
}
}
self.maybe_recompress();
}
fn delete_arc(&mut self, state: StateId, arc_idx: usize) {
let state_idx = state as usize;
if state_idx >= self.states.len() {
return;
}
let arcs_start = self.states[state_idx].arcs_start as usize;
let num_arcs = self.states[state_idx].num_arcs as usize;
if arc_idx >= num_arcs {
return; }
let delete_pos = arcs_start + arc_idx;
self.data.remove(delete_pos);
self.states[state_idx].num_arcs -= 1;
for i in 0..self.states.len() {
if self.states[i].arcs_start as usize > delete_pos {
self.states[i].arcs_start -= 1;
}
}
}
fn reserve_states(&mut self, n: usize) {
self.states.reserve(n);
self.final_weights.reserve(n);
}
fn reserve_arcs(&mut self, _state: StateId, n: usize) {
self.data.reserve(n);
}
fn clear(&mut self) {
self.states.clear();
self.data.clear();
self.final_weights.clear();
self.start = None;
}
}
impl<W: Semiring, C: Compactor<W>> CompactFst<W, C> {
fn maybe_recompress(&mut self) {
let total_arcs: usize = self.states.iter().map(|s| s.num_arcs as usize).sum();
let data_overhead = self.data.len().saturating_sub(total_arcs);
if data_overhead > total_arcs / 5 {
self.recompress_data();
}
}
fn recompress_data(&mut self) {
let mut new_data = Vec::new();
let mut new_states = Vec::new();
for state in self.states.iter() {
let arcs_start = state.arcs_start as usize;
let num_arcs = state.num_arcs as usize;
let state_arcs: Vec<_> = self.data[arcs_start..arcs_start + num_arcs]
.iter()
.map(|elem| self.compactor.expand(elem))
.collect();
let new_arcs_start = new_data.len() as u32;
for arc in state_arcs {
new_data.push(self.compactor.compact(&arc));
}
new_states.push(CompactState {
final_weight_idx: state.final_weight_idx,
arcs_start: new_arcs_start,
num_arcs: state.num_arcs,
});
}
self.states = new_states;
self.data = new_data;
self.states.shrink_to_fit();
self.data.shrink_to_fit();
}
pub fn compression_ratio(&self) -> f64 {
let compressed_size = std::mem::size_of_val(&*self.data)
+ std::mem::size_of_val(&*self.states)
+ std::mem::size_of_val(&*self.final_weights);
let estimated_uncompressed = self.states.len() * std::mem::size_of::<StateId>()
+ self.data.len() * std::mem::size_of::<Arc<W>>();
if estimated_uncompressed == 0 {
1.0
} else {
compressed_size as f64 / estimated_uncompressed as f64
}
}
pub fn force_recompress(&mut self) {
self.recompress_data();
}
pub fn enable_adaptive_compression(&mut self, config: AdaptiveConfig) {
let _ = config; }
pub fn enable_streaming(&mut self, config: StreamingConfig) {
let _ = config; }
pub fn analyze_compression_patterns(&self) -> CompressionAnalysis {
let total_arcs: usize = self.states.iter().map(|s| s.num_arcs as usize).sum();
let avg_arcs_per_state = if self.states.is_empty() {
0.0
} else {
total_arcs as f64 / self.states.len() as f64
};
let mut label_distribution = HashMap::new();
for state in 0..self.states.len() {
let arcs = self.expanded_arcs(state as StateId);
for arc in arcs {
*label_distribution.entry(arc.ilabel).or_insert(0) += 1;
}
}
let _total_labels = label_distribution.values().sum::<u32>() as f64;
let unique_labels = label_distribution.len();
let recommended_strategy = if unique_labels < 256 && avg_arcs_per_state > 50.0 {
CompressionStrategy::Huffman } else if avg_arcs_per_state < 10.0 {
CompressionStrategy::VarInt } else if total_arcs > 10000 {
CompressionStrategy::LZ4 } else {
CompressionStrategy::Default };
let expected_ratio = match recommended_strategy {
CompressionStrategy::Huffman => 0.4, CompressionStrategy::VarInt => 0.6, CompressionStrategy::LZ4 => 0.5, CompressionStrategy::RunLength => 0.3, CompressionStrategy::Context => 0.35, CompressionStrategy::Default => 0.8, };
CompressionAnalysis {
recommended_strategy,
expected_ratio,
current_ratio: self.compression_ratio(),
data_characteristics: DataCharacteristics {
total_states: self.states.len(),
total_arcs,
avg_arcs_per_state,
unique_labels,
label_entropy: calculate_entropy(&label_distribution),
has_repetitive_patterns: detect_repetitive_patterns(&self.states),
},
memory_usage: std::mem::size_of_val(&*self.data) + std::mem::size_of_val(&*self.states),
}
}
pub fn stream_construct<I>(&mut self, input_stream: I, config: StreamingConfig)
where
I: Iterator<Item = Arc<W>>,
{
let mut chunk = Vec::with_capacity(config.chunk_size);
let mut processed = 0;
for arc in input_stream {
chunk.push(arc);
if chunk.len() >= config.chunk_size {
self.process_chunk(&chunk, &config);
chunk.clear();
processed += config.chunk_size;
if let Some(limit) = config.memory_limit {
if self.estimated_memory_usage() > limit {
self.flush_to_external_storage(&config);
}
}
if let Some(ref callback) = config.progress_callback {
callback(processed);
}
}
}
if !chunk.is_empty() {
self.process_chunk(&chunk, &config);
}
}
fn process_chunk(&mut self, chunk: &[Arc<W>], _config: &StreamingConfig) {
let mut state_arcs: HashMap<StateId, Vec<Arc<W>>> = HashMap::new();
for arc in chunk {
let source_state = arc.nextstate.saturating_sub(1);
state_arcs
.entry(source_state)
.or_default()
.push(arc.clone());
}
for (state, arcs) in state_arcs {
while self.states.len() <= state as usize {
self.add_state();
}
for arc in arcs {
self.add_arc(state, arc);
}
}
}
fn flush_to_external_storage(&mut self, _config: &StreamingConfig) {
self.force_recompress();
}
fn estimated_memory_usage(&self) -> usize {
std::mem::size_of_val(&*self.states)
+ std::mem::size_of_val(&*self.data)
+ std::mem::size_of_val(&*self.final_weights)
+ std::mem::size_of_val(&self.compactor)
}
}
impl<W: Semiring, C: Compactor<W>> ExpandedFst<W> for CompactFst<W, C> {
fn arcs_slice(&self, _state: StateId) -> &[Arc<W>] {
&[]
}
}
impl<W: Semiring, C: Compactor<W>> CompactFst<W, C> {
pub fn expanded_arcs(&self, state: StateId) -> Vec<Arc<W>> {
if state as usize >= self.states.len() {
return Vec::new();
}
let compact_state = &self.states[state as usize];
let arcs_start = compact_state.arcs_start as usize;
let num_arcs = compact_state.num_arcs as usize;
if num_arcs == 0 {
return Vec::new();
}
self.data[arcs_start..arcs_start + num_arcs]
.iter()
.map(|compressed_arc| self.compactor.expand(compressed_arc))
.collect()
}
pub fn expanded_arcs_cached(&self, state: StateId) -> Vec<Arc<W>> {
self.expanded_arcs(state)
}
pub fn prefetch_arcs<I>(&self, states: I)
where
I: IntoIterator<Item = StateId>,
{
for state in states {
let _arcs = self.expanded_arcs(state);
}
}
pub fn clear_arc_cache(&self) {
}
pub fn cache_stats(&self) -> CacheStats {
CacheStats {
cache_hits: 0,
cache_misses: 0,
cache_size: 0,
memory_usage: 0,
evictions: 0,
}
}
pub fn set_prefetching(&mut self, _enabled: bool) {
}
pub fn batch_expand_arcs(&self, states: &[StateId]) -> HashMap<StateId, Vec<Arc<W>>> {
let mut result = HashMap::with_capacity(states.len());
for &state in states {
if (state as usize) < self.states.len() {
result.insert(state, self.expanded_arcs(state));
}
}
result
}
pub fn supports_efficient_expansion(&self) -> bool {
let total_arcs: usize = self.states.iter().map(|s| s.num_arcs as usize).sum();
let avg_arcs_per_state = if self.states.is_empty() {
0.0
} else {
total_arcs as f64 / self.states.len() as f64
};
avg_arcs_per_state <= 100.0 && self.states.len() <= 10000
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub cache_hits: u64,
pub cache_misses: u64,
pub cache_size: usize,
pub memory_usage: usize,
pub evictions: u64,
}
impl CacheStats {
pub fn hit_rate(&self) -> f64 {
let total = self.cache_hits + self.cache_misses;
if total == 0 {
0.0
} else {
(self.cache_hits as f64 / total as f64) * 100.0
}
}
pub fn is_performing_well(&self) -> bool {
self.hit_rate() > 80.0 && self.memory_usage < 100 * 1024 * 1024 }
}
#[derive(Debug, Clone)]
pub struct AdaptiveConfig {
pub enable_streaming: bool,
pub memory_limit: usize,
pub compression_threshold: f64,
pub analysis_window: usize,
}
impl Default for AdaptiveConfig {
fn default() -> Self {
Self {
enable_streaming: false,
memory_limit: 100_000_000, compression_threshold: 0.7,
analysis_window: 1000,
}
}
}
#[derive(Debug, Clone)]
pub struct StreamingConfig {
pub chunk_size: usize,
pub temp_dir: String,
pub memory_limit: Option<usize>,
pub progress_callback: Option<fn(usize)>,
}
impl Default for StreamingConfig {
fn default() -> Self {
Self {
chunk_size: 10000,
temp_dir: "/tmp/arcweight".to_string(),
memory_limit: Some(500_000_000), progress_callback: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompressionStrategy {
Default,
VarInt,
RunLength,
Huffman,
LZ4,
Context,
}
#[derive(Debug, Clone)]
pub struct CompressionAnalysis {
pub recommended_strategy: CompressionStrategy,
pub expected_ratio: f64,
pub current_ratio: f64,
pub data_characteristics: DataCharacteristics,
pub memory_usage: usize,
}
#[derive(Debug, Clone)]
pub struct DataCharacteristics {
pub total_states: usize,
pub total_arcs: usize,
pub avg_arcs_per_state: f64,
pub unique_labels: usize,
pub label_entropy: f64,
pub has_repetitive_patterns: bool,
}
fn calculate_entropy(distribution: &HashMap<u32, u32>) -> f64 {
let total: u32 = distribution.values().sum();
if total == 0 {
return 0.0;
}
let mut entropy = 0.0;
for &count in distribution.values() {
if count > 0 {
let probability = count as f64 / total as f64;
entropy -= probability * probability.log2();
}
}
entropy
}
fn detect_repetitive_patterns(states: &[CompactState]) -> bool {
if states.len() < 10 {
return false;
}
let mut arc_count_freq = HashMap::new();
for state in states {
*arc_count_freq.entry(state.num_arcs).or_insert(0) += 1;
}
let max_freq = arc_count_freq.values().max().unwrap_or(&0);
(*max_freq as f64 / states.len() as f64) > 0.5
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_compact_fst_new() {
let fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
assert_eq!(fst.num_states(), 0);
assert!(fst.start().is_none());
assert_eq!(fst.states.len(), 0);
assert_eq!(fst.data.len(), 0);
assert_eq!(fst.final_weights.len(), 0);
}
#[test]
fn test_compact_fst_add_state() {
let mut fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
assert_eq!(s0, 0);
assert_eq!(s1, 1);
assert_eq!(s2, 2);
assert_eq!(fst.num_states(), 3);
assert_eq!(fst.states.len(), 3);
assert_eq!(fst.final_weights.len(), 3);
}
#[test]
fn test_compact_fst_start_state() {
let mut fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
assert!(fst.start().is_none());
let s0 = fst.add_state();
let s1 = fst.add_state();
assert!(fst.start().is_none());
fst.start = Some(s0);
assert_eq!(fst.start(), Some(s0));
fst.start = Some(s1);
assert_eq!(fst.start(), Some(s1));
}
#[test]
fn test_compact_fst_final_weights() {
let mut fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
let s2 = fst.add_state();
assert!(fst.final_weight(s0).is_none());
assert!(fst.final_weight(s1).is_none());
assert!(fst.final_weight(s2).is_none());
fst.set_final_weight(s0, Some(TropicalWeight::new(1.5)));
fst.set_final_weight(s2, Some(TropicalWeight::one()));
assert_eq!(fst.final_weight(s0), Some(&TropicalWeight::new(1.5)));
assert!(fst.final_weight(s1).is_none());
assert_eq!(fst.final_weight(s2), Some(&TropicalWeight::one()));
fst.set_final_weight(s0, Some(TropicalWeight::new(2.5)));
assert_eq!(fst.final_weight(s0), Some(&TropicalWeight::new(2.5)));
fst.set_final_weight(s0, None);
assert!(fst.final_weight(s0).is_none());
}
#[test]
fn test_compact_fst_final_weight_bounds() {
let mut fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let _s0 = fst.add_state();
assert!(fst.final_weight(10).is_none());
fst.set_final_weight(5, Some(TropicalWeight::new(std::f32::consts::PI)));
assert_eq!(fst.final_weights.len(), 6); assert_eq!(
fst.final_weight(5),
Some(&TropicalWeight::new(std::f32::consts::PI))
);
for i in 1..5 {
assert!(fst.final_weight(i).is_none());
}
}
#[test]
fn test_compact_fst_num_arcs() {
let fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
assert_eq!(fst.num_arcs(0), 0);
assert_eq!(fst.num_arcs(100), 0);
let mut fst = fst;
let s0 = fst.add_state();
let s1 = fst.add_state();
assert_eq!(fst.num_arcs(s0), 0);
assert_eq!(fst.num_arcs(s1), 0);
fst.states[s0 as usize].num_arcs = 3;
fst.states[s1 as usize].num_arcs = 1;
assert_eq!(fst.num_arcs(s0), 3);
assert_eq!(fst.num_arcs(s1), 1);
}
#[test]
fn test_compact_fst_properties() {
let fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let props = fst.properties();
let default_props = FstProperties::default();
assert_eq!(props.known, default_props.known);
assert_eq!(props.properties, default_props.properties);
}
#[test]
fn test_compact_fst_arcs_empty() {
let mut fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let s0 = fst.add_state();
let arcs: Vec<_> = fst.arcs(s0).collect();
assert_eq!(arcs.len(), 0);
let arcs: Vec<_> = fst.arcs(100).collect();
assert_eq!(arcs.len(), 0);
}
#[test]
fn test_compact_fst_with_boolean_weights() {
let mut fst = CompactFst::<BooleanWeight, DefaultCompactor<BooleanWeight>>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_final_weight(s0, Some(BooleanWeight::one()));
fst.set_final_weight(s1, Some(BooleanWeight::zero()));
assert_eq!(fst.final_weight(s0), Some(&BooleanWeight::one()));
assert_eq!(fst.final_weight(s1), Some(&BooleanWeight::zero()));
assert_eq!(fst.num_states(), 2);
}
#[test]
fn test_compact_fst_with_log_weights() {
let mut fst = CompactFst::<LogWeight, DefaultCompactor<LogWeight>>::new();
let s0 = fst.add_state();
let s1 = fst.add_state();
fst.set_final_weight(s0, Some(LogWeight::new(std::f64::consts::E)));
fst.set_final_weight(s1, Some(LogWeight::one()));
assert_eq!(
fst.final_weight(s0),
Some(&LogWeight::new(std::f64::consts::E))
);
assert_eq!(fst.final_weight(s1), Some(&LogWeight::one()));
}
#[test]
fn test_default_compactor_arc_compression() {
let arc = Arc::new(10, 20, TropicalWeight::new(1.5), 30);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&arc);
let expanded = compactor.expand(&compressed);
assert_eq!(arc.ilabel, expanded.ilabel);
assert_eq!(arc.olabel, expanded.olabel);
assert_eq!(arc.weight, expanded.weight);
assert_eq!(arc.nextstate, expanded.nextstate);
}
#[test]
fn test_default_compactor_weight_compression() {
let weight = TropicalWeight::new(std::f32::consts::PI);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let compressed = compactor.compact_weight(&weight);
let expanded = compactor.expand_weight(&compressed);
assert_eq!(weight, expanded);
}
#[test]
fn test_default_compactor_zero_one_weights() {
let zero = TropicalWeight::zero();
let one = TropicalWeight::one();
let compactor = DefaultCompactor::<TropicalWeight>::default();
let compressed_zero = compactor.compact_weight(&zero);
let expanded_zero = compactor.expand_weight(&compressed_zero);
assert_eq!(zero, expanded_zero);
assert!(crate::semiring::Semiring::is_zero(&expanded_zero));
let compressed_one = compactor.compact_weight(&one);
let expanded_one = compactor.expand_weight(&compressed_one);
assert_eq!(one, expanded_one);
assert!(crate::semiring::Semiring::is_one(&expanded_one));
}
#[test]
fn test_default_compactor_epsilon_arc() {
let epsilon_arc = Arc::epsilon(TropicalWeight::new(0.5), 42);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&epsilon_arc);
let expanded = compactor.expand(&compressed);
assert_eq!(epsilon_arc.ilabel, 0);
assert_eq!(epsilon_arc.olabel, 0);
assert_eq!(expanded.ilabel, 0);
assert_eq!(expanded.olabel, 0);
assert_eq!(expanded.weight, epsilon_arc.weight);
assert_eq!(expanded.nextstate, 42);
}
#[test]
fn test_default_compactor_large_labels() {
let large_arc = Arc::new(
u32::MAX - 1,
u32::MAX,
TropicalWeight::new(1000.0),
u32::MAX - 2,
);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&large_arc);
let expanded = compactor.expand(&compressed);
assert_eq!(large_arc.ilabel, expanded.ilabel);
assert_eq!(large_arc.olabel, expanded.olabel);
assert_eq!(large_arc.weight, expanded.weight);
assert_eq!(large_arc.nextstate, expanded.nextstate);
}
#[test]
fn test_compact_element_arc_variant() {
let element = CompactElement::Arc {
ilabel: 100,
olabel: 200,
weight: TropicalWeight::new(2.5),
nextstate: 300,
};
if let CompactElement::Arc {
ilabel,
olabel,
weight,
nextstate,
} = element
{
assert_eq!(ilabel, 100);
assert_eq!(olabel, 200);
assert_eq!(weight, TropicalWeight::new(2.5));
assert_eq!(nextstate, 300);
} else {
panic!("Expected Arc variant");
}
}
#[test]
fn test_compact_element_weight_variant() {
let element = CompactElement::Weight(TropicalWeight::new(42.0));
if let CompactElement::Weight(weight) = element {
assert_eq!(weight, TropicalWeight::new(42.0));
} else {
panic!("Expected Weight variant");
}
}
#[test]
#[should_panic(expected = "Expected arc element")]
fn test_default_compactor_expand_panic_on_weight() {
let weight_element = CompactElement::Weight(TropicalWeight::new(1.0));
let compactor = DefaultCompactor::<TropicalWeight>::default();
compactor.expand(&weight_element);
}
#[test]
#[should_panic(expected = "Expected weight element")]
fn test_default_compactor_expand_weight_panic_on_arc() {
let arc_element = CompactElement::Arc {
ilabel: 1,
olabel: 2,
weight: TropicalWeight::new(1.0),
nextstate: 3,
};
let compactor = DefaultCompactor::<TropicalWeight>::default();
compactor.expand_weight(&arc_element);
}
#[test]
fn test_compact_state_structure() {
let state = CompactState {
final_weight_idx: Some(42),
arcs_start: 100,
num_arcs: 5,
};
assert_eq!(state.final_weight_idx, Some(42));
assert_eq!(state.arcs_start, 100);
assert_eq!(state.num_arcs, 5);
let state_no_final = CompactState {
final_weight_idx: None,
arcs_start: 0,
num_arcs: 0,
};
assert_eq!(state_no_final.final_weight_idx, None);
assert_eq!(state_no_final.arcs_start, 0);
assert_eq!(state_no_final.num_arcs, 0);
}
#[test]
fn test_compact_arc_iterator_empty() {
let data: Vec<CompactElement<TropicalWeight>> = vec![];
let compactor = DefaultCompactor::<TropicalWeight>::default();
let mut iter: CompactArcIterator<'_, TropicalWeight, DefaultCompactor<TropicalWeight>> =
CompactArcIterator {
data: &data,
compactor: &compactor,
pos: 0,
end: 0,
_phantom: PhantomData,
};
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
iter.reset();
assert_eq!(iter.next(), None);
}
#[test]
fn test_compact_arc_iterator_with_data() {
let arc1 = Arc::new(1, 2, TropicalWeight::new(1.0), 10);
let arc2 = Arc::new(3, 4, TropicalWeight::new(2.0), 20);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let data = vec![compactor.compact(&arc1), compactor.compact(&arc2)];
let mut iter: CompactArcIterator<'_, TropicalWeight, DefaultCompactor<TropicalWeight>> =
CompactArcIterator {
data: &data,
compactor: &compactor,
pos: 0,
end: 2,
_phantom: PhantomData,
};
let first = iter.next().unwrap();
assert_eq!(first.ilabel, arc1.ilabel);
assert_eq!(first.olabel, arc1.olabel);
assert_eq!(first.weight, arc1.weight);
assert_eq!(first.nextstate, arc1.nextstate);
let second = iter.next().unwrap();
assert_eq!(second.ilabel, arc2.ilabel);
assert_eq!(second.olabel, arc2.olabel);
assert_eq!(second.weight, arc2.weight);
assert_eq!(second.nextstate, arc2.nextstate);
assert_eq!(iter.next(), None);
}
#[test]
fn test_compact_arc_iterator_reset() {
let arc = Arc::new(1, 2, TropicalWeight::new(1.0), 10);
let compactor = DefaultCompactor::<TropicalWeight>::default();
let data = vec![compactor.compact(&arc)];
let mut iter: CompactArcIterator<'_, TropicalWeight, DefaultCompactor<TropicalWeight>> =
CompactArcIterator {
data: &data,
compactor: &compactor,
pos: 0,
end: 1,
_phantom: PhantomData,
};
assert!(iter.next().is_some());
assert!(iter.next().is_none());
iter.reset();
assert!(iter.next().is_some());
assert!(iter.next().is_none());
}
#[test]
fn test_compact_arc_iterator_partial_range() {
let arcs = [
Arc::new(1, 1, TropicalWeight::new(1.0), 1),
Arc::new(2, 2, TropicalWeight::new(2.0), 2),
Arc::new(3, 3, TropicalWeight::new(3.0), 3),
Arc::new(4, 4, TropicalWeight::new(4.0), 4),
];
let compactor = DefaultCompactor::<TropicalWeight>::default();
let data: Vec<_> = arcs.iter().map(|arc| compactor.compact(arc)).collect();
let mut iter: CompactArcIterator<'_, TropicalWeight, DefaultCompactor<TropicalWeight>> =
CompactArcIterator {
data: &data,
compactor: &compactor,
pos: 1,
end: 3,
_phantom: PhantomData,
};
let first = iter.next().unwrap();
assert_eq!(first.ilabel, 2);
assert_eq!(first.weight, TropicalWeight::new(2.0));
let second = iter.next().unwrap();
assert_eq!(second.ilabel, 3);
assert_eq!(second.weight, TropicalWeight::new(3.0));
assert!(iter.next().is_none());
}
#[test]
fn test_compact_fst_default_trait() {
let fst1 = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::default();
let fst2 = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
assert_eq!(fst1.num_states(), fst2.num_states());
assert_eq!(fst1.start(), fst2.start());
assert_eq!(fst1.states.len(), fst2.states.len());
assert_eq!(fst1.data.len(), fst2.data.len());
}
#[test]
fn test_compact_fst_memory_efficiency_concept() {
let mut compact_fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let mut vector_fst = VectorFst::<TropicalWeight>::new();
for _ in 0..10 {
compact_fst.add_state();
vector_fst.add_state();
}
compact_fst.set_final_weight(9, Some(TropicalWeight::new(1.0)));
vector_fst.set_final(9, TropicalWeight::new(1.0));
assert_eq!(compact_fst.num_states(), vector_fst.num_states());
assert_eq!(
compact_fst.final_weight(9).copied(),
vector_fst.final_weight(9).copied()
);
}
#[test]
fn test_compact_fst_type_compatibility() {
let _tropical_fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let _log_fst = CompactFst::<LogWeight, DefaultCompactor<LogWeight>>::new();
let _bool_fst = CompactFst::<BooleanWeight, DefaultCompactor<BooleanWeight>>::new();
let _prob_fst = CompactFst::<ProbabilityWeight, DefaultCompactor<ProbabilityWeight>>::new();
}
#[test]
fn test_bit_pack_compactor_creation() {
let ascii_compactor = BitPackCompactor::<TropicalWeight>::new(7, 7, 10);
assert_eq!(ascii_compactor.ilabel_bits, 7);
assert_eq!(ascii_compactor.olabel_bits, 7);
assert_eq!(ascii_compactor.state_bits, 10);
assert_eq!(ascii_compactor.weight_bits, 40);
let phoneme_compactor = BitPackCompactor::<TropicalWeight>::new(8, 8, 12);
assert_eq!(phoneme_compactor.ilabel_bits, 8);
assert_eq!(phoneme_compactor.olabel_bits, 8);
assert_eq!(phoneme_compactor.state_bits, 12);
assert_eq!(phoneme_compactor.weight_bits, 36);
let max_compactor = BitPackCompactor::<TropicalWeight>::new(16, 16, 16);
assert_eq!(max_compactor.weight_bits, 16); }
#[test]
#[should_panic(expected = "Label and state bits must fit in 48 bits")]
fn test_bit_pack_compactor_too_many_bits() {
BitPackCompactor::<TropicalWeight>::new(20, 20, 20); }
#[test]
fn test_quantized_compactor_creation() {
let linear_compactor = QuantizedCompactor::<TropicalWeight>::new(
QuantizationMode::Linear {
min: 0.0,
max: 100.0,
},
256,
);
assert_eq!(linear_compactor.levels, 256);
let log_compactor = QuantizedCompactor::<TropicalWeight>::new(
QuantizationMode::Logarithmic {
min: 0.001,
max: 1000.0,
},
1024,
);
assert_eq!(log_compactor.levels, 1024);
let max_compactor = QuantizedCompactor::<TropicalWeight>::new(
QuantizationMode::Linear {
min: -1.0,
max: 1.0,
},
65_536,
);
assert_eq!(max_compactor.levels, 65_536);
}
#[test]
#[should_panic(expected = "Levels must be between 2 and 65536")]
fn test_quantized_compactor_invalid_levels() {
QuantizedCompactor::<TropicalWeight>::new(
QuantizationMode::Linear { min: 0.0, max: 1.0 },
1,
);
}
#[test]
fn test_delta_compactor_elements() {
let arc = Arc::new(100, 200, TropicalWeight::new(1.5), 300);
let compactor = DeltaCompactor::<TropicalWeight>::default();
let absolute = compactor.compact(&arc);
match &absolute {
DeltaElement::Absolute {
ilabel,
olabel,
weight,
nextstate,
} => {
assert_eq!(*ilabel, 100);
assert_eq!(*olabel, 200);
assert_eq!(*weight, TropicalWeight::new(1.5));
assert_eq!(*nextstate, 300);
}
_ => panic!("Expected Absolute variant"),
}
let expanded = compactor.expand(&absolute);
assert_eq!(expanded.ilabel, arc.ilabel);
assert_eq!(expanded.olabel, arc.olabel);
assert_eq!(expanded.weight, arc.weight);
assert_eq!(expanded.nextstate, arc.nextstate);
let delta = DeltaElement::Delta {
ilabel_delta: 10,
olabel_delta: -5,
weight: TropicalWeight::new(0.5),
nextstate_delta: 1,
};
let delta_expanded = compactor.expand(&delta);
assert_eq!(delta_expanded.ilabel, 10);
assert_eq!(delta_expanded.olabel, 0); assert_eq!(delta_expanded.weight, TropicalWeight::new(0.5));
assert_eq!(delta_expanded.nextstate, 1);
}
#[test]
fn test_varint_encoding() {
assert_eq!(encode_varint(0), vec![0x00]);
assert_eq!(encode_varint(127), vec![0x7F]);
assert_eq!(encode_varint(128), vec![0x80, 0x01]);
assert_eq!(encode_varint(300), vec![0xAC, 0x02]);
assert_eq!(encode_varint(16_384), vec![0x80, 0x80, 0x01]);
for value in [0, 1, 127, 128, 255, 256, 1000, 10_000, 100_000, 1_000_000] {
let encoded = encode_varint(value);
let decoded = decode_varint(&encoded);
assert_eq!(decoded, value, "Round-trip failed for {value}");
}
}
#[test]
fn test_varint_compactor() {
let arc = Arc::new(42, 128, TropicalWeight::new(std::f32::consts::PI), 1000);
let compactor = VarIntCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&arc);
assert_eq!(compressed.encoded_ilabel, encode_varint(42));
assert_eq!(compressed.encoded_olabel, encode_varint(128));
assert_eq!(compressed.weight, TropicalWeight::new(std::f32::consts::PI));
assert_eq!(compressed.encoded_nextstate, encode_varint(1000));
let expanded = compactor.expand(&compressed);
assert_eq!(expanded.ilabel, arc.ilabel);
assert_eq!(expanded.olabel, arc.olabel);
assert_eq!(expanded.weight, arc.weight);
assert_eq!(expanded.nextstate, arc.nextstate);
}
#[test]
fn test_varint_compactor_large_values() {
let large_arc = Arc::new(
u32::MAX,
u32::MAX - 1,
TropicalWeight::new(999.9),
u32::MAX - 2,
);
let compactor = VarIntCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&large_arc);
let expanded = compactor.expand(&compressed);
assert_eq!(expanded.ilabel, large_arc.ilabel);
assert_eq!(expanded.olabel, large_arc.olabel);
assert_eq!(expanded.weight, large_arc.weight);
assert_eq!(expanded.nextstate, large_arc.nextstate);
}
#[test]
fn test_multiple_compactor_types() {
let _default_fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let _delta_fst = CompactFst::<TropicalWeight, DeltaCompactor<TropicalWeight>>::new();
let _varint_fst = CompactFst::<TropicalWeight, VarIntCompactor<TropicalWeight>>::new();
}
#[test]
fn test_quantization_mode_variants() {
let linear_mode = QuantizationMode::Linear {
min: -10.0,
max: 10.0,
};
let log_mode = QuantizationMode::Logarithmic {
min: 0.001,
max: 1000.0,
};
match linear_mode {
QuantizationMode::Linear { min, max } => {
assert_eq!(min, -10.0);
assert_eq!(max, 10.0);
}
_ => panic!("Expected Linear variant"),
}
match log_mode {
QuantizationMode::Logarithmic { min, max } => {
assert_eq!(min, 0.001);
assert_eq!(max, 1000.0);
}
_ => panic!("Expected Logarithmic variant"),
}
}
#[test]
fn test_bitpack_compactor_round_trip() {
let original_arc = Arc::new(100, 200, TropicalWeight::new(5.5), 300);
let compactor = BitPackCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&original_arc);
let expanded = compactor.expand(&compressed);
assert_eq!(expanded.ilabel, 100);
assert_eq!(expanded.olabel, 200);
assert_eq!(expanded.nextstate, 300);
let weight_diff = (expanded.weight.value() - 5.5).abs();
assert!(
weight_diff <= 1.0,
"Weight should be reasonably close after quantization"
);
}
#[test]
fn test_bitpack_compactor_large_values() {
let large_arc = Arc::new(0x1_FFFF, 0x2_FFFF, TropicalWeight::new(99_999.0), 0x3_FFFF);
let compactor = BitPackCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&large_arc);
let expanded = compactor.expand(&compressed);
assert_eq!(expanded.ilabel, 0x1FFFF & 0xFFFF); assert_eq!(expanded.olabel, 0x2FFFF & 0xFFFF);
assert_eq!(expanded.nextstate, 0x3FFFF & 0xFFFF);
}
#[test]
fn test_bitpack_compactor_infinity_weight() {
let inf_arc = Arc::new(1, 2, TropicalWeight::zero(), 3); let compactor = BitPackCompactor::<TropicalWeight>::default();
let compressed = compactor.compact(&inf_arc);
let expanded = compactor.expand(&compressed);
assert!(num_traits::Zero::is_zero(&expanded.weight));
}
#[test]
fn test_quantized_compactor_linear_mode() {
let mode = QuantizationMode::Linear {
min: 0.0,
max: 10.0,
};
let levels = 256u32;
let test_weights = [0.0, 2.5, 5.0, 7.5, 10.0, 15.0];
for &weight_val in &test_weights {
let quantized = QuantizedCompactor::<TropicalWeight>::quantize_weight_value(
weight_val, &mode, levels,
);
let dequantized = QuantizedCompactor::<TropicalWeight>::dequantize_weight_value(
quantized, &mode, levels,
);
if (0.0..=10.0).contains(&weight_val) {
let error = (dequantized - weight_val).abs();
assert!(
error <= 0.1,
"Round-trip error too large: {weight_val} -> {quantized} -> {dequantized}"
);
}
}
}
#[test]
fn test_quantized_compactor_logarithmic_mode() {
let mode = QuantizationMode::Logarithmic {
min: 0.1,
max: 100.0,
};
let levels = 1024u32;
let test_weights = [0.1, 1.0, 10.0, 100.0];
for &weight_val in &test_weights {
let quantized = QuantizedCompactor::<TropicalWeight>::quantize_weight_value(
weight_val, &mode, levels,
);
let dequantized = QuantizedCompactor::<TropicalWeight>::dequantize_weight_value(
quantized, &mode, levels,
);
let relative_error = ((dequantized - weight_val) / weight_val).abs();
assert!(
relative_error <= 0.05,
"Relative error too large: {} -> {} ({}% error)",
weight_val,
dequantized,
relative_error * 100.0
);
}
}
#[test]
fn test_quantized_compactor_infinity_handling() {
let mode = QuantizationMode::Linear {
min: 0.0,
max: 100.0,
};
let levels = 256u32;
let quantized = QuantizedCompactor::<TropicalWeight>::quantize_weight_value(
f64::INFINITY,
&mode,
levels,
);
assert_eq!(quantized, (levels - 1) as u16);
let dequantized =
QuantizedCompactor::<TropicalWeight>::dequantize_weight_value(quantized, &mode, levels);
assert!(dequantized.is_infinite());
}
#[test]
fn test_delta_compactor_small_deltas() {
let base_arc = Arc::new(100, 200, TropicalWeight::new(1.0), 300);
let next_arc = Arc::new(101, 199, TropicalWeight::new(1.5), 302);
let delta = DeltaCompactor::<TropicalWeight>::compute_delta(&next_arc, &base_arc);
match delta {
DeltaElement::Delta {
ilabel_delta,
olabel_delta,
nextstate_delta,
..
} => {
assert_eq!(ilabel_delta, 1); assert_eq!(olabel_delta, -1); assert_eq!(nextstate_delta, 2); }
_ => panic!("Expected Delta variant for small differences"),
}
let applied = DeltaCompactor::<TropicalWeight>::apply_delta(&base_arc, &delta);
assert_eq!(applied.ilabel, next_arc.ilabel);
assert_eq!(applied.olabel, next_arc.olabel);
assert_eq!(applied.nextstate, next_arc.nextstate);
}
#[test]
fn test_delta_compactor_large_deltas() {
let base_arc = Arc::new(100, 200, TropicalWeight::new(1.0), 300);
let far_arc = Arc::new(70_000, 80_000, TropicalWeight::new(2.0), 90_000);
let delta = DeltaCompactor::<TropicalWeight>::compute_delta(&far_arc, &base_arc);
match delta {
DeltaElement::Absolute {
ilabel,
olabel,
nextstate,
..
} => {
assert_eq!(ilabel, 70_000);
assert_eq!(olabel, 80_000);
assert_eq!(nextstate, 90_000);
}
_ => panic!("Expected Absolute variant for large differences"),
}
}
#[test]
fn test_compact_fst_from_vector_fst() {
let mut vector_fst = VectorFst::<TropicalWeight>::new();
let s0 = vector_fst.add_state();
let s1 = vector_fst.add_state();
let s2 = vector_fst.add_state();
vector_fst.set_start(s0);
vector_fst.set_final(s2, TropicalWeight::new(2.0));
vector_fst.add_arc(s0, Arc::new(1, 1, TropicalWeight::new(0.5), s1));
vector_fst.add_arc(s1, Arc::new(2, 2, TropicalWeight::new(1.0), s2));
vector_fst.add_arc(s0, Arc::new(3, 3, TropicalWeight::new(1.5), s2));
let compact_fst =
CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::from_fst(&vector_fst);
assert_eq!(compact_fst.num_states(), vector_fst.num_states());
assert_eq!(compact_fst.start(), vector_fst.start());
assert_eq!(compact_fst.final_weight(s2), vector_fst.final_weight(s2));
assert!(compact_fst.final_weight(s0).is_none());
assert!(compact_fst.final_weight(s1).is_none());
assert_eq!(compact_fst.num_arcs(s0), vector_fst.num_arcs(s0));
assert_eq!(compact_fst.num_arcs(s1), vector_fst.num_arcs(s1));
assert_eq!(compact_fst.num_arcs(s2), vector_fst.num_arcs(s2));
let compact_arcs_s0: Vec<_> = compact_fst.arcs(s0).collect();
let vector_arcs_s0: Vec<_> = vector_fst.arcs(s0).collect();
assert_eq!(compact_arcs_s0.len(), vector_arcs_s0.len());
}
#[test]
fn test_compact_fst_with_compactor() {
let _default_fst = CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::new();
let _bit_packed_fst =
CompactFst::with_compactor(BitPackCompactor::<TropicalWeight>::new(8, 8, 16));
let _quantized_fst = CompactFst::with_compactor(QuantizedCompactor::<TropicalWeight>::new(
QuantizationMode::Linear {
min: 0.0,
max: 100.0,
},
256,
));
}
#[test]
fn test_compression_ratio_concept() {
let mut large_fst = VectorFst::<TropicalWeight>::new();
for _i in 0..100 {
large_fst.add_state();
}
large_fst.set_start(0);
large_fst.set_final(99, TropicalWeight::one());
for i in 0..99 {
large_fst.add_arc(
i,
Arc::new(i % 10, i % 10, TropicalWeight::new((i % 20) as f32), i + 1),
);
}
let default_compact =
CompactFst::<TropicalWeight, DefaultCompactor<TropicalWeight>>::from_fst(&large_fst);
let bitpack_compact =
CompactFst::<TropicalWeight, BitPackCompactor<TropicalWeight>>::from_fst(&large_fst);
assert_eq!(default_compact.num_states(), bitpack_compact.num_states());
assert_eq!(default_compact.start(), bitpack_compact.start());
assert_eq!(default_compact.data.len(), bitpack_compact.data.len());
}
#[test]
fn test_varint_encoding_edge_cases() {
let edge_cases = [0, 1, 127, 128, 255, 256, 16_383, 16_384, u32::MAX];
for &value in &edge_cases {
let encoded = encode_varint(value);
let decoded = decode_varint(&encoded);
assert_eq!(decoded, value, "Varint round-trip failed for {value}");
match value {
0..=127 => assert_eq!(encoded.len(), 1, "Single byte expected for {value}"),
128..=16_383 => assert_eq!(encoded.len(), 2, "Two bytes expected for {value}"),
16_384..=2_097_151 => {
assert_eq!(encoded.len(), 3, "Three bytes expected for {value}")
}
_ => assert!(encoded.len() <= 5, "Max 5 bytes for any u32"),
}
}
}
#[test]
fn test_semiring_compatibility() {
let tropical_arc = Arc::new(1, 2, TropicalWeight::new(std::f32::consts::PI), 4);
let compactor = BitPackCompactor::<TropicalWeight>::default();
let _tropical_compressed = compactor.compact(&tropical_arc);
let weight = TropicalWeight::new(42.0);
let compressed_weight = compactor.compact_weight(&weight);
let expanded_weight = compactor.expand_weight(&compressed_weight);
let weight_diff = (expanded_weight.value() - 42.0).abs();
assert!(
weight_diff <= 1.0,
"Weight round-trip should be reasonably accurate"
);
}
}