use crate::traits::{Finite, Number, TotalOrd};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LAPError {
#[error("The matrix value type is non-fractional and is not supported by LAP algorithms.")]
NonFractionalValueTypeUnsupported,
#[error("The matrix is not square.")]
NonSquareMatrix,
#[error("The matrix is empty.")]
EmptyMatrix,
#[error("The matrix contains zero values.")]
ZeroValues,
#[error("The matrix contains negative values.")]
NegativeValues,
#[error("The matrix contains non-finite values.")]
NonFiniteValues,
#[error("The matrix contains a value larger than the maximum cost.")]
ValueTooLarge,
#[error("The provided maximal cost is not a finite number.")]
MaximalCostNotFinite,
#[error("The provided maximal cost is not a positive number.")]
MaximalCostNotPositive,
#[error("The provided padding value is not a finite number.")]
PaddingValueNotFinite,
#[error("The provided padding value is not a positive number.")]
PaddingValueNotPositive,
#[error(
"The padding cost is too small: padding_cost / 2 must be strictly greater than the maximum sparse value."
)]
PaddingCostTooSmall,
#[error("Failed to build the expanded sparse matrix from the provided sparse structure.")]
ExpandedMatrixBuildFailed,
#[error("Internal index conversion failed while processing the sparse wrapper.")]
IndexConversionFailed,
#[error("The sparse structure has no perfect matching (infeasible assignment).")]
InfeasibleAssignment,
}
pub(crate) fn validate_sparse_wrapper_costs<V>(padding_cost: V, max_cost: V) -> Result<(), LAPError>
where
V: Number + Finite,
{
if !padding_cost.is_finite() {
return Err(LAPError::PaddingValueNotFinite);
}
if padding_cost <= V::zero() {
return Err(LAPError::PaddingValueNotPositive);
}
if !max_cost.is_finite() {
return Err(LAPError::MaximalCostNotFinite);
}
if max_cost <= V::zero() {
return Err(LAPError::MaximalCostNotPositive);
}
if padding_cost >= max_cost {
return Err(LAPError::ValueTooLarge);
}
Ok(())
}
pub(crate) fn validate_max_cost<V>(max_cost: V) -> Result<(), LAPError>
where
V: Number + Finite,
{
if !max_cost.is_finite() {
return Err(LAPError::MaximalCostNotFinite);
}
if max_cost <= V::zero() {
return Err(LAPError::MaximalCostNotPositive);
}
Ok(())
}
pub(crate) fn validate_fractional_value_domain<V>() -> Result<(), LAPError>
where
V: Number + Finite,
{
let one = V::one();
let two = one + one;
if two == V::zero() || one / two == V::zero() {
return Err(LAPError::NonFractionalValueTypeUnsupported);
}
Ok(())
}
pub(crate) fn validate_lap_entry_costs<V>(max_cost: V) -> Result<(), LAPError>
where
V: Number + Finite,
{
validate_fractional_value_domain::<V>()?;
validate_max_cost(max_cost)
}
pub(crate) fn validate_sparse_lap_entry_costs<V>(
padding_cost: V,
max_cost: V,
) -> Result<(), LAPError>
where
V: Number + Finite,
{
validate_fractional_value_domain::<V>()?;
validate_sparse_wrapper_costs(padding_cost, max_cost)
}
macro_rules! sparse_padded_lap_impl {
($self:expr, $padding_cost:expr, $max_cost:expr, $solve_method:ident) => {{
validate_sparse_lap_entry_costs($padding_cost, $max_cost)?;
if $self.is_empty() {
return Ok(vec![]);
}
if $self.max_sparse_value().is_some_and(|value| value >= $padding_cost) {
return Err(LAPError::PaddingCostTooSmall);
}
let padding =
PaddedMatrix2D::new($self, |_| $padding_cost).map_err(|_| LAPError::NonSquareMatrix)?;
let assignment = padding.$solve_method($max_cost)?;
Ok(assignment
.into_iter()
.filter(|&(row_index, column_index)| !padding.is_imputed((row_index, column_index)))
.collect())
}};
}
pub(crate) use sparse_padded_lap_impl;
pub(crate) fn validate_lap_value_against_max<V>(value: V, max_cost: V) -> Result<(), LAPError>
where
V: Number + Finite + TotalOrd,
{
if !value.is_finite() {
return Err(LAPError::NonFiniteValues);
}
if value == V::zero() {
return Err(LAPError::ZeroValues);
}
if value < V::zero() {
return Err(LAPError::NegativeValues);
}
if value >= max_cost {
return Err(LAPError::ValueTooLarge);
}
Ok(())
}