Skip to main content

simplex_trace/
num.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::ops::Neg;
4
5pub use crate::rational::Rational;
6
7/// Conversion into a [`Rational`], so the builder API accepts plain integers and fractions.
8pub trait IntoRational {
9    fn into_rational(self) -> Rational;
10}
11
12impl IntoRational for Rational {
13    fn into_rational(self) -> Rational {
14        self
15    }
16}
17
18impl IntoRational for i32 {
19    fn into_rational(self) -> Rational {
20        Rational::from(self)
21    }
22}
23
24impl IntoRational for i64 {
25    fn into_rational(self) -> Rational {
26        Rational::from(self)
27    }
28}
29
30/// `(numerator, denominator)`. Panics if the denominator is zero.
31impl IntoRational for (i64, i64) {
32    fn into_rational(self) -> Rational {
33        ratio(self.0, self.1)
34    }
35}
36
37/// Builds the fraction `n / d`. Panics if `d == 0`.
38pub fn ratio(n: i64, d: i64) -> Rational {
39    Rational::new(n, d)
40}
41
42/// A number of the form `m·M + c`, where `M` is the symbolic "big M" penalty.
43///
44/// Ordering is lexicographic (`M` dominates), which is exactly what the Big-M method needs
45/// without picking a numeric value for `M`. With `m == 0` it is an ordinary rational.
46///
47/// With the `serde` feature it is `{"m": "7", "c": "-4"}` for `7M - 4`.
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct Coef {
51    /// Coefficient of `M`.
52    pub m: Rational,
53    /// Constant part.
54    pub c: Rational,
55}
56
57impl Coef {
58    pub fn zero() -> Self {
59        Coef::constant(Rational::zero())
60    }
61
62    pub fn constant(c: Rational) -> Self {
63        Coef {
64            m: Rational::zero(),
65            c,
66        }
67    }
68
69    pub fn big_m(m: Rational) -> Self {
70        Coef {
71            m,
72            c: Rational::zero(),
73        }
74    }
75
76    pub fn is_zero(&self) -> bool {
77        self.m.is_zero() && self.c.is_zero()
78    }
79
80    pub fn is_negative(&self) -> bool {
81        match self.m.cmp(&Rational::zero()) {
82            Ordering::Less => true,
83            Ordering::Greater => false,
84            Ordering::Equal => self.c.is_negative(),
85        }
86    }
87
88    pub fn is_positive(&self) -> bool {
89        match self.m.cmp(&Rational::zero()) {
90            Ordering::Greater => true,
91            Ordering::Less => false,
92            Ordering::Equal => self.c.is_positive(),
93        }
94    }
95
96    /// `self -= f * r`
97    pub(crate) fn sub_mul(&mut self, f: &Coef, r: &Rational) {
98        self.m -= &f.m * r;
99        self.c -= &f.c * r;
100    }
101}
102
103impl PartialOrd for Coef {
104    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
105        Some(self.cmp(other))
106    }
107}
108
109impl Ord for Coef {
110    fn cmp(&self, other: &Self) -> Ordering {
111        self.m.cmp(&other.m).then_with(|| self.c.cmp(&other.c))
112    }
113}
114
115impl Neg for Coef {
116    type Output = Coef;
117
118    fn neg(self) -> Coef {
119        Coef {
120            m: -self.m,
121            c: -self.c,
122        }
123    }
124}
125
126impl fmt::Display for Coef {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        if self.m.is_zero() {
129            return write!(f, "{}", self.c);
130        }
131        let one = Rational::one();
132        let m = if self.m == one {
133            "M".to_string()
134        } else if self.m == -one {
135            "-M".to_string()
136        } else {
137            format!("{}M", self.m)
138        };
139        if self.c.is_zero() {
140            write!(f, "{m}")
141        } else if self.c.is_negative() {
142            write!(f, "{m} - {}", -self.c.clone())
143        } else {
144            write!(f, "{m} + {}", self.c)
145        }
146    }
147}