Skip to main content

oxiz_math/
interval.rs

1//! Interval arithmetic for bound propagation.
2//!
3//! This module provides interval arithmetic operations used for:
4//! - Bound propagation in linear and nonlinear arithmetic
5//! - Sign determination for polynomials
6//! - Root isolation in CAD
7//!
8//! Reference: Z3's `math/interval/` directory.
9
10#[allow(unused_imports)]
11use crate::prelude::*;
12use core::cmp::Ordering;
13use core::fmt;
14use core::ops::{Add, Div, Mul, Neg, Sub};
15use num_bigint::BigInt;
16use num_rational::BigRational;
17use num_traits::{One, Signed, Zero};
18
19/// An endpoint of an interval, which can be finite or infinite.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum Bound {
22    /// Negative infinity.
23    NegInf,
24    /// A finite rational value.
25    Finite(BigRational),
26    /// Positive infinity.
27    PosInf,
28}
29
30impl Bound {
31    /// Create a finite bound.
32    #[inline]
33    pub fn finite(r: BigRational) -> Self {
34        Self::Finite(r)
35    }
36
37    /// Create a finite bound from an integer.
38    #[inline]
39    pub fn from_int(n: i64) -> Self {
40        Self::Finite(BigRational::from_integer(BigInt::from(n)))
41    }
42
43    /// Check if this bound is finite.
44    #[inline]
45    pub fn is_finite(&self) -> bool {
46        matches!(self, Self::Finite(_))
47    }
48
49    /// Check if this bound is infinite.
50    #[inline]
51    pub fn is_infinite(&self) -> bool {
52        !self.is_finite()
53    }
54
55    /// Check if this bound is negative infinity.
56    #[inline]
57    pub fn is_neg_inf(&self) -> bool {
58        matches!(self, Self::NegInf)
59    }
60
61    /// Check if this bound is positive infinity.
62    #[inline]
63    pub fn is_pos_inf(&self) -> bool {
64        matches!(self, Self::PosInf)
65    }
66
67    /// Get the finite value, or None if infinite.
68    pub fn as_finite(&self) -> Option<&BigRational> {
69        match self {
70            Self::Finite(r) => Some(r),
71            _ => None,
72        }
73    }
74
75    /// Negate the bound.
76    pub fn negate(&self) -> Bound {
77        match self {
78            Self::NegInf => Self::PosInf,
79            Self::PosInf => Self::NegInf,
80            Self::Finite(r) => Self::Finite(-r),
81        }
82    }
83
84    /// Add two bounds.
85    pub fn add(&self, other: &Bound) -> Bound {
86        match (self, other) {
87            (Self::NegInf, Self::PosInf) | (Self::PosInf, Self::NegInf) => {
88                panic!("Undefined: inf + (-inf)")
89            }
90            (Self::NegInf, _) | (_, Self::NegInf) => Self::NegInf,
91            (Self::PosInf, _) | (_, Self::PosInf) => Self::PosInf,
92            (Self::Finite(a), Self::Finite(b)) => Self::Finite(a + b),
93        }
94    }
95
96    /// Subtract two bounds.
97    pub fn sub(&self, other: &Bound) -> Bound {
98        self.add(&other.negate())
99    }
100
101    /// Multiply two bounds.
102    pub fn mul(&self, other: &Bound) -> Bound {
103        match (self, other) {
104            (Self::Finite(a), Self::Finite(b)) => Self::Finite(a * b),
105            (Self::Finite(a), inf) | (inf, Self::Finite(a)) => {
106                if a.is_zero() {
107                    // 0 * inf is undefined, but we can treat it as 0 for interval purposes
108                    Self::Finite(BigRational::zero())
109                } else if a.is_positive() {
110                    inf.clone()
111                } else {
112                    inf.negate()
113                }
114            }
115            (Self::NegInf, Self::NegInf) | (Self::PosInf, Self::PosInf) => Self::PosInf,
116            (Self::NegInf, Self::PosInf) | (Self::PosInf, Self::NegInf) => Self::NegInf,
117        }
118    }
119
120    /// Compare two bounds.
121    pub fn cmp_bound(&self, other: &Bound) -> Ordering {
122        match (self, other) {
123            (Self::NegInf, Self::NegInf) => Ordering::Equal,
124            (Self::NegInf, _) => Ordering::Less,
125            (_, Self::NegInf) => Ordering::Greater,
126            (Self::PosInf, Self::PosInf) => Ordering::Equal,
127            (Self::PosInf, _) => Ordering::Greater,
128            (_, Self::PosInf) => Ordering::Less,
129            (Self::Finite(a), Self::Finite(b)) => a.cmp(b),
130        }
131    }
132
133    /// Get the minimum of two bounds.
134    pub fn min(&self, other: &Bound) -> Bound {
135        if self.cmp_bound(other) == Ordering::Less {
136            self.clone()
137        } else {
138            other.clone()
139        }
140    }
141
142    /// Get the maximum of two bounds.
143    pub fn max(&self, other: &Bound) -> Bound {
144        if self.cmp_bound(other) == Ordering::Greater {
145            self.clone()
146        } else {
147            other.clone()
148        }
149    }
150}
151
152impl PartialOrd for Bound {
153    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
154        Some(core::cmp::Ord::cmp(self, other))
155    }
156}
157
158impl Ord for Bound {
159    fn cmp(&self, other: &Self) -> Ordering {
160        self.cmp_bound(other)
161    }
162}
163
164impl fmt::Display for Bound {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            Self::NegInf => write!(f, "-∞"),
168            Self::PosInf => write!(f, "+∞"),
169            Self::Finite(r) => write!(f, "{}", r),
170        }
171    }
172}
173
174/// An interval [lo, hi] with optional open/closed endpoints.
175/// Represents the set of values x such that lo ≤ x ≤ hi (closed)
176/// or lo < x < hi (open), depending on the flags.
177#[derive(Clone, Debug, PartialEq, Eq)]
178pub struct Interval {
179    /// Lower bound.
180    pub lo: Bound,
181    /// Upper bound.
182    pub hi: Bound,
183    /// True if lower bound is open (exclusive).
184    pub lo_open: bool,
185    /// True if upper bound is open (exclusive).
186    pub hi_open: bool,
187}
188
189impl Interval {
190    /// Create the empty interval.
191    pub fn empty() -> Self {
192        Self {
193            lo: Bound::PosInf,
194            hi: Bound::NegInf,
195            lo_open: true,
196            hi_open: true,
197        }
198    }
199
200    /// Create the interval containing all reals (-∞, +∞).
201    pub fn reals() -> Self {
202        Self {
203            lo: Bound::NegInf,
204            hi: Bound::PosInf,
205            lo_open: true,
206            hi_open: true,
207        }
208    }
209
210    /// Create a point interval [a, a].
211    pub fn point(a: BigRational) -> Self {
212        Self {
213            lo: Bound::Finite(a.clone()),
214            hi: Bound::Finite(a),
215            lo_open: false,
216            hi_open: false,
217        }
218    }
219
220    /// Create a closed interval [a, b].
221    pub fn closed(a: BigRational, b: BigRational) -> Self {
222        Self {
223            lo: Bound::Finite(a),
224            hi: Bound::Finite(b),
225            lo_open: false,
226            hi_open: false,
227        }
228    }
229
230    /// Create an open interval (a, b).
231    pub fn open(a: BigRational, b: BigRational) -> Self {
232        Self {
233            lo: Bound::Finite(a),
234            hi: Bound::Finite(b),
235            lo_open: true,
236            hi_open: true,
237        }
238    }
239
240    /// Create a half-open interval [a, b).
241    pub fn half_open_right(a: BigRational, b: BigRational) -> Self {
242        Self {
243            lo: Bound::Finite(a),
244            hi: Bound::Finite(b),
245            lo_open: false,
246            hi_open: true,
247        }
248    }
249
250    /// Create a half-open interval (a, b].
251    pub fn half_open_left(a: BigRational, b: BigRational) -> Self {
252        Self {
253            lo: Bound::Finite(a),
254            hi: Bound::Finite(b),
255            lo_open: true,
256            hi_open: false,
257        }
258    }
259
260    /// Create an interval (-∞, b].
261    pub fn at_most(b: BigRational) -> Self {
262        Self {
263            lo: Bound::NegInf,
264            hi: Bound::Finite(b),
265            lo_open: true,
266            hi_open: false,
267        }
268    }
269
270    /// Create an interval (-∞, b).
271    pub fn less_than(b: BigRational) -> Self {
272        Self {
273            lo: Bound::NegInf,
274            hi: Bound::Finite(b),
275            lo_open: true,
276            hi_open: true,
277        }
278    }
279
280    /// Create an interval [a, +∞).
281    pub fn at_least(a: BigRational) -> Self {
282        Self {
283            lo: Bound::Finite(a),
284            hi: Bound::PosInf,
285            lo_open: false,
286            hi_open: true,
287        }
288    }
289
290    /// Create an interval (a, +∞).
291    pub fn greater_than(a: BigRational) -> Self {
292        Self {
293            lo: Bound::Finite(a),
294            hi: Bound::PosInf,
295            lo_open: true,
296            hi_open: true,
297        }
298    }
299
300    /// Check if the interval is empty.
301    pub fn is_empty(&self) -> bool {
302        match self.lo.cmp_bound(&self.hi) {
303            Ordering::Greater => true,
304            Ordering::Equal => self.lo_open || self.hi_open,
305            Ordering::Less => false,
306        }
307    }
308
309    /// Check if the interval is a single point.
310    pub fn is_point(&self) -> bool {
311        !self.is_empty() && self.lo == self.hi && !self.lo_open && !self.hi_open
312    }
313
314    /// Check if the interval is bounded (both endpoints finite).
315    pub fn is_bounded(&self) -> bool {
316        self.lo.is_finite() && self.hi.is_finite()
317    }
318
319    /// Check if the interval contains a value.
320    pub fn contains(&self, x: &BigRational) -> bool {
321        let x_bound = Bound::Finite(x.clone());
322        let lo_ok = match self.lo.cmp_bound(&x_bound) {
323            Ordering::Less => true,
324            Ordering::Equal => !self.lo_open,
325            Ordering::Greater => false,
326        };
327        let hi_ok = match x_bound.cmp_bound(&self.hi) {
328            Ordering::Less => true,
329            Ordering::Equal => !self.hi_open,
330            Ordering::Greater => false,
331        };
332        lo_ok && hi_ok
333    }
334
335    /// Check if this interval contains zero.
336    pub fn contains_zero(&self) -> bool {
337        self.contains(&BigRational::zero())
338    }
339
340    /// Check if all values in the interval are positive.
341    pub fn is_positive(&self) -> bool {
342        match &self.lo {
343            Bound::NegInf => false,
344            Bound::PosInf => self.is_empty(),
345            Bound::Finite(r) => {
346                if r.is_positive() {
347                    true
348                } else if r.is_zero() {
349                    self.lo_open
350                } else {
351                    false
352                }
353            }
354        }
355    }
356
357    /// Check if all values in the interval are negative.
358    pub fn is_negative(&self) -> bool {
359        match &self.hi {
360            Bound::PosInf => false,
361            Bound::NegInf => self.is_empty(),
362            Bound::Finite(r) => {
363                if r.is_negative() {
364                    true
365                } else if r.is_zero() {
366                    self.hi_open
367                } else {
368                    false
369                }
370            }
371        }
372    }
373
374    /// Check if all values are non-negative.
375    pub fn is_non_negative(&self) -> bool {
376        match &self.lo {
377            Bound::NegInf => false,
378            Bound::PosInf => true,
379            Bound::Finite(r) => !r.is_negative(),
380        }
381    }
382
383    /// Check if all values are non-positive.
384    pub fn is_non_positive(&self) -> bool {
385        match &self.hi {
386            Bound::PosInf => false,
387            Bound::NegInf => true,
388            Bound::Finite(r) => !r.is_positive(),
389        }
390    }
391
392    /// Get the sign of the interval.
393    /// Returns Some(1) if all positive, Some(-1) if all negative, Some(0) if only zero.
394    /// Returns None if the interval contains values of different signs.
395    pub fn sign(&self) -> Option<i8> {
396        if self.is_empty() {
397            return None;
398        }
399        if self.is_point()
400            && let Bound::Finite(r) = &self.lo
401        {
402            return Some(if r.is_positive() {
403                1
404            } else if r.is_negative() {
405                -1
406            } else {
407                0
408            });
409        }
410        if self.is_positive() {
411            Some(1)
412        } else if self.is_negative() {
413            Some(-1)
414        } else {
415            None
416        }
417    }
418
419    /// Negate the interval.
420    pub fn negate(&self) -> Interval {
421        Interval {
422            lo: self.hi.negate(),
423            hi: self.lo.negate(),
424            lo_open: self.hi_open,
425            hi_open: self.lo_open,
426        }
427    }
428
429    /// Add two intervals.
430    pub fn add(&self, other: &Interval) -> Interval {
431        if self.is_empty() || other.is_empty() {
432            return Interval::empty();
433        }
434        Interval {
435            lo: self.lo.add(&other.lo),
436            hi: self.hi.add(&other.hi),
437            lo_open: self.lo_open || other.lo_open,
438            hi_open: self.hi_open || other.hi_open,
439        }
440    }
441
442    /// Subtract two intervals.
443    pub fn sub(&self, other: &Interval) -> Interval {
444        self.add(&other.negate())
445    }
446
447    /// Multiply two intervals.
448    pub fn mul(&self, other: &Interval) -> Interval {
449        if self.is_empty() || other.is_empty() {
450            return Interval::empty();
451        }
452
453        // Handle cases with infinity more carefully
454        // For bounded intervals, we compute all four products
455        // For unbounded intervals, the logic is more complex
456
457        // Compute all four products and their openness
458        let products = [
459            (&self.lo, &other.lo, self.lo_open || other.lo_open),
460            (&self.lo, &other.hi, self.lo_open || other.hi_open),
461            (&self.hi, &other.lo, self.hi_open || other.lo_open),
462            (&self.hi, &other.hi, self.hi_open || other.hi_open),
463        ];
464
465        // Compute actual product values
466        let mut bounds: Vec<(Bound, bool)> = products
467            .iter()
468            .map(|(a, b, open)| (a.mul(b), *open))
469            .collect();
470
471        // Find min and max
472        bounds.sort_by(|a, b| a.0.cmp_bound(&b.0));
473
474        let (lo, lo_open) = bounds
475            .first()
476            .expect("collection validated to be non-empty")
477            .clone();
478        let (hi, hi_open) = bounds
479            .last()
480            .expect("collection validated to be non-empty")
481            .clone();
482
483        Interval {
484            lo,
485            hi,
486            lo_open,
487            hi_open,
488        }
489    }
490
491    /// Divide two intervals.
492    /// Returns None if division by zero occurs (divisor contains zero).
493    pub fn div(&self, other: &Interval) -> Option<Interval> {
494        if self.is_empty() || other.is_empty() {
495            return Some(Interval::empty());
496        }
497
498        // Check if divisor contains zero
499        if other.contains_zero() {
500            // Division by zero is undefined
501            // In interval arithmetic, we need to split the interval or return extended intervals
502            // For simplicity, return None to indicate undefined
503            return None;
504        }
505
506        // Division: [a,b] / [c,d] = [a,b] * [1/d, 1/c]
507        // When c,d don't contain zero, we compute reciprocals and multiply
508
509        // Compute reciprocal of other
510        let recip_lo = match &other.hi {
511            Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
512            Bound::Finite(r) => {
513                if r.is_zero() {
514                    return None; // Contains zero
515                }
516                Bound::Finite(BigRational::one() / r)
517            }
518        };
519
520        let recip_hi = match &other.lo {
521            Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
522            Bound::Finite(r) => {
523                if r.is_zero() {
524                    return None; // Contains zero
525                }
526                Bound::Finite(BigRational::one() / r)
527            }
528        };
529
530        let recip = Interval {
531            lo: recip_lo,
532            hi: recip_hi,
533            lo_open: other.hi_open,
534            hi_open: other.lo_open,
535        };
536
537        Some(self.mul(&recip))
538    }
539
540    /// Compute the intersection of two intervals.
541    pub fn intersect(&self, other: &Interval) -> Interval {
542        if self.is_empty() || other.is_empty() {
543            return Interval::empty();
544        }
545
546        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
547            Ordering::Less => (other.lo.clone(), other.lo_open),
548            Ordering::Greater => (self.lo.clone(), self.lo_open),
549            Ordering::Equal => (self.lo.clone(), self.lo_open || other.lo_open),
550        };
551
552        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
553            Ordering::Less => (self.hi.clone(), self.hi_open),
554            Ordering::Greater => (other.hi.clone(), other.hi_open),
555            Ordering::Equal => (self.hi.clone(), self.hi_open || other.hi_open),
556        };
557
558        Interval {
559            lo,
560            hi,
561            lo_open,
562            hi_open,
563        }
564    }
565
566    /// Compute the union of two intervals (if contiguous).
567    /// Returns None if the intervals are not contiguous.
568    pub fn union(&self, other: &Interval) -> Option<Interval> {
569        if self.is_empty() {
570            return Some(other.clone());
571        }
572        if other.is_empty() {
573            return Some(self.clone());
574        }
575
576        // Check if intervals overlap or are adjacent
577        let intersects = !self.intersect(other).is_empty();
578        let adjacent_left = self.hi == other.lo && (!self.hi_open || !other.lo_open);
579        let adjacent_right = other.hi == self.lo && (!other.hi_open || !self.lo_open);
580
581        if !intersects && !adjacent_left && !adjacent_right {
582            return None;
583        }
584
585        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
586            Ordering::Less => (self.lo.clone(), self.lo_open),
587            Ordering::Greater => (other.lo.clone(), other.lo_open),
588            Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
589        };
590
591        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
592            Ordering::Greater => (self.hi.clone(), self.hi_open),
593            Ordering::Less => (other.hi.clone(), other.hi_open),
594            Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
595        };
596
597        Some(Interval {
598            lo,
599            hi,
600            lo_open,
601            hi_open,
602        })
603    }
604
605    /// Compute the power of an interval.
606    pub fn pow(&self, n: u32) -> Interval {
607        if self.is_empty() {
608            return Interval::empty();
609        }
610        if n == 0 {
611            return Interval::point(BigRational::one());
612        }
613        if n == 1 {
614            return self.clone();
615        }
616
617        if n.is_multiple_of(2) {
618            // Even power: result is non-negative
619            // [a,b]^n depends on whether interval contains 0
620            if self.contains_zero() {
621                // [min(|a|,|b|)^n, max(|a|,|b|)^n] but includes 0
622                let lo_abs = match &self.lo {
623                    Bound::Finite(r) => r.abs(),
624                    Bound::NegInf => return Interval::at_least(BigRational::zero()),
625                    Bound::PosInf => unreachable!(),
626                };
627                let hi_abs = match &self.hi {
628                    Bound::Finite(r) => r.abs(),
629                    Bound::PosInf => return Interval::at_least(BigRational::zero()),
630                    Bound::NegInf => unreachable!(),
631                };
632                let max_abs = lo_abs.max(hi_abs);
633                Interval {
634                    lo: Bound::Finite(BigRational::zero()),
635                    hi: Bound::Finite(pow_rational(&max_abs, n)),
636                    lo_open: false,
637                    hi_open: self.lo_open && self.hi_open,
638                }
639            } else if self.is_positive() {
640                // All positive: [a^n, b^n]
641                let lo_val = match &self.lo {
642                    Bound::Finite(r) => pow_rational(r, n),
643                    _ => return Interval::at_least(BigRational::zero()),
644                };
645                let hi_val = match &self.hi {
646                    Bound::Finite(r) => pow_rational(r, n),
647                    _ => return Interval::at_least(lo_val),
648                };
649                Interval {
650                    lo: Bound::Finite(lo_val),
651                    hi: Bound::Finite(hi_val),
652                    lo_open: self.lo_open,
653                    hi_open: self.hi_open,
654                }
655            } else {
656                // All negative: [b^n, a^n]
657                let lo_val = match &self.hi {
658                    Bound::Finite(r) => pow_rational(r, n),
659                    _ => return Interval::at_least(BigRational::zero()),
660                };
661                let hi_val = match &self.lo {
662                    Bound::Finite(r) => pow_rational(r, n),
663                    _ => return Interval::at_least(lo_val),
664                };
665                Interval {
666                    lo: Bound::Finite(lo_val),
667                    hi: Bound::Finite(hi_val),
668                    lo_open: self.hi_open,
669                    hi_open: self.lo_open,
670                }
671            }
672        } else {
673            // Odd power: preserves sign
674            // [a^n, b^n]
675            let lo_val = match &self.lo {
676                Bound::NegInf => Bound::NegInf,
677                Bound::PosInf => Bound::PosInf,
678                Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
679            };
680            let hi_val = match &self.hi {
681                Bound::NegInf => Bound::NegInf,
682                Bound::PosInf => Bound::PosInf,
683                Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
684            };
685            Interval {
686                lo: lo_val,
687                hi: hi_val,
688                lo_open: self.lo_open,
689                hi_open: self.hi_open,
690            }
691        }
692    }
693
694    /// Get the midpoint of the interval (if bounded).
695    pub fn midpoint(&self) -> Option<BigRational> {
696        match (&self.lo, &self.hi) {
697            (Bound::Finite(a), Bound::Finite(b)) => {
698                Some((a + b) / BigRational::from_integer(BigInt::from(2)))
699            }
700            _ => None,
701        }
702    }
703
704    /// Get the width of the interval (if bounded).
705    pub fn width(&self) -> Option<BigRational> {
706        match (&self.lo, &self.hi) {
707            (Bound::Finite(a), Bound::Finite(b)) => Some(b - a),
708            _ => None,
709        }
710    }
711
712    /// Split the interval at a point.
713    pub fn split(&self, at: &BigRational) -> (Interval, Interval) {
714        if !self.contains(at) {
715            return (self.clone(), Interval::empty());
716        }
717
718        let left = Interval {
719            lo: self.lo.clone(),
720            hi: Bound::Finite(at.clone()),
721            lo_open: self.lo_open,
722            hi_open: false,
723        };
724
725        let right = Interval {
726            lo: Bound::Finite(at.clone()),
727            hi: self.hi.clone(),
728            lo_open: false,
729            hi_open: self.hi_open,
730        };
731
732        (left, right)
733    }
734
735    /// Convex hull of two intervals (always returns a single interval).
736    /// This is the smallest interval containing both inputs.
737    pub fn hull(&self, other: &Interval) -> Interval {
738        if self.is_empty() {
739            return other.clone();
740        }
741        if other.is_empty() {
742            return self.clone();
743        }
744
745        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
746            Ordering::Less => (self.lo.clone(), self.lo_open),
747            Ordering::Greater => (other.lo.clone(), other.lo_open),
748            Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
749        };
750
751        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
752            Ordering::Greater => (self.hi.clone(), self.hi_open),
753            Ordering::Less => (other.hi.clone(), other.hi_open),
754            Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
755        };
756
757        Interval {
758            lo,
759            hi,
760            lo_open,
761            hi_open,
762        }
763    }
764
765    /// Tighten lower bound to be at least the given value.
766    /// Returns a new interval with the tightened bound, or empty if inconsistent.
767    pub fn tighten_lower(&self, new_lo: Bound, new_lo_open: bool) -> Interval {
768        if self.is_empty() {
769            return Interval::empty();
770        }
771
772        let (lo, lo_open) = match self.lo.cmp_bound(&new_lo) {
773            Ordering::Less => (new_lo, new_lo_open),
774            Ordering::Greater => (self.lo.clone(), self.lo_open),
775            Ordering::Equal => (self.lo.clone(), self.lo_open || new_lo_open),
776        };
777
778        Interval {
779            lo,
780            hi: self.hi.clone(),
781            lo_open,
782            hi_open: self.hi_open,
783        }
784    }
785
786    /// Tighten upper bound to be at most the given value.
787    /// Returns a new interval with the tightened bound, or empty if inconsistent.
788    pub fn tighten_upper(&self, new_hi: Bound, new_hi_open: bool) -> Interval {
789        if self.is_empty() {
790            return Interval::empty();
791        }
792
793        let (hi, hi_open) = match self.hi.cmp_bound(&new_hi) {
794            Ordering::Greater => (new_hi, new_hi_open),
795            Ordering::Less => (self.hi.clone(), self.hi_open),
796            Ordering::Equal => (self.hi.clone(), self.hi_open || new_hi_open),
797        };
798
799        Interval {
800            lo: self.lo.clone(),
801            hi,
802            lo_open: self.lo_open,
803            hi_open,
804        }
805    }
806
807    /// Propagate bounds from constraint: x + y ∈ result_interval.
808    /// Given x and result, tighten y's bounds.
809    /// Returns the tightened interval for y.
810    pub fn propagate_add(x: &Interval, result: &Interval) -> Interval {
811        // y ∈ result - x
812        result.sub(x)
813    }
814
815    /// Propagate bounds from constraint: x - y ∈ result_interval.
816    /// Given x and result, tighten y's bounds.
817    /// Returns the tightened interval for y.
818    pub fn propagate_sub_left(x: &Interval, result: &Interval) -> Interval {
819        // x - y = result => y = x - result
820        x.sub(result)
821    }
822
823    /// Propagate bounds from constraint: x - y ∈ result_interval.
824    /// Given y and result, tighten x's bounds.
825    /// Returns the tightened interval for x.
826    pub fn propagate_sub_right(y: &Interval, result: &Interval) -> Interval {
827        // x - y = result => x = result + y
828        result.add(y)
829    }
830
831    /// Propagate bounds from constraint: x * y ∈ result_interval.
832    /// Given x and result, tighten y's bounds (if x doesn't contain 0).
833    /// Returns None if x contains 0, otherwise returns the tightened interval for y.
834    pub fn propagate_mul(x: &Interval, result: &Interval) -> Option<Interval> {
835        // y ∈ result / x
836        Interval::div(result, x)
837    }
838
839    /// Propagate bounds from constraint: x / y ∈ result_interval.
840    /// Given x and result, tighten y's bounds (if result doesn't contain 0).
841    /// Returns None if propagation is impossible, otherwise returns the tightened interval for y.
842    pub fn propagate_div_left(x: &Interval, result: &Interval) -> Option<Interval> {
843        // x / y = result => y = x / result
844        Interval::div(x, result)
845    }
846
847    /// Propagate bounds from constraint: x / y ∈ result_interval.
848    /// Given y and result, tighten x's bounds.
849    /// Returns the tightened interval for x.
850    pub fn propagate_div_right(y: &Interval, result: &Interval) -> Interval {
851        // x / y = result => x = result * y
852        result.mul(y)
853    }
854
855    /// Check if this interval is a subset of another interval.
856    pub fn is_subset_of(&self, other: &Interval) -> bool {
857        if self.is_empty() {
858            return true;
859        }
860        if other.is_empty() {
861            return false;
862        }
863
864        // Check lower bound
865        let lo_ok = match self.lo.cmp_bound(&other.lo) {
866            Ordering::Less => false,
867            Ordering::Greater => true,
868            Ordering::Equal => !self.lo_open || other.lo_open,
869        };
870
871        // Check upper bound
872        let hi_ok = match self.hi.cmp_bound(&other.hi) {
873            Ordering::Greater => false,
874            Ordering::Less => true,
875            Ordering::Equal => !self.hi_open || other.hi_open,
876        };
877
878        lo_ok && hi_ok
879    }
880
881    /// Check if two intervals overlap (have non-empty intersection).
882    pub fn overlaps(&self, other: &Interval) -> bool {
883        !self.intersect(other).is_empty()
884    }
885
886    /// Widen the interval by a given amount on each side.
887    pub fn widen(&self, amount: &BigRational) -> Interval {
888        if self.is_empty() {
889            return Interval::empty();
890        }
891
892        let lo = match &self.lo {
893            Bound::Finite(r) => Bound::Finite(r - amount),
894            bound => bound.clone(),
895        };
896
897        let hi = match &self.hi {
898            Bound::Finite(r) => Bound::Finite(r + amount),
899            bound => bound.clone(),
900        };
901
902        Interval {
903            lo,
904            hi,
905            lo_open: self.lo_open,
906            hi_open: self.hi_open,
907        }
908    }
909}
910
911/// Compute r^n for a rational number.
912fn pow_rational(r: &BigRational, n: u32) -> BigRational {
913    if n == 0 {
914        return BigRational::one();
915    }
916
917    let mut result = BigRational::one();
918    let mut base = r.clone();
919    let mut exp = n;
920
921    while exp > 0 {
922        if exp & 1 == 1 {
923            result = &result * &base;
924        }
925        base = &base * &base;
926        exp >>= 1;
927    }
928
929    result
930}
931
932impl fmt::Display for Interval {
933    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
934        if self.is_empty() {
935            write!(f, "∅")
936        } else {
937            let lo_bracket = if self.lo_open { '(' } else { '[' };
938            let hi_bracket = if self.hi_open { ')' } else { ']' };
939            write!(f, "{}{}, {}{}", lo_bracket, self.lo, self.hi, hi_bracket)
940        }
941    }
942}
943
944impl Default for Interval {
945    fn default() -> Self {
946        Self::reals()
947    }
948}
949
950impl Neg for Interval {
951    type Output = Interval;
952
953    fn neg(self) -> Self::Output {
954        self.negate()
955    }
956}
957
958impl Neg for &Interval {
959    type Output = Interval;
960
961    fn neg(self) -> Self::Output {
962        self.negate()
963    }
964}
965
966impl Add for Interval {
967    type Output = Interval;
968
969    fn add(self, rhs: Self) -> Self::Output {
970        Interval::add(&self, &rhs)
971    }
972}
973
974impl Add<&Interval> for &Interval {
975    type Output = Interval;
976
977    fn add(self, rhs: &Interval) -> Self::Output {
978        Interval::add(self, rhs)
979    }
980}
981
982impl Sub for Interval {
983    type Output = Interval;
984
985    fn sub(self, rhs: Self) -> Self::Output {
986        Interval::sub(&self, &rhs)
987    }
988}
989
990impl Sub<&Interval> for &Interval {
991    type Output = Interval;
992
993    fn sub(self, rhs: &Interval) -> Self::Output {
994        Interval::sub(self, rhs)
995    }
996}
997
998impl Mul for Interval {
999    type Output = Interval;
1000
1001    fn mul(self, rhs: Self) -> Self::Output {
1002        Interval::mul(&self, &rhs)
1003    }
1004}
1005
1006impl Mul<&Interval> for &Interval {
1007    type Output = Interval;
1008
1009    fn mul(self, rhs: &Interval) -> Self::Output {
1010        Interval::mul(self, rhs)
1011    }
1012}
1013
1014impl Div for Interval {
1015    type Output = Option<Interval>;
1016
1017    fn div(self, rhs: Self) -> Self::Output {
1018        Interval::div(&self, &rhs)
1019    }
1020}
1021
1022impl Div<&Interval> for &Interval {
1023    type Output = Option<Interval>;
1024
1025    fn div(self, rhs: &Interval) -> Self::Output {
1026        Interval::div(self, rhs)
1027    }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032    use super::*;
1033
1034    fn rat(n: i64) -> BigRational {
1035        BigRational::from_integer(BigInt::from(n))
1036    }
1037
1038    #[test]
1039    fn test_interval_empty() {
1040        let i = Interval::empty();
1041        assert!(i.is_empty());
1042        assert!(!i.contains(&rat(0)));
1043    }
1044
1045    #[test]
1046    fn test_interval_point() {
1047        let i = Interval::point(rat(5));
1048        assert!(i.is_point());
1049        assert!(i.contains(&rat(5)));
1050        assert!(!i.contains(&rat(4)));
1051        assert!(i.is_positive());
1052    }
1053
1054    #[test]
1055    fn test_interval_closed() {
1056        let i = Interval::closed(rat(1), rat(5));
1057        assert!(i.contains(&rat(1)));
1058        assert!(i.contains(&rat(3)));
1059        assert!(i.contains(&rat(5)));
1060        assert!(!i.contains(&rat(0)));
1061        assert!(!i.contains(&rat(6)));
1062    }
1063
1064    #[test]
1065    fn test_interval_open() {
1066        let i = Interval::open(rat(1), rat(5));
1067        assert!(!i.contains(&rat(1)));
1068        assert!(i.contains(&rat(3)));
1069        assert!(!i.contains(&rat(5)));
1070    }
1071
1072    #[test]
1073    fn test_interval_add() {
1074        let a = Interval::closed(rat(1), rat(3));
1075        let b = Interval::closed(rat(2), rat(4));
1076        let c = Interval::add(&a, &b);
1077        assert!(c.contains(&rat(3)));
1078        assert!(c.contains(&rat(7)));
1079        assert!(!c.contains(&rat(2)));
1080        assert!(!c.contains(&rat(8)));
1081    }
1082
1083    #[test]
1084    fn test_interval_mul() {
1085        let a = Interval::closed(rat(2), rat(3));
1086        let b = Interval::closed(rat(4), rat(5));
1087        let c = Interval::mul(&a, &b);
1088        // [2,3] * [4,5] = [8, 15]
1089        assert!(c.contains(&rat(8)));
1090        assert!(c.contains(&rat(15)));
1091        assert!(!c.contains(&rat(7)));
1092        assert!(!c.contains(&rat(16)));
1093    }
1094
1095    #[test]
1096    fn test_interval_mul_mixed_signs() {
1097        let a = Interval::closed(rat(-2), rat(3));
1098        let b = Interval::closed(rat(-1), rat(2));
1099        let c = Interval::mul(&a, &b);
1100        // Products are: -2*-1=2, -2*2=-4, 3*-1=-3, 3*2=6
1101        // So result is [-4, 6]
1102        assert!(c.contains(&rat(-4)));
1103        assert!(c.contains(&rat(6)));
1104        assert!(c.contains(&rat(0)));
1105        assert!(!c.contains(&rat(-5)));
1106        assert!(!c.contains(&rat(7)));
1107    }
1108
1109    #[test]
1110    fn test_interval_negate() {
1111        let a = Interval::closed(rat(1), rat(5));
1112        let b = a.negate();
1113        assert!(b.contains(&rat(-5)));
1114        assert!(b.contains(&rat(-1)));
1115        assert!(!b.contains(&rat(0)));
1116    }
1117
1118    #[test]
1119    fn test_interval_intersect() {
1120        let a = Interval::closed(rat(1), rat(5));
1121        let b = Interval::closed(rat(3), rat(7));
1122        let c = a.intersect(&b);
1123        // [1,5] ∩ [3,7] = [3,5]
1124        assert!(c.contains(&rat(3)));
1125        assert!(c.contains(&rat(5)));
1126        assert!(!c.contains(&rat(2)));
1127        assert!(!c.contains(&rat(6)));
1128    }
1129
1130    #[test]
1131    fn test_interval_pow_even() {
1132        let a = Interval::closed(rat(-2), rat(3));
1133        let b = a.pow(2);
1134        // [-2,3]^2 = [0, 9]
1135        assert!(b.contains(&rat(0)));
1136        assert!(b.contains(&rat(9)));
1137        assert!(!b.contains(&rat(-1)));
1138        assert!(!b.contains(&rat(10)));
1139    }
1140
1141    #[test]
1142    fn test_interval_pow_odd() {
1143        let a = Interval::closed(rat(-2), rat(3));
1144        let b = a.pow(3);
1145        // [-2,3]^3 = [-8, 27]
1146        assert!(b.contains(&rat(-8)));
1147        assert!(b.contains(&rat(27)));
1148        assert!(!b.contains(&rat(-9)));
1149        assert!(!b.contains(&rat(28)));
1150    }
1151
1152    #[test]
1153    fn test_interval_sign() {
1154        assert_eq!(Interval::closed(rat(1), rat(5)).sign(), Some(1));
1155        assert_eq!(Interval::closed(rat(-5), rat(-1)).sign(), Some(-1));
1156        assert_eq!(Interval::closed(rat(-1), rat(1)).sign(), None);
1157        assert_eq!(Interval::point(rat(0)).sign(), Some(0));
1158    }
1159
1160    #[test]
1161    fn test_interval_unbounded() {
1162        let a = Interval::at_least(rat(0));
1163        assert!(a.is_non_negative());
1164        assert!(a.contains(&rat(0)));
1165        assert!(a.contains(&rat(1000)));
1166        assert!(!a.contains(&rat(-1)));
1167
1168        let b = Interval::less_than(rat(0));
1169        assert!(b.is_negative());
1170        assert!(!b.contains(&rat(0)));
1171        assert!(b.contains(&rat(-1)));
1172    }
1173
1174    #[test]
1175    fn test_interval_midpoint() {
1176        let i = Interval::closed(rat(2), rat(8));
1177        assert_eq!(i.midpoint(), Some(rat(5)));
1178    }
1179
1180    #[test]
1181    fn test_interval_div() {
1182        // [4, 12] / [2, 3] = [4/3, 6]
1183        let a = Interval::closed(rat(4), rat(12));
1184        let b = Interval::closed(rat(2), rat(3));
1185        let c = Interval::div(&a, &b).expect("Division should succeed");
1186        // 4/3 ≈ 1.333..., 12/2 = 6
1187        assert!(c.contains(&BigRational::new(BigInt::from(4), BigInt::from(3))));
1188        assert!(c.contains(&rat(6)));
1189    }
1190
1191    #[test]
1192    fn test_interval_div_by_zero() {
1193        // Division by interval containing zero should return None
1194        let a = Interval::closed(rat(1), rat(2));
1195        let b = Interval::closed(rat(-1), rat(1)); // Contains zero
1196        assert!(Interval::div(&a, &b).is_none());
1197    }
1198
1199    #[test]
1200    fn test_interval_div_positive() {
1201        // [6, 12] / [2, 3] = [2, 6]
1202        let a = Interval::closed(rat(6), rat(12));
1203        let b = Interval::closed(rat(2), rat(3));
1204        let c = Interval::div(&a, &b).expect("Division should succeed");
1205        assert!(c.contains(&rat(2)));
1206        assert!(c.contains(&rat(6)));
1207    }
1208
1209    #[test]
1210    fn test_interval_hull() {
1211        let a = Interval::closed(rat(1), rat(3));
1212        let b = Interval::closed(rat(5), rat(7));
1213        let hull = a.hull(&b);
1214        // Hull should be [1, 7]
1215        assert_eq!(hull.lo, Bound::Finite(rat(1)));
1216        assert_eq!(hull.hi, Bound::Finite(rat(7)));
1217        assert!(hull.contains(&rat(1)));
1218        assert!(hull.contains(&rat(4)));
1219        assert!(hull.contains(&rat(7)));
1220    }
1221
1222    #[test]
1223    fn test_interval_tighten_lower() {
1224        let i = Interval::closed(rat(1), rat(10));
1225        let tightened = i.tighten_lower(Bound::Finite(rat(5)), false);
1226        // Should become [5, 10]
1227        assert_eq!(tightened.lo, Bound::Finite(rat(5)));
1228        assert_eq!(tightened.hi, Bound::Finite(rat(10)));
1229        assert!(!tightened.contains(&rat(4)));
1230        assert!(tightened.contains(&rat(5)));
1231    }
1232
1233    #[test]
1234    fn test_interval_tighten_upper() {
1235        let i = Interval::closed(rat(1), rat(10));
1236        let tightened = i.tighten_upper(Bound::Finite(rat(6)), false);
1237        // Should become [1, 6]
1238        assert_eq!(tightened.lo, Bound::Finite(rat(1)));
1239        assert_eq!(tightened.hi, Bound::Finite(rat(6)));
1240        assert!(tightened.contains(&rat(6)));
1241        assert!(!tightened.contains(&rat(7)));
1242    }
1243
1244    #[test]
1245    fn test_interval_propagate_add() {
1246        // x + y = result, x = [1, 2], result = [5, 8]
1247        // Then y = result - x = [5, 8] - [1, 2] = [3, 7]
1248        let x = Interval::closed(rat(1), rat(2));
1249        let result = Interval::closed(rat(5), rat(8));
1250        let y = Interval::propagate_add(&x, &result);
1251        assert!(y.contains(&rat(3)));
1252        assert!(y.contains(&rat(7)));
1253        assert!(!y.contains(&rat(2)));
1254        assert!(!y.contains(&rat(8)));
1255    }
1256
1257    #[test]
1258    fn test_interval_propagate_sub() {
1259        // x - y = result, x = [10, 15], result = [2, 5]
1260        // Then y = x - result = [10, 15] - [2, 5] = [5, 13]
1261        let x = Interval::closed(rat(10), rat(15));
1262        let result = Interval::closed(rat(2), rat(5));
1263        let y = Interval::propagate_sub_left(&x, &result);
1264        assert!(y.contains(&rat(5)));
1265        assert!(y.contains(&rat(13)));
1266    }
1267
1268    #[test]
1269    fn test_interval_propagate_mul() {
1270        // x * y = result, x = [2, 3], result = [10, 18]
1271        // Then y = result / x = [10, 18] / [2, 3]
1272        let x = Interval::closed(rat(2), rat(3));
1273        let result = Interval::closed(rat(10), rat(18));
1274        let y = Interval::propagate_mul(&x, &result).expect("Propagation should succeed");
1275        // y should be roughly [10/3, 9] = [3.33..., 9]
1276        assert!(y.contains(&rat(5)));
1277    }
1278
1279    #[test]
1280    fn test_interval_is_subset_of() {
1281        let a = Interval::closed(rat(2), rat(5));
1282        let b = Interval::closed(rat(1), rat(10));
1283        assert!(a.is_subset_of(&b));
1284        assert!(!b.is_subset_of(&a));
1285
1286        let c = Interval::closed(rat(1), rat(10));
1287        assert!(c.is_subset_of(&c));
1288    }
1289
1290    #[test]
1291    fn test_interval_overlaps() {
1292        let a = Interval::closed(rat(1), rat(5));
1293        let b = Interval::closed(rat(3), rat(7));
1294        assert!(a.overlaps(&b));
1295        assert!(b.overlaps(&a));
1296
1297        let c = Interval::closed(rat(10), rat(15));
1298        assert!(!a.overlaps(&c));
1299    }
1300
1301    #[test]
1302    fn test_interval_widen() {
1303        let i = Interval::closed(rat(5), rat(10));
1304        let widened = i.widen(&rat(2));
1305        // Should become [3, 12]
1306        assert_eq!(widened.lo, Bound::Finite(rat(3)));
1307        assert_eq!(widened.hi, Bound::Finite(rat(12)));
1308        assert!(widened.contains(&rat(3)));
1309        assert!(widened.contains(&rat(12)));
1310        assert!(!widened.contains(&rat(2)));
1311        assert!(!widened.contains(&rat(13)));
1312    }
1313}