use super::msm_position::MsmPosition;
use super::msm_state::MsmState;
use super::MsmConfig;
use smallvec::SmallVec;
const COST_EPSILON: f64 = 1e-9;
#[inline]
pub fn transition_msm_position(
position: &MsmPosition,
query_value: Option<f64>,
target_value: Option<f64>,
config: &MsmConfig,
max_cost: f64,
query_length: usize,
target_length: usize,
) -> SmallVec<[MsmPosition; 4]> {
let mut next_positions = SmallVec::new();
if let (Some(qv), Some(tv)) = (query_value, target_value) {
let move_cost = position.accumulated_cost + (qv - tv).abs();
if move_cost <= max_cost + COST_EPSILON {
let new_pos = MsmPosition::new(
position.query_index + 1,
position.target_index + 1,
move_cost,
qv, tv, );
if new_pos.can_reach_acceptance(query_length, target_length, max_cost, config.c) {
next_positions.push(new_pos);
}
}
}
if let Some(qv) = query_value {
let c_cost = config.c_func(qv, position.last_query_value, position.last_target_value);
let merge_cost = position.accumulated_cost + c_cost;
if merge_cost <= max_cost + COST_EPSILON {
let new_pos = MsmPosition::new(
position.query_index + 1,
position.target_index, merge_cost,
qv, position.last_target_value, );
if new_pos.can_reach_acceptance(query_length, target_length, max_cost, config.c) {
next_positions.push(new_pos);
}
}
}
if let Some(tv) = target_value {
let query_context = query_value.unwrap_or(position.last_query_value);
let c_cost = config.c_func(tv, query_context, position.last_target_value);
let split_cost = position.accumulated_cost + c_cost;
if split_cost <= max_cost + COST_EPSILON {
let new_pos = MsmPosition::new(
position.query_index, position.target_index + 1,
split_cost,
position.last_query_value, tv, );
if new_pos.can_reach_acceptance(query_length, target_length, max_cost, config.c) {
next_positions.push(new_pos);
}
}
}
next_positions
}
pub fn transition_msm_state(
state: &MsmState,
query_value: Option<f64>,
target_value: Option<f64>,
config: &MsmConfig,
max_cost: f64,
query_length: usize,
target_length: usize,
) -> Option<MsmState> {
if state.is_empty() {
return None;
}
if query_value.is_none() && target_value.is_none() {
let final_positions: Vec<_> = state
.iter()
.filter(|p| p.is_final(query_length, target_length))
.cloned()
.collect();
if final_positions.is_empty() {
return None;
}
let mut new_state = MsmState::with_capacity(final_positions.len());
for pos in final_positions {
new_state.insert(pos, max_cost, COST_EPSILON);
}
return Some(new_state);
}
let mut new_state = MsmState::with_capacity(state.len() * 3);
for position in state.iter() {
let next_positions = transition_msm_position(
position,
query_value,
target_value,
config,
max_cost,
query_length,
target_length,
);
for new_pos in next_positions {
new_state.insert(new_pos, max_cost, COST_EPSILON);
}
}
if new_state.is_empty() {
None
} else {
Some(new_state)
}
}
pub fn initial_msm_state(
query: &[f64],
target: &[f64],
config: &MsmConfig,
max_cost: f64,
) -> Option<MsmState> {
if query.is_empty() && target.is_empty() {
let mut state = MsmState::new();
state.insert_unchecked(MsmPosition::new(0, 0, 0.0, 0.0, 0.0));
return Some(state);
}
if query.is_empty() || target.is_empty() {
return None;
}
let initial_pos = MsmPosition::initial(query[0], target[0]);
if !initial_pos.can_reach_acceptance(query.len(), target.len(), max_cost, config.c) {
return None;
}
Some(MsmState::single(initial_pos))
}
pub fn msm_distance_automaton(
query: &[f64],
target: &[f64],
config: &MsmConfig,
max_cost: f64,
) -> Option<f64> {
if query.is_empty() && target.is_empty() {
return Some(0.0);
}
if query.is_empty() || target.is_empty() {
return None;
}
let _ = initial_msm_state(query, target, config, max_cost)?;
let initial_cost = (query[0] - target[0]).abs();
if initial_cost > max_cost + COST_EPSILON {
return None;
}
let mut state = MsmState::single(MsmPosition::new(1, 1, initial_cost, query[0], target[0]));
let m = query.len();
let n = target.len();
let mut first_col = MsmState::single(MsmPosition::new(1, 1, initial_cost, query[0], target[0]));
for i in 2..=m {
let prev_cost = first_col
.iter()
.find(|p| p.query_index == i - 1 && p.target_index == 1)
.map(|p| p.accumulated_cost)
.unwrap_or(f64::INFINITY);
let c_cost = config.c_func(query[i - 1], query[i - 2], target[0]);
let new_cost = prev_cost + c_cost;
if new_cost <= max_cost + COST_EPSILON {
first_col.insert_unchecked(MsmPosition::new(i, 1, new_cost, query[i - 1], target[0]));
}
}
let mut current_row =
MsmState::single(MsmPosition::new(1, 1, initial_cost, query[0], target[0]));
for j in 2..=n {
let prev_cost = current_row
.iter()
.find(|p| p.query_index == 1 && p.target_index == j - 1)
.map(|p| p.accumulated_cost)
.unwrap_or(f64::INFINITY);
let c_cost = config.c_func(target[j - 1], query[0], target[j - 2]);
let new_cost = prev_cost + c_cost;
if new_cost <= max_cost + COST_EPSILON {
current_row.insert_unchecked(MsmPosition::new(1, j, new_cost, query[0], target[j - 1]));
}
}
state.clear();
for pos in first_col.iter() {
state.insert_unchecked(*pos);
}
for pos in current_row.iter() {
if pos.query_index != 1 || pos.target_index != 1 {
state.insert_unchecked(*pos);
}
}
for i in 2..=m {
for j in 2..=n {
let mut best_cost = f64::INFINITY;
let mut best_qv = query[i - 1];
let mut best_tv = target[j - 1];
if let Some(prev) = state
.iter()
.find(|p| p.query_index == i - 1 && p.target_index == j - 1)
{
let move_cost = prev.accumulated_cost + (query[i - 1] - target[j - 1]).abs();
if move_cost < best_cost {
best_cost = move_cost;
best_qv = query[i - 1];
best_tv = target[j - 1];
}
}
if let Some(prev) = state
.iter()
.find(|p| p.query_index == i - 1 && p.target_index == j)
{
let c_cost = config.c_func(query[i - 1], prev.last_query_value, target[j - 1]);
let merge_cost = prev.accumulated_cost + c_cost;
if merge_cost < best_cost {
best_cost = merge_cost;
best_qv = query[i - 1];
best_tv = prev.last_target_value;
}
}
if let Some(prev) = state
.iter()
.find(|p| p.query_index == i && p.target_index == j - 1)
{
let c_cost = config.c_func(target[j - 1], query[i - 1], prev.last_target_value);
let split_cost = prev.accumulated_cost + c_cost;
if split_cost < best_cost {
best_cost = split_cost;
best_qv = prev.last_query_value;
best_tv = target[j - 1];
}
}
if best_cost <= max_cost + COST_EPSILON {
state.insert_unchecked(MsmPosition::new(i, j, best_cost, best_qv, best_tv));
}
}
}
state.min_final_distance(m, n)
}
pub fn msm_distance_wavefront(
query: &[f64],
target: &[f64],
config: &MsmConfig,
max_cost: f64,
) -> Option<f64> {
let m = query.len();
let n = target.len();
if m == 0 && n == 0 {
return Some(0.0);
}
if m == 0 || n == 0 {
return None;
}
let mut cost = vec![vec![f64::INFINITY; n + 1]; m + 1];
cost[1][1] = (query[0] - target[0]).abs();
if cost[1][1] > max_cost + COST_EPSILON {
return None;
}
for i in 2..=m {
let c_cost = config.c_func(query[i - 1], query[i - 2], target[0]);
cost[i][1] = cost[i - 1][1] + c_cost;
}
for j in 2..=n {
let c_cost = config.c_func(target[j - 1], query[0], target[j - 2]);
cost[1][j] = cost[1][j - 1] + c_cost;
}
let mut has_valid = true;
for i in 2..=m {
let mut row_has_valid = false;
for j in 2..=n {
let move_cost = cost[i - 1][j - 1] + (query[i - 1] - target[j - 1]).abs();
let merge_cost =
cost[i - 1][j] + config.c_func(query[i - 1], query[i - 2], target[j - 1]);
let split_cost =
cost[i][j - 1] + config.c_func(target[j - 1], query[i - 1], target[j - 2]);
cost[i][j] = move_cost.min(merge_cost).min(split_cost);
if cost[i][j] <= max_cost + COST_EPSILON {
row_has_valid = true;
}
}
if !row_has_valid {
has_valid = false;
break;
}
}
if has_valid && cost[m][n] <= max_cost + COST_EPSILON {
Some(cost[m][n])
} else if cost[m][n].is_finite() {
Some(cost[m][n])
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-9;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn test_transition_msm_position_move() {
let config = MsmConfig::new(1.0);
let pos = MsmPosition::new(0, 0, 0.0, 1.0, 2.0);
let next = transition_msm_position(&pos, Some(1.5), Some(2.5), &config, 10.0, 3, 3);
assert!(next.len() >= 1);
let move_pos = next
.iter()
.find(|p| p.query_index == 1 && p.target_index == 1);
assert!(move_pos.is_some());
let move_pos = move_pos.expect("expected Some move_pos in test");
assert!(approx_eq(move_pos.accumulated_cost, 1.0)); }
#[test]
fn test_transition_msm_position_merge() {
let config = MsmConfig::new(1.0);
let pos = MsmPosition::new(0, 1, 0.0, 1.0, 2.0);
let next = transition_msm_position(&pos, Some(1.5), None, &config, 10.0, 3, 1);
let merge_pos = next
.iter()
.find(|p| p.query_index == 1 && p.target_index == 1);
assert!(merge_pos.is_some());
}
#[test]
fn test_transition_msm_position_split() {
let config = MsmConfig::new(1.0);
let pos = MsmPosition::new(1, 0, 0.0, 1.0, 2.0);
let next = transition_msm_position(&pos, None, Some(2.5), &config, 10.0, 1, 3);
let split_pos = next
.iter()
.find(|p| p.query_index == 1 && p.target_index == 1);
assert!(split_pos.is_some());
}
#[test]
fn test_transition_msm_state() {
let config = MsmConfig::new(1.0);
let state = MsmState::single(MsmPosition::new(0, 0, 0.0, 1.0, 2.0));
let next_state = transition_msm_state(&state, Some(1.5), Some(2.5), &config, 10.0, 3, 3);
assert!(next_state.is_some());
let next_state = next_state.expect("expected Some next_state in test");
assert!(!next_state.is_empty());
}
#[test]
fn test_initial_msm_state() {
let query = vec![1.0, 2.0, 3.0];
let target = vec![1.0, 2.0, 3.0];
let config = MsmConfig::new(1.0);
let state = initial_msm_state(&query, &target, &config, 10.0);
assert!(state.is_some());
assert_eq!(state.expect("expected Some state in test").len(), 1);
}
#[test]
fn test_initial_msm_state_empty() {
let config = MsmConfig::new(1.0);
let state = initial_msm_state(&[], &[], &config, 10.0);
assert!(state.is_some());
let state = initial_msm_state(&[1.0], &[], &config, 10.0);
assert!(state.is_none());
}
#[test]
fn test_msm_distance_automaton_identical() {
let config = MsmConfig::new(1.0);
let series = vec![1.0, 2.0, 3.0];
let dist = msm_distance_automaton(&series, &series, &config, f64::INFINITY);
assert!(dist.is_some());
assert!(approx_eq(dist.expect("expected Some dist in test"), 0.0));
}
#[test]
fn test_msm_distance_automaton_single_move() {
let config = MsmConfig::new(1.0);
let x = vec![1.0];
let y = vec![2.0];
let dist = msm_distance_automaton(&x, &y, &config, f64::INFINITY);
assert!(dist.is_some());
assert!(approx_eq(dist.expect("expected Some dist in test"), 1.0)); }
#[test]
fn test_msm_distance_automaton_shift() {
let config = MsmConfig::new(1.0);
let x = vec![1.0, 2.0, 3.0];
let y = vec![2.0, 3.0, 4.0];
let dist = msm_distance_automaton(&x, &y, &config, f64::INFINITY);
assert!(dist.is_some());
assert!(approx_eq(dist.expect("expected Some dist in test"), 3.0));
}
#[test]
fn test_msm_distance_wavefront_identical() {
let config = MsmConfig::new(1.0);
let series = vec![1.0, 2.0, 3.0];
let dist = msm_distance_wavefront(&series, &series, &config, f64::INFINITY);
assert!(dist.is_some());
assert!(approx_eq(dist.expect("expected Some dist in test"), 0.0));
}
#[test]
fn test_msm_distance_wavefront_matches_dp() {
let config = MsmConfig::new(1.0);
let x = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let y = vec![1.0, 3.0, 2.0];
let dist_dp = config.distance(&x, &y);
let dist_wavefront = msm_distance_wavefront(&x, &y, &config, f64::INFINITY);
assert!(dist_wavefront.is_some());
assert!(
approx_eq(
dist_dp,
dist_wavefront.expect("expected Some dist_wavefront in test")
),
"DP: {}, Wavefront: {}",
dist_dp,
dist_wavefront.expect("expected Some dist_wavefront in test")
);
}
#[test]
fn test_msm_distance_automaton_matches_dp() {
let config = MsmConfig::new(1.0);
let x = vec![1.0, 2.0, 3.0, 2.0, 1.0];
let y = vec![1.0, 3.0, 2.0];
let dist_dp = config.distance(&x, &y);
let dist_auto = msm_distance_automaton(&x, &y, &config, f64::INFINITY);
assert!(dist_auto.is_some());
assert!(
approx_eq(dist_dp, dist_auto.expect("expected Some dist_auto in test")),
"DP: {}, Automaton: {}",
dist_dp,
dist_auto.expect("expected Some dist_auto in test")
);
}
#[test]
fn test_msm_with_threshold() {
let config = MsmConfig::new(1.0);
let x = vec![1.0, 2.0, 3.0];
let y = vec![5.0, 6.0, 7.0];
let dist = msm_distance_wavefront(&x, &y, &config, 15.0);
assert!(dist.is_some());
let dist = msm_distance_wavefront(&x, &y, &config, 5.0);
if let Some(d) = dist {
assert!(d > 5.0);
}
}
#[test]
fn test_c_function_in_transitions() {
let config = MsmConfig::new(1.0);
let cost1 = config.c_func(2.0, 1.0, 3.0); assert!(approx_eq(cost1, 1.0));
let cost2 = config.c_func(5.0, 1.0, 3.0); assert!(approx_eq(cost2, 1.0 + 2.0)); }
}