Skip to main content

sim_lib_discrete_graph/
cost.rs

1//! Finite additive costs shared by graph dynamic programs.
2
3use core::{cmp::Ordering, fmt::Debug};
4
5use crate::GraphError;
6
7/// A finite, partially ordered cost with checked addition.
8///
9/// Floating-point implementations reject infinities and NaNs. Algorithms call
10/// [`FiniteCost::compare`] rather than relying on an ambient total-order wrapper,
11/// so non-finite values fail closed at the public boundary.
12pub trait FiniteCost: Clone + Debug + PartialEq + PartialOrd {
13    /// Additive identity.
14    fn zero() -> Self;
15
16    /// Exact or finite checked addition.
17    fn checked_add(&self, rhs: &Self) -> Option<Self>;
18
19    /// Whether this value is a valid finite algorithm input.
20    fn is_finite(&self) -> bool {
21        true
22    }
23
24    /// Compares two valid finite costs.
25    fn compare(&self, rhs: &Self) -> Option<Ordering> {
26        self.partial_cmp(rhs)
27    }
28}
29
30macro_rules! integer_finite_cost {
31    ($($ty:ty),+ $(,)?) => {
32        $(
33            impl FiniteCost for $ty {
34                fn zero() -> Self {
35                    0
36                }
37
38                fn checked_add(&self, rhs: &Self) -> Option<Self> {
39                    (*self).checked_add(*rhs)
40                }
41            }
42        )+
43    };
44}
45
46integer_finite_cost!(
47    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
48);
49
50macro_rules! float_finite_cost {
51    ($($ty:ty),+ $(,)?) => {
52        $(
53            impl FiniteCost for $ty {
54                fn zero() -> Self {
55                    0.0
56                }
57
58                fn checked_add(&self, rhs: &Self) -> Option<Self> {
59                    let value = *self + *rhs;
60                    value.is_finite().then_some(value)
61                }
62
63                fn is_finite(&self) -> bool {
64                    <$ty>::is_finite(*self)
65                }
66
67                fn compare(&self, rhs: &Self) -> Option<Ordering> {
68                    (self.is_finite() && rhs.is_finite()).then(|| self.total_cmp(rhs))
69                }
70            }
71        )+
72    };
73}
74
75float_finite_cost!(f32, f64);
76
77pub(crate) fn add<C: FiniteCost>(left: &C, right: &C, context: &str) -> Result<C, GraphError> {
78    left.checked_add(right)
79        .filter(FiniteCost::is_finite)
80        .ok_or_else(|| GraphError::WeightOverflow(context.to_owned()))
81}
82
83pub(crate) fn compare<C: FiniteCost>(
84    left: &C,
85    right: &C,
86    context: &str,
87) -> Result<Ordering, GraphError> {
88    left.compare(right)
89        .ok_or_else(|| GraphError::NonFiniteCost(context.to_owned()))
90}
91
92pub(crate) fn validate<C: FiniteCost>(cost: &C, context: &str) -> Result<(), GraphError> {
93    if cost.is_finite() {
94        Ok(())
95    } else {
96        Err(GraphError::NonFiniteCost(context.to_owned()))
97    }
98}