woof_engine/interval/integer/
mul.rs

1use std::ops::Mul;
2
3use itertools::{Itertools, MinMaxResult};
4use num::BigInt;
5
6use super::IntegerInterval;
7
8impl<'a> Mul<&'a IntegerInterval> for &'a IntegerInterval {
9    type Output = IntegerInterval;
10
11    fn mul(self, rhs: Self) -> Self::Output {
12        fn min_max_to_interval<T>(arr: T) -> IntegerInterval
13        where
14            T: IntoIterator<Item = BigInt>,
15        {
16            match arr.into_iter().minmax() {
17                MinMaxResult::NoElements => unreachable!(),
18                MinMaxResult::OneElement(x) => IntegerInterval::Single(x),
19                MinMaxResult::MinMax(start, end) => IntegerInterval::Bound { start, end },
20            }
21        }
22
23        use IntegerInterval::*;
24        match (self, rhs) {
25            // Can only get a empty interval if one of the inputs is empty.
26            (Empty, _) | (_, Empty) => Empty,
27
28            // Can only get a single element interval if both inputs are single element.
29            (Single(x), Single(y)) => Single(x * y),
30
31            // Multiply closed intervals to get a bounded interval.
32            (Single(x), Bound { start: c, end: d }) => {
33                min_max_to_interval([x * c, x * d])
34            }
35            (Bound { start: a, end: b }, Single(y)) => {
36                min_max_to_interval([a * y, b * y])
37            }
38            (Bound { start: a, end: b }, Bound { start: c, end: d }) => {
39                min_max_to_interval([a * c, a * d, b * c, b * d])
40            }
41
42            // Right infinity will always be a right infinity under multiplication.
43            (Single(x), OpenRight { start: c }) => {
44                if x != &0.into() {
45                    OpenRight { start: x * c }
46                } else {
47                    Single(0.into())
48                }
49            },
50            (OpenRight { start: a }, Single(y)) => {
51                if y != &0.into() {
52                    OpenRight { start: a * y }
53                } else {
54                    Single(0.into())
55                }
56            },
57            (Bound { start: a, end: b }, OpenRight { start: c }) => {
58                let start = [a * c, b * c].into_iter().min().unwrap();
59                OpenRight { start }
60            }
61            (OpenRight { start: a }, Bound { start: c, end: d }) => {
62                let start = [a * c, a * d].into_iter().min().unwrap();
63                OpenRight { start }
64            }
65            (OpenRight { start: a }, OpenRight { start: c }) => {
66                OpenRight { start: a * c }
67            }
68
69            // Left infinity will always be a left infinity under multiplication.
70            (Single(x), OpenLeft { end: d }) => OpenLeft { end: x * d },
71            (OpenLeft { end: b }, Single(y)) => OpenLeft { end: b * y },
72            (Bound { start: a, end: b }, OpenLeft { end: d }) => {
73                let end = [a * d, b * d].into_iter().max().unwrap();
74                OpenLeft { end }
75            }
76            (OpenLeft { end: b }, Bound { start: c, end: d }) => {
77                let end = [b * c, b * d].into_iter().max().unwrap();
78                OpenLeft { end }
79            }
80            (OpenLeft { end: b }, OpenLeft { end: d }) => OpenLeft { end: b * d },
81
82            // A open interval or two open intervals facing opposite directions make a
83            // open interval.
84            (Open, _) | (_, Open) => Open,
85            (OpenRight { .. }, OpenLeft { .. }) => Open,
86            (OpenLeft { .. }, OpenRight { .. }) => Open,
87        }
88        .normalize()
89    }
90}
91
92#[test]
93fn mul_examples() {
94    // panic!("{}", &IntegerInterval::new(0, 0) * &IntegerInterval::OpenRight { start: 3.into() })
95}
96
97#[test]
98fn mul() {
99    use super::test::*;
100
101    fn do_mul(a: IntegerInterval, b: IntegerInterval) {
102        // Do operation under test to get interval.
103        let c = &a * &b;
104
105        let assert_in_c = |x, y| {
106            use Integer::*;
107            match (x, y) {
108                (Finite(x), Finite(y)) => assert_in_interval(x * y, &c),
109                (Finite(_), NegativeInfinity) => assert_neg_infinity_in_interval(&c),
110                (Finite(_), PositiveInfinity) => assert_infinity_in_interval(&c),
111                (NegativeInfinity, Finite(_)) => assert_neg_infinity_in_interval(&c),
112                (PositiveInfinity, Finite(_)) => assert_infinity_in_interval(&c),
113                (NegativeInfinity, NegativeInfinity) => {
114                    assert_neg_infinity_in_interval(&c)
115                }
116                (PositiveInfinity, PositiveInfinity) => assert_infinity_in_interval(&c),
117                (PositiveInfinity, NegativeInfinity) => {
118                    assert_neg_infinity_in_interval(&c);
119                    assert_infinity_in_interval(&c);
120                }
121                (NegativeInfinity, PositiveInfinity) => {
122                    assert_neg_infinity_in_interval(&c);
123                    assert_infinity_in_interval(&c);
124                }
125            }
126        };
127
128        if (matches!(&a, IntegerInterval::Empty) || matches!(&b, IntegerInterval::Empty))
129        {
130            assert!(matches!(c, IntegerInterval::Empty));
131        } else {
132            // low bound, low bound
133            assert_in_c(
134                interval_into_integers(a.clone()).0,
135                interval_into_integers(b.clone()).0,
136            );
137
138            // low bound, high bound
139            assert_in_c(
140                interval_into_integers(a.clone()).0,
141                interval_into_integers(b.clone()).1,
142            );
143
144            // high bound, low bound
145            assert_in_c(
146                interval_into_integers(a.clone()).1,
147                interval_into_integers(b.clone()).0,
148            );
149
150            // high bound, high bound
151            assert_in_c(
152                interval_into_integers(a.clone()).1,
153                interval_into_integers(b.clone()).1,
154            );
155        }
156    }
157
158    proptest!(|(a in interval_strategy(), b in interval_strategy())| {
159        do_mul(a, b)
160    });
161
162    proptest!(|(a in interval_strategy(), b in interval_strategy())| {
163        let c = &(&a * &b) / &b;
164        if matches!(a, IntegerInterval::Empty) || matches!(b, IntegerInterval::Empty) {
165            assert_eq!(c, IntegerInterval::Empty)
166        } else if matches!(b, IntegerInterval::Open) {
167            assert_eq!(c, IntegerInterval::Single(0.into()))
168        } else {
169            assert_eq!(c, a);
170        }
171    });
172}