1use crate::{
2 macros::{impl_add_inverse, impl_mul_inverse},
3 parse::{Parse, ParseError, RawParseError},
4 v1, ATol, VariableID,
5};
6use approx::AbsDiffEq;
7use num::Zero;
8use proptest::prelude::*;
9use std::{collections::BTreeMap, ops::*};
10use thiserror::Error;
11
12#[non_exhaustive]
13#[derive(Debug, Error)]
14pub enum BoundError {
15 #[error("lower({lower}) or upper({upper}) never be NAN")]
16 NotANumber { lower: f64, upper: f64 },
17 #[error("lower({lower}) = +inf or upper({upper}) = -inf is not allowed")]
18 InvalidInfinity { lower: f64, upper: f64 },
19 #[error("Lower is larger than Upper: lower({lower}) > upper({upper})")]
20 UpperSmallerThanLower { lower: f64, upper: f64 },
21}
22
23impl BoundError {
24 fn check(lower: f64, upper: f64) -> Result<(), BoundError> {
25 if lower.is_nan() || upper.is_nan() {
26 return Err(BoundError::NotANumber { lower, upper });
27 }
28 if lower == f64::INFINITY || upper == f64::NEG_INFINITY {
29 return Err(BoundError::InvalidInfinity { lower, upper });
30 }
31 if lower > upper {
32 return Err(BoundError::UpperSmallerThanLower { lower, upper });
33 }
34 Ok(())
35 }
36}
37
38impl From<BoundError> for ParseError {
39 fn from(e: BoundError) -> Self {
40 RawParseError::from(e).into()
41 }
42}
43
44pub type Bounds = BTreeMap<VariableID, Bound>;
49
50#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
72pub struct Bound {
73 lower: f64,
74 upper: f64,
75}
76
77impl std::fmt::Debug for Bound {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "Bound{self}")
80 }
81}
82
83impl std::fmt::Display for Bound {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 let lower_bracket = if self.lower == f64::NEG_INFINITY {
86 "("
87 } else {
88 "["
89 };
90 let upper_bracket = if self.upper == f64::INFINITY {
91 ")"
92 } else {
93 "]"
94 };
95
96 let lower_str = if self.lower == f64::NEG_INFINITY {
97 "-inf".to_string()
98 } else {
99 self.lower.to_string()
100 };
101
102 let upper_str = if self.upper == f64::INFINITY {
103 "inf".to_string()
104 } else {
105 self.upper.to_string()
106 };
107
108 write!(f, "{lower_bracket}{lower_str}, {upper_str}{upper_bracket}")
109 }
110}
111
112impl Default for Bound {
113 fn default() -> Self {
114 Self {
115 lower: f64::NEG_INFINITY,
116 upper: f64::INFINITY,
117 }
118 }
119}
120
121impl Parse for v1::Bound {
122 type Output = Bound;
123 type Context = ();
124 fn parse(self, _: &Self::Context) -> Result<Self::Output, ParseError> {
125 let out = Bound::new(self.lower, self.upper)?;
126 Ok(out)
127 }
128}
129
130impl TryFrom<v1::Bound> for Bound {
131 type Error = BoundError;
132 fn try_from(value: v1::Bound) -> Result<Self, Self::Error> {
133 Self::new(value.lower, value.upper)
134 }
135}
136
137impl TryFrom<&v1::DecisionVariable> for Bound {
138 type Error = BoundError;
139 fn try_from(v: &v1::DecisionVariable) -> Result<Self, Self::Error> {
140 if let Some(bound) = &v.bound {
141 Self::try_from(bound.clone())
142 } else if v.kind() == v1::decision_variable::Kind::Binary {
143 Self::new(0.0, 1.0)
144 } else {
145 Ok(Self::default())
146 }
147 }
148}
149
150impl From<Bound> for v1::Bound {
151 fn from(bound: Bound) -> Self {
152 Self {
153 lower: bound.lower,
154 upper: bound.upper,
155 }
156 }
157}
158
159impl TryFrom<f64> for Bound {
160 type Error = BoundError;
161 fn try_from(value: f64) -> Result<Self, Self::Error> {
162 Self::new(value, value)
163 }
164}
165
166impl TryFrom<[f64; 2]> for Bound {
167 type Error = BoundError;
168 fn try_from([lower, upper]: [f64; 2]) -> Result<Self, Self::Error> {
169 Self::new(lower, upper)
170 }
171}
172
173impl Add for Bound {
174 type Output = Bound;
175 fn add(self, rhs: Self) -> Self::Output {
176 Self::new(self.lower + rhs.lower, self.upper + rhs.upper).unwrap()
177 }
178}
179impl Add<f64> for Bound {
180 type Output = Bound;
181 fn add(self, rhs: f64) -> Self::Output {
182 Bound::new(self.lower + rhs, self.upper + rhs).unwrap()
183 }
184}
185impl_add_inverse!(f64, Bound);
186
187impl AddAssign for Bound {
188 fn add_assign(&mut self, rhs: Self) {
189 *self = *self + rhs;
190 }
191}
192
193impl AddAssign<f64> for Bound {
194 fn add_assign(&mut self, rhs: f64) {
195 *self = *self + rhs;
196 }
197}
198
199impl Zero for Bound {
200 fn zero() -> Self {
201 Self::try_from(0.0).unwrap()
202 }
203 fn is_zero(&self) -> bool {
204 self.lower == 0.0 && self.upper == 0.0
205 }
206}
207
208impl Mul for Bound {
209 type Output = Bound;
210 fn mul(self, rhs: Self) -> Self::Output {
211 if self == Bound::zero() || rhs == Bound::zero() {
213 return Bound::zero();
214 }
215 let a = self.lower * rhs.lower;
216 let b = self.lower * rhs.upper;
217 let c = self.upper * rhs.lower;
218 let d = self.upper * rhs.upper;
219 Bound::new(a.min(b).min(c).min(d), a.max(b).max(c).max(d)).unwrap()
220 }
221}
222
223impl Mul<f64> for Bound {
224 type Output = Bound;
225 fn mul(self, rhs: f64) -> Self::Output {
226 if rhs >= 0.0 {
227 Bound::new(self.lower * rhs, self.upper * rhs).unwrap()
228 } else {
229 Bound::new(self.upper * rhs, self.lower * rhs).unwrap()
230 }
231 }
232}
233impl_mul_inverse!(f64, Bound);
234
235impl MulAssign for Bound {
236 fn mul_assign(&mut self, rhs: Self) {
237 *self = *self * rhs;
238 }
239}
240impl MulAssign<f64> for Bound {
241 fn mul_assign(&mut self, rhs: f64) {
242 *self = *self * rhs;
243 }
244}
245
246impl PartialEq<f64> for Bound {
247 fn eq(&self, other: &f64) -> bool {
248 self.lower == *other && self.upper == *other
249 }
250}
251
252impl PartialEq<Bound> for f64 {
253 fn eq(&self, other: &Bound) -> bool {
254 other == self
255 }
256}
257
258impl PartialOrd<f64> for Bound {
262 fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> {
263 debug_assert!(
264 self.lower <= self.upper,
265 "lower({}) <= upper({})",
266 self.lower,
267 self.upper
268 );
269 if other <= &self.lower {
270 Some(std::cmp::Ordering::Greater)
271 } else if other >= &self.upper {
272 Some(std::cmp::Ordering::Less)
273 } else {
274 None
275 }
276 }
277}
278
279impl PartialOrd<Bound> for f64 {
280 fn partial_cmp(&self, other: &Bound) -> Option<std::cmp::Ordering> {
281 other.partial_cmp(self).map(|o| o.reverse())
282 }
283}
284
285impl AbsDiffEq for Bound {
286 type Epsilon = ATol;
287
288 fn default_epsilon() -> Self::Epsilon {
289 ATol::default()
290 }
291
292 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
293 (self.lower == other.lower || self.lower.abs_diff_eq(&other.lower, *epsilon))
295 && (self.upper == other.upper || self.upper.abs_diff_eq(&other.upper, *epsilon))
296 }
297}
298
299impl Bound {
300 pub fn positive() -> Self {
302 Self::new(0.0, f64::INFINITY).unwrap()
303 }
304
305 pub fn negative() -> Self {
307 Self::new(f64::NEG_INFINITY, 0.0).unwrap()
308 }
309
310 pub fn unbounded() -> Self {
312 Self::default()
313 }
314
315 pub fn of_binary() -> Self {
316 Self::new(0.0, 1.0).unwrap()
317 }
318
319 pub fn new(lower: f64, upper: f64) -> Result<Self, BoundError> {
320 BoundError::check(lower, upper)?;
321 Ok(Self { lower, upper })
322 }
323
324 pub fn lower(&self) -> f64 {
325 self.lower
326 }
327
328 pub fn upper(&self) -> f64 {
329 self.upper
330 }
331
332 pub fn width(&self) -> f64 {
333 self.upper - self.lower
334 }
335
336 pub fn set_lower(&mut self, lower: f64) -> Result<(), BoundError> {
337 BoundError::check(lower, self.upper)?;
338 self.lower = lower;
339 Ok(())
340 }
341
342 pub fn set_upper(&mut self, upper: f64) -> Result<(), BoundError> {
343 BoundError::check(self.lower, upper)?;
344 self.upper = upper;
345 Ok(())
346 }
347
348 pub fn as_integer_bound(&self, atol: crate::ATol) -> Option<Self> {
376 let lower = if self.lower.is_finite() {
377 let out = (self.lower - atol).ceil();
378 if out == 0.0 {
379 0.0 } else {
381 out
382 }
383 } else {
384 self.lower
385 };
386 let upper = if self.upper.is_finite() {
387 (self.upper + atol).floor()
388 } else {
389 self.upper
390 };
391 if upper < lower {
392 None
393 } else {
394 Some(Self { lower, upper })
395 }
396 }
397
398 pub fn is_point(&self, atol: ATol) -> Option<f64> {
400 if self.lower.abs_diff_eq(&self.upper, atol.into_inner()) {
401 Some(self.lower)
402 } else {
403 None
404 }
405 }
406
407 pub fn is_finite(&self) -> bool {
409 self.lower.is_finite() && self.upper.is_finite()
410 }
411
412 pub fn intersection(&self, other: &Self) -> Option<Self> {
414 Self::new(self.lower.max(other.lower), self.upper.min(other.upper)).ok()
415 }
416
417 pub fn pow(&self, exp: u8) -> Self {
418 if exp.is_multiple_of(2) {
419 if self.lower >= 0.0 {
420 Bound::new(self.lower.powi(exp as i32), self.upper.powi(exp as i32)).unwrap()
422 } else if self.upper <= 0.0 {
423 Bound::new(self.upper.powi(exp as i32), self.lower.powi(exp as i32)).unwrap()
425 } else {
426 Bound::new(
428 0.0,
429 self.upper
430 .abs()
431 .powi(exp as i32)
432 .max(self.lower.abs().powi(exp as i32)),
433 )
434 .unwrap()
435 }
436 } else {
437 Bound::new(self.lower.powi(exp as i32), self.upper.powi(exp as i32)).unwrap()
439 }
440 }
441
442 pub fn contains(&self, value: f64, atol: crate::ATol) -> bool {
444 self.lower - atol <= value && value <= self.upper + atol
445 }
446
447 pub fn as_range(&self) -> RangeInclusive<f64> {
448 self.lower..=self.upper
449 }
450
451 pub fn nearest_to_zero(&self) -> f64 {
452 if self.lower >= 0.0 {
453 self.lower
454 } else if self.upper <= 0.0 {
455 self.upper
456 } else {
457 0.0
458 }
459 }
460
461 pub fn arbitrary_containing(&self, max_abs: f64) -> BoxedStrategy<f64> {
466 assert!(max_abs > 0.0);
467 match (self.lower, self.upper) {
469 (f64::NEG_INFINITY, f64::INFINITY) => (-max_abs..=max_abs).boxed(),
470 (f64::NEG_INFINITY, upper) => {
471 let upper = upper.min(max_abs);
472 prop_oneof![(-max_abs..=upper).boxed(), Just(upper)].boxed()
473 }
474 (lower, f64::INFINITY) => {
475 let lower = lower.max(-max_abs);
476 prop_oneof![(lower..=max_abs).boxed(), Just(lower)].boxed()
477 }
478 (lower, upper) => {
479 let lower = lower.max(-max_abs);
480 let upper = upper.min(max_abs);
481 if lower == upper {
482 Just(lower).boxed()
483 } else {
484 prop_oneof![Just(upper), Just(lower), (lower..=upper)].boxed()
485 }
486 }
487 }
488 }
489
490 pub fn arbitrary_containing_integer(&self, max_abs: u64) -> BoxedStrategy<i64> {
491 let lower = self.lower.max(-(max_abs as f64)).ceil() as i64;
492 let upper = self.upper.min(max_abs as f64).floor() as i64;
493 if lower == upper {
494 Just(lower).boxed()
495 } else {
496 (lower..=upper).boxed()
497 }
498 }
499}
500
501impl Arbitrary for Bound {
502 type Parameters = ();
503 type Strategy = BoxedStrategy<Self>;
504 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
505 (
506 prop_oneof![
507 Just(f64::NEG_INFINITY),
508 Just(0.0),
509 (-10..=10).prop_map(|x| x as f64),
510 -10.0..=10.0
511 ],
512 prop_oneof![
513 Just(f64::INFINITY),
514 Just(0.0),
515 (-10..=10).prop_map(|x| x as f64),
516 -10.0..=10.0
517 ],
518 )
519 .prop_map(|(lower, upper)| {
520 if lower <= upper {
521 Bound::new(lower, upper).unwrap()
522 } else {
523 Bound::new(upper, lower).unwrap()
524 }
525 })
526 .boxed()
527 }
528}
529
530mod logical_memory;
531
532#[cfg(test)]
533mod tests {
534 use approx::assert_abs_diff_eq;
535
536 use super::*;
537
538 #[test]
539 fn partial_ord() {
540 assert!(1.0 <= Bound::new(2.0, 3.0).unwrap());
541 assert!(2.0 <= Bound::new(2.0, 3.0).unwrap());
542 assert!(3.0 >= Bound::new(2.0, 3.0).unwrap());
543 assert!(4.0 >= Bound::new(2.0, 3.0).unwrap());
544 assert!(f64::NEG_INFINITY <= Bound::new(2.0, 3.0).unwrap());
545 assert!(f64::INFINITY >= Bound::new(2.0, 3.0).unwrap());
546 }
547
548 #[test]
549 fn eq() {
550 assert_eq!(
551 Bound::new(f64::NEG_INFINITY, f64::INFINITY).unwrap(),
552 Bound::default()
553 );
554 assert_eq!(Bound::new(0.0, f64::INFINITY).unwrap(), Bound::positive());
555 assert_eq!(
556 Bound::new(f64::NEG_INFINITY, 0.0).unwrap(),
557 Bound::negative()
558 );
559
560 assert_abs_diff_eq!(
561 Bound::new(1.0, 2.0).unwrap(),
562 Bound::new(1.0, 2.00000001).unwrap(),
563 );
564 assert_abs_diff_eq!(
565 Bound::new(f64::NEG_INFINITY, f64::INFINITY).unwrap(),
566 Bound::default()
567 );
568 assert_abs_diff_eq!(Bound::new(0.0, f64::INFINITY).unwrap(), Bound::positive());
569 assert_abs_diff_eq!(
570 Bound::new(f64::NEG_INFINITY, 0.0).unwrap(),
571 Bound::negative()
572 );
573 }
574
575 #[test]
576 fn intersection() {
577 assert_eq!(
578 Bound::new(1.0, 2.0)
579 .unwrap()
580 .intersection(&Bound::new(1.5, 3.0).unwrap()),
581 Some(Bound::new(1.5, 2.0).unwrap())
582 );
583 assert_eq!(
584 Bound::new(1.0, 2.0)
585 .unwrap()
586 .intersection(&Bound::new(2.5, 3.0).unwrap()),
587 None
588 );
589 assert_eq!(
590 Bound::positive().intersection(&Bound::negative()).unwrap(),
591 0.0
592 );
593 }
594
595 #[test]
596 fn minus_zero() {
597 let bound = Bound::new(-0.1, 1.1).unwrap();
598 insta::assert_snapshot!(bound.as_integer_bound(ATol::default()).unwrap(), @"[0, 1]");
599 }
600
601 #[test]
602 fn bound_pow() {
603 insta::assert_debug_snapshot!(Bound::new(2.0, 3.0).unwrap().pow(2), @"Bound[4, 9]");
604 insta::assert_debug_snapshot!(Bound::new(2.0, 3.0).unwrap().pow(3), @"Bound[8, 27]");
605 insta::assert_debug_snapshot!(Bound::new(-2.0, 3.0).unwrap().pow(2), @"Bound[0, 9]");
606 insta::assert_debug_snapshot!(Bound::new(-2.0, 3.0).unwrap().pow(3), @"Bound[-8, 27]");
607 insta::assert_debug_snapshot!(Bound::default().pow(2), @"Bound[0, inf)");
608 insta::assert_debug_snapshot!(Bound::default().pow(3), @"Bound(-inf, inf)");
609 }
610
611 fn bound_and_containing() -> BoxedStrategy<(Bound, f64)> {
612 Bound::arbitrary()
613 .prop_flat_map(|bound| (Just(bound), bound.arbitrary_containing(1e5)))
614 .boxed()
615 }
616
617 proptest! {
618 #[test]
619 fn contains((bound, value) in bound_and_containing()) {
620 prop_assert!(bound.contains(value, crate::ATol::default()));
621 }
622
623 #[test]
624 fn add((b1, v1) in bound_and_containing(), (b2, v2) in bound_and_containing()) {
625 prop_assert!((b1 + b2).contains(v1 + v2, crate::ATol::default()));
626 }
627
628 #[test]
629 fn mul((b1, v1) in bound_and_containing(), (b2, v2) in bound_and_containing()) {
630 prop_assert!((b1 * b2).contains(v1 * v2, crate::ATol::default()));
631 }
632
633 #[test]
634 fn pow((b, v) in bound_and_containing()) {
635 prop_assert!(b.pow(2).contains(v.powi(2), crate::ATol::default()));
636 prop_assert!(b.pow(3).contains(v.powi(3), crate::ATol::default()));
637 }
638 }
639}