use core::fmt::Debug;
use crate::{FiniteCost, GraphError};
pub trait AssignmentCost: FiniteCost {
fn checked_sub(&self, rhs: &Self) -> Option<Self>;
}
macro_rules! integer_assignment_cost {
($($ty:ty),+ $(,)?) => {
$(
impl AssignmentCost for $ty {
fn checked_sub(&self, rhs: &Self) -> Option<Self> {
(*self).checked_sub(*rhs)
}
}
)+
};
}
integer_assignment_cost!(i32, i64, i128, isize);
macro_rules! float_assignment_cost {
($($ty:ty),+ $(,)?) => {
$(
impl AssignmentCost for $ty {
fn checked_sub(&self, rhs: &Self) -> Option<Self> {
let value = *self - *rhs;
value.is_finite().then_some(value)
}
}
)+
};
}
float_assignment_cost!(f32, f64);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CostMatrix<C> {
rows: usize,
columns: usize,
values: Vec<Option<C>>,
}
impl<C> CostMatrix<C> {
pub fn new(rows: usize, columns: usize, values: Vec<C>) -> Result<Self, GraphError> {
let expected = rows.checked_mul(columns).ok_or_else(|| {
GraphError::InvalidAssignment("cost matrix dimensions overflow".to_owned())
})?;
if values.len() != expected {
return Err(GraphError::InvalidAssignment(format!(
"cost matrix has {} values, expected {expected}",
values.len()
)));
}
Ok(Self {
rows,
columns,
values: values.into_iter().map(Some).collect(),
})
}
pub fn from_optional(
rows: usize,
columns: usize,
values: Vec<Option<C>>,
) -> Result<Self, GraphError> {
let expected = rows.checked_mul(columns).ok_or_else(|| {
GraphError::InvalidAssignment("cost matrix dimensions overflow".to_owned())
})?;
if values.len() != expected {
return Err(GraphError::InvalidAssignment(format!(
"cost matrix has {} values, expected {expected}",
values.len()
)));
}
Ok(Self {
rows,
columns,
values,
})
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn columns(&self) -> usize {
self.columns
}
pub fn get(&self, source: usize, target: usize) -> Option<&C> {
if source >= self.rows || target >= self.columns {
return None;
}
self.values
.get(source * self.columns + target)
.and_then(Option::as_ref)
}
pub fn forbid(&mut self, source: usize, target: usize) -> Result<Option<C>, GraphError> {
let index = self.index(source, target)?;
Ok(self.values[index].take())
}
pub fn allow(&mut self, source: usize, target: usize, cost: C) -> Result<(), GraphError> {
let index = self.index(source, target)?;
self.values[index] = Some(cost);
Ok(())
}
pub fn is_forbidden(&self, source: usize, target: usize) -> Result<bool, GraphError> {
let index = self.index(source, target)?;
Ok(self.values[index].is_none())
}
pub(super) fn value(&self, source: usize, target: usize) -> &C {
self.values[source * self.columns + target]
.as_ref()
.expect("algorithm requests only allowed assignment edges")
}
pub(super) fn allowed(&self, source: usize, target: usize) -> bool {
self.values[source * self.columns + target].is_some()
}
pub(super) fn values(&self) -> &[Option<C>] {
&self.values
}
fn index(&self, source: usize, target: usize) -> Result<usize, GraphError> {
if source >= self.rows || target >= self.columns {
return Err(GraphError::InvalidAssignment(format!(
"assignment edge ({source}, {target}) is outside {} by {} matrix",
self.rows, self.columns
)));
}
Ok(source * self.columns + target)
}
}
impl<C> TryFrom<Vec<Vec<C>>> for CostMatrix<C> {
type Error = GraphError;
fn try_from(rows: Vec<Vec<C>>) -> Result<Self, Self::Error> {
let columns = rows.first().map_or(0, Vec::len);
if rows.iter().any(|row| row.len() != columns) {
return Err(GraphError::InvalidAssignment(
"cost matrix rows have different lengths".to_owned(),
));
}
let row_count = rows.len();
Self::new(row_count, columns, rows.into_iter().flatten().collect())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DoublingPolicy<C> {
Forbid,
Allow {
per_source: Vec<C>,
},
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VoiceCrossingPolicy {
Allow,
Forbid,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AssignmentPolicy<C> {
pub insertion_costs: Vec<C>,
pub deletion_costs: Vec<C>,
pub doubling: DoublingPolicy<C>,
pub voice_crossing: VoiceCrossingPolicy,
}
impl<C> AssignmentPolicy<C> {
pub fn new(insertion_costs: Vec<C>, deletion_costs: Vec<C>) -> Self {
Self {
insertion_costs,
deletion_costs,
doubling: DoublingPolicy::Forbid,
voice_crossing: VoiceCrossingPolicy::Allow,
}
}
pub fn with_doubling(mut self, per_source: Vec<C>) -> Self {
self.doubling = DoublingPolicy::Allow { per_source };
self
}
pub fn with_voice_crossing(mut self, policy: VoiceCrossingPolicy) -> Self {
self.voice_crossing = policy;
self
}
pub(super) fn doubling_cost(&self, source: usize) -> Option<&C> {
match &self.doubling {
DoublingPolicy::Forbid => None,
DoublingPolicy::Allow { per_source } => per_source.get(source),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssignmentOperation<C> {
Match {
source: usize,
target: usize,
cost: C,
},
Double {
source: usize,
target: usize,
cost: C,
},
Insert {
target: usize,
cost: C,
},
Delete {
source: usize,
cost: C,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssignmentCertificate<C> {
MinCostFlow {
potentials: Vec<C>,
},
OrderPreserving {
suffix_costs: Vec<Vec<C>>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Assignment<C> {
pub operations: Vec<AssignmentOperation<C>>,
pub total_cost: C,
pub certificate: AssignmentCertificate<C>,
pub receipt: crate::AlgorithmReceipt,
}