use std::cmp::Ordering;
use std::fmt;
#[derive(Clone, Copy)]
pub struct MsmPosition {
pub query_index: usize,
pub target_index: usize,
pub accumulated_cost: f64,
pub last_query_value: f64,
pub last_target_value: f64,
pub is_special: bool,
}
impl MsmPosition {
#[inline]
pub fn new(
query_index: usize,
target_index: usize,
accumulated_cost: f64,
last_query_value: f64,
last_target_value: f64,
) -> Self {
Self {
query_index,
target_index,
accumulated_cost,
last_query_value,
last_target_value,
is_special: false,
}
}
#[inline]
pub fn initial(first_query_value: f64, first_target_value: f64) -> Self {
Self {
query_index: 0,
target_index: 0,
accumulated_cost: 0.0,
last_query_value: first_query_value,
last_target_value: first_target_value,
is_special: false,
}
}
#[inline]
pub fn with_special(
query_index: usize,
target_index: usize,
accumulated_cost: f64,
last_query_value: f64,
last_target_value: f64,
is_special: bool,
) -> Self {
Self {
query_index,
target_index,
accumulated_cost,
last_query_value,
last_target_value,
is_special,
}
}
#[inline]
pub fn can_reach_acceptance(
&self,
query_length: usize,
target_length: usize,
max_cost: f64,
c_const: f64,
) -> bool {
let remaining_query = query_length.saturating_sub(self.query_index);
let remaining_target = target_length.saturating_sub(self.target_index);
let length_diff = remaining_query.abs_diff(remaining_target);
let min_remaining_cost = length_diff as f64 * c_const;
self.accumulated_cost + min_remaining_cost <= max_cost + 1e-9
}
#[inline]
pub fn is_final(&self, query_length: usize, target_length: usize) -> bool {
self.query_index >= query_length && self.target_index >= target_length
}
#[inline]
pub fn diagonal_distance(&self) -> i64 {
self.query_index as i64 - self.target_index as i64
}
}
impl fmt::Debug for MsmPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MsmPos({},{},cost={:.3},qv={:.2},tv={:.2}{})",
self.query_index,
self.target_index,
self.accumulated_cost,
self.last_query_value,
self.last_target_value,
if self.is_special { ",special" } else { "" }
)
}
}
impl fmt::Display for MsmPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({},{})→{:.3}",
self.query_index, self.target_index, self.accumulated_cost
)
}
}
impl PartialEq for MsmPosition {
fn eq(&self, other: &Self) -> bool {
self.query_index == other.query_index
&& self.target_index == other.target_index
&& self.is_special == other.is_special
&& (self.accumulated_cost - other.accumulated_cost).abs() < 1e-9
&& (self.last_query_value - other.last_query_value).abs() < 1e-9
&& (self.last_target_value - other.last_target_value).abs() < 1e-9
}
}
impl Eq for MsmPosition {}
impl PartialOrd for MsmPosition {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MsmPosition {
fn cmp(&self, other: &Self) -> Ordering {
match self
.accumulated_cost
.partial_cmp(&other.accumulated_cost)
.unwrap_or(Ordering::Equal)
{
Ordering::Equal => {}
ord => return ord,
}
match (self.query_index + self.target_index)
.cmp(&(other.query_index + other.target_index))
.reverse()
{
Ordering::Equal => {}
ord => return ord,
}
match self.is_special.cmp(&other.is_special) {
Ordering::Equal => {}
ord => return ord,
}
(self.query_index, self.target_index).cmp(&(other.query_index, other.target_index))
}
}
#[inline]
pub fn msm_subsumes(a: &MsmPosition, b: &MsmPosition, epsilon: f64) -> bool {
if a.query_index != b.query_index
|| a.target_index != b.target_index
|| a.is_special != b.is_special
{
return false;
}
if a.accumulated_cost > b.accumulated_cost + epsilon {
return false;
}
let query_value_close = (a.last_query_value - b.last_query_value).abs() < epsilon;
let target_value_close = (a.last_target_value - b.last_target_value).abs() < epsilon;
query_value_close && target_value_close
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-9;
#[test]
fn test_position_creation() {
let pos = MsmPosition::new(2, 3, 1.5, 2.0, 3.0);
assert_eq!(pos.query_index, 2);
assert_eq!(pos.target_index, 3);
assert!((pos.accumulated_cost - 1.5).abs() < EPSILON);
assert!((pos.last_query_value - 2.0).abs() < EPSILON);
assert!((pos.last_target_value - 3.0).abs() < EPSILON);
assert!(!pos.is_special);
}
#[test]
fn test_initial_position() {
let pos = MsmPosition::initial(1.0, 2.0);
assert_eq!(pos.query_index, 0);
assert_eq!(pos.target_index, 0);
assert!((pos.accumulated_cost - 0.0).abs() < EPSILON);
assert!((pos.last_query_value - 1.0).abs() < EPSILON);
assert!((pos.last_target_value - 2.0).abs() < EPSILON);
}
#[test]
fn test_is_final() {
let pos = MsmPosition::new(3, 4, 2.0, 1.0, 2.0);
assert!(pos.is_final(3, 4));
assert!(pos.is_final(2, 3)); assert!(!pos.is_final(4, 4));
assert!(!pos.is_final(3, 5));
}
#[test]
fn test_can_reach_acceptance() {
let pos = MsmPosition::new(2, 2, 1.0, 1.0, 2.0);
assert!(pos.can_reach_acceptance(2, 2, 2.0, 1.0));
assert!(pos.can_reach_acceptance(3, 2, 2.0, 1.0));
assert!(!pos.can_reach_acceptance(4, 2, 2.0, 1.0));
}
#[test]
fn test_diagonal_distance() {
let pos1 = MsmPosition::new(3, 3, 0.0, 0.0, 0.0);
assert_eq!(pos1.diagonal_distance(), 0);
let pos2 = MsmPosition::new(5, 3, 0.0, 0.0, 0.0);
assert_eq!(pos2.diagonal_distance(), 2);
let pos3 = MsmPosition::new(2, 5, 0.0, 0.0, 0.0);
assert_eq!(pos3.diagonal_distance(), -3);
}
#[test]
fn test_subsumption() {
let a = MsmPosition::new(2, 2, 1.0, 3.0, 4.0);
let b = MsmPosition::new(2, 2, 2.0, 3.0, 4.0);
assert!(msm_subsumes(&a, &b, EPSILON));
assert!(!msm_subsumes(&b, &a, EPSILON));
let c = MsmPosition::new(2, 2, 1.0, 3.0, 4.0);
assert!(msm_subsumes(&a, &c, EPSILON));
assert!(msm_subsumes(&c, &a, EPSILON));
let d = MsmPosition::new(3, 2, 0.5, 3.0, 4.0);
assert!(!msm_subsumes(&a, &d, EPSILON));
assert!(!msm_subsumes(&d, &a, EPSILON));
let e = MsmPosition::new(2, 2, 1.0, 5.0, 4.0);
assert!(!msm_subsumes(&a, &e, EPSILON));
}
#[test]
fn test_ordering() {
let pos1 = MsmPosition::new(2, 2, 1.0, 0.0, 0.0);
let pos2 = MsmPosition::new(2, 2, 2.0, 0.0, 0.0);
let pos3 = MsmPosition::new(3, 3, 1.0, 0.0, 0.0);
assert!(pos1 < pos2);
assert!(pos3 < pos1);
}
#[test]
fn test_debug_format() {
let pos = MsmPosition::with_special(2, 3, 1.5, 2.0, 3.0, true);
let debug = format!("{:?}", pos);
assert!(debug.contains("MsmPos"));
assert!(debug.contains("2,3"));
assert!(debug.contains("1.5"));
assert!(debug.contains("special"));
}
#[test]
fn test_display_format() {
let pos = MsmPosition::new(2, 3, 1.5, 0.0, 0.0);
let display = format!("{}", pos);
assert_eq!(display, "(2,3)→1.500");
}
}