use super::algorithm::Algorithm;
use super::position_f64::PositionF64;
use smallvec::SmallVec;
use std::collections::BTreeSet;
const EPSILON: f64 = 1e-9;
#[derive(Debug, Clone, PartialEq)]
pub struct StateF64 {
positions: SmallVec<[PositionF64; 8]>,
}
impl StateF64 {
pub fn new() -> Self {
Self {
positions: SmallVec::new(),
}
}
pub fn single(position: PositionF64) -> Self {
let mut positions = SmallVec::new();
positions.push(position);
Self { positions }
}
pub fn initial() -> Self {
Self::single(PositionF64::initial())
}
pub fn from_positions(mut positions: Vec<PositionF64>) -> Self {
positions.sort();
positions.dedup_by(|a, b| a.approx_eq(b));
Self {
positions: SmallVec::from_vec(positions),
}
}
pub fn insert(&mut self, position: PositionF64, algorithm: Algorithm, query_length: usize) {
for existing in &self.positions {
if existing.subsumes(&position, algorithm, query_length) {
return; }
}
self.positions
.retain(|p| !position.subsumes(p, algorithm, query_length));
let insert_pos = self
.positions
.binary_search(&position)
.unwrap_or_else(|pos| pos);
self.positions.insert(insert_pos, position);
}
pub fn merge(&mut self, other: &StateF64, algorithm: Algorithm, query_length: usize) {
for position in &other.positions {
self.insert(*position, algorithm, query_length);
}
}
pub fn head(&self) -> Option<&PositionF64> {
self.positions.first()
}
#[inline(always)]
pub fn positions(&self) -> &[PositionF64] {
&self.positions
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.positions.is_empty()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.positions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &PositionF64> {
self.positions.iter()
}
#[inline]
pub fn clear(&mut self) {
self.positions.clear();
}
#[inline]
pub fn copy_from(&mut self, other: &StateF64) {
self.positions.clear();
self.positions.reserve(other.positions.len());
for pos in &other.positions {
self.positions.push(*pos);
}
}
#[inline]
pub fn min_distance(&self) -> Option<f64> {
self.positions.first().map(|first| {
if self.positions.len() == 1 {
return first.accumulated_cost;
}
self.positions
.iter()
.map(|p| p.accumulated_cost)
.fold(f64::INFINITY, f64::min)
})
}
#[inline]
pub fn infer_distance(&self, query_length: usize) -> Option<f64> {
if self.positions.len() == 1 {
let p = &self.positions[0];
if p.is_special {
return None;
}
let remaining = query_length.saturating_sub(p.term_index) as f64;
return Some(p.accumulated_cost + remaining);
}
self.positions
.iter()
.filter(|p| !p.is_special)
.map(|p| {
let remaining = query_length.saturating_sub(p.term_index) as f64;
p.accumulated_cost + remaining
})
.fold(None, |acc, dist| match acc {
None => Some(dist),
Some(min) => Some(min.min(dist)),
})
}
#[inline]
pub fn infer_prefix_distance(&self, query_length: usize) -> Option<f64> {
if self.positions.len() == 1 {
let p = &self.positions[0];
return if p.term_index >= query_length {
Some(p.accumulated_cost)
} else {
None
};
}
self.positions
.iter()
.filter(|p| p.term_index >= query_length)
.map(|p| p.accumulated_cost)
.fold(None, |acc, cost| match acc {
None => Some(cost),
Some(min) => Some(min.min(cost)),
})
}
#[inline]
pub fn all_exceed_threshold(&self, threshold: f64) -> bool {
self.positions
.iter()
.all(|p| p.accumulated_cost > threshold + EPSILON)
}
}
impl Default for StateF64 {
fn default() -> Self {
Self::new()
}
}
impl FromIterator<PositionF64> for StateF64 {
fn from_iter<T: IntoIterator<Item = PositionF64>>(iter: T) -> Self {
let positions: BTreeSet<PositionF64> = iter.into_iter().collect();
Self::from_positions(positions.into_iter().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_EPSILON: f64 = 1e-10;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < TEST_EPSILON
}
#[test]
fn test_state_creation() {
let state = StateF64::new();
assert!(state.is_empty());
assert_eq!(state.len(), 0);
}
#[test]
fn test_state_single() {
let pos = PositionF64::new(3, 1.5);
let state = StateF64::single(pos);
assert_eq!(state.len(), 1);
assert!(state
.head()
.expect("test fixture: head on non-empty state")
.approx_eq(&pos));
}
#[test]
fn test_state_initial() {
let state = StateF64::initial();
assert_eq!(state.len(), 1);
let head = state.head().expect("test fixture: head on non-empty state");
assert_eq!(head.term_index, 0);
assert!(approx_eq(head.accumulated_cost, 0.0));
}
#[test]
fn test_insert_maintains_order() {
let mut state = StateF64::new();
let query_length = 10;
state.insert(PositionF64::new(3, 2.0), Algorithm::Standard, query_length);
state.insert(PositionF64::new(1, 1.0), Algorithm::Standard, query_length);
state.insert(PositionF64::new(2, 1.5), Algorithm::Standard, query_length);
let positions: Vec<_> = state.positions().to_vec();
assert_eq!(positions[0].term_index, 1);
assert_eq!(positions[1].term_index, 2);
assert_eq!(positions[2].term_index, 3);
}
#[test]
fn test_subsumption_removes_positions() {
let mut state = StateF64::new();
let query_length = 10;
state.insert(PositionF64::new(5, 3.0), Algorithm::Standard, query_length);
assert_eq!(state.len(), 1);
state.insert(PositionF64::new(5, 2.0), Algorithm::Standard, query_length);
assert_eq!(state.len(), 1);
let pos = state.head().expect("test fixture: head on non-empty state");
assert_eq!(pos.term_index, 5);
assert!(approx_eq(pos.accumulated_cost, 2.0));
}
#[test]
fn test_position_subsumed_on_insert() {
let mut state = StateF64::new();
let query_length = 10;
state.insert(PositionF64::new(5, 2.0), Algorithm::Standard, query_length);
state.insert(PositionF64::new(5, 3.0), Algorithm::Standard, query_length);
assert_eq!(state.len(), 1);
assert!(approx_eq(
state
.head()
.expect("test fixture: head on non-empty state")
.accumulated_cost,
2.0
));
}
#[test]
fn test_min_distance() {
let mut state = StateF64::new();
let query_length = 10;
state.insert(PositionF64::new(3, 2.5), Algorithm::Standard, query_length);
state.insert(PositionF64::new(4, 1.5), Algorithm::Standard, query_length);
state.insert(PositionF64::new(5, 3.0), Algorithm::Standard, query_length);
assert!(approx_eq(
state
.min_distance()
.expect("test fixture: min_distance on non-empty state"),
1.5
));
}
#[test]
fn test_infer_distance() {
let mut state = StateF64::new();
let query_length = 7;
state.insert(PositionF64::new(3, 1.0), Algorithm::Standard, query_length);
state.insert(PositionF64::new(5, 2.0), Algorithm::Standard, query_length);
let dist = state
.infer_distance(query_length)
.expect("test fixture: infer_distance on non-empty state");
assert!(approx_eq(dist, 4.0));
}
#[test]
fn test_infer_prefix_distance() {
let mut state = StateF64::new();
let query_length = 5;
state.insert(PositionF64::new(5, 1.5), Algorithm::Standard, query_length);
state.insert(PositionF64::new(3, 0.5), Algorithm::Standard, query_length);
let dist = state
.infer_prefix_distance(query_length)
.expect("test fixture: infer_prefix_distance on qualifying state");
assert!(approx_eq(dist, 1.5));
}
#[test]
fn test_all_exceed_threshold() {
let mut state = StateF64::new();
let query_length = 10;
state.insert(PositionF64::new(0, 2.5), Algorithm::Standard, query_length);
state.insert(PositionF64::new(1, 3.0), Algorithm::Standard, query_length);
assert!(state.all_exceed_threshold(2.4));
assert!(!state.all_exceed_threshold(2.5));
assert!(!state.all_exceed_threshold(3.0));
}
#[test]
fn test_merge() {
let mut state1 = StateF64::single(PositionF64::new(0, 0.0));
let state2 = StateF64::single(PositionF64::new(3, 0.5));
state1.merge(&state2, Algorithm::Standard, 10);
assert_eq!(state1.len(), 2);
}
#[test]
fn test_clear_and_copy() {
let mut state1 = StateF64::single(PositionF64::new(0, 0.0));
let state2 = StateF64::single(PositionF64::new(5, 2.5));
state1.copy_from(&state2);
assert_eq!(state1.len(), 1);
assert_eq!(
state1
.head()
.expect("test fixture: head on non-empty state")
.term_index,
5
);
}
}