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        // Multiple corners can tie at the same extremal *value* while
475        // disagreeing on whether it is attained. The classic example is a
476        // factor pinned at exactly 0: `0 * y = 0` is attained for every `y`
477        // regardless of the openness of the *other* corner's bound, so if
478        // any tied corner is closed, the extremum is attained (closed) even
479        // though another corner produces the same numeric value only as an
480        // unattained limit. Picking an arbitrary tied corner (e.g. the first
481        // after sorting) can therefore mark an attainable value like 0 as
482        // open, excluding it from the result interval.
483        let (lo, lo_open) = {
484            let min_val = bounds[0].0.clone();
485            let is_open = bounds
486                .iter()
487                .take_while(|(b, _)| b.cmp_bound(&min_val) == Ordering::Equal)
488                .all(|(_, open)| *open);
489            (min_val, is_open)
490        };
491        let (hi, hi_open) = {
492            let max_val = bounds[bounds.len() - 1].0.clone();
493            let is_open = bounds
494                .iter()
495                .rev()
496                .take_while(|(b, _)| b.cmp_bound(&max_val) == Ordering::Equal)
497                .all(|(_, open)| *open);
498            (max_val, is_open)
499        };
500
501        Interval {
502            lo,
503            hi,
504            lo_open,
505            hi_open,
506        }
507    }
508
509    /// Divide two intervals.
510    /// Returns None if division by zero occurs (divisor contains zero).
511    pub fn div(&self, other: &Interval) -> Option<Interval> {
512        if self.is_empty() || other.is_empty() {
513            return Some(Interval::empty());
514        }
515
516        // Check if divisor contains zero
517        if other.contains_zero() {
518            // Division by zero is undefined
519            // In interval arithmetic, we need to split the interval or return extended intervals
520            // For simplicity, return None to indicate undefined
521            return None;
522        }
523
524        // Division: [a,b] / [c,d] = [a,b] * [1/d, 1/c]
525        // When c,d don't contain zero, we compute reciprocals and multiply
526
527        // Compute reciprocal of other
528        let recip_lo = match &other.hi {
529            Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
530            Bound::Finite(r) => {
531                if r.is_zero() {
532                    return None; // Contains zero
533                }
534                Bound::Finite(BigRational::one() / r)
535            }
536        };
537
538        let recip_hi = match &other.lo {
539            Bound::NegInf | Bound::PosInf => Bound::Finite(BigRational::zero()),
540            Bound::Finite(r) => {
541                if r.is_zero() {
542                    return None; // Contains zero
543                }
544                Bound::Finite(BigRational::one() / r)
545            }
546        };
547
548        let recip = Interval {
549            lo: recip_lo,
550            hi: recip_hi,
551            lo_open: other.hi_open,
552            hi_open: other.lo_open,
553        };
554
555        Some(self.mul(&recip))
556    }
557
558    /// Compute the intersection of two intervals.
559    pub fn intersect(&self, other: &Interval) -> Interval {
560        if self.is_empty() || other.is_empty() {
561            return Interval::empty();
562        }
563
564        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
565            Ordering::Less => (other.lo.clone(), other.lo_open),
566            Ordering::Greater => (self.lo.clone(), self.lo_open),
567            Ordering::Equal => (self.lo.clone(), self.lo_open || other.lo_open),
568        };
569
570        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
571            Ordering::Less => (self.hi.clone(), self.hi_open),
572            Ordering::Greater => (other.hi.clone(), other.hi_open),
573            Ordering::Equal => (self.hi.clone(), self.hi_open || other.hi_open),
574        };
575
576        Interval {
577            lo,
578            hi,
579            lo_open,
580            hi_open,
581        }
582    }
583
584    /// Compute the union of two intervals (if contiguous).
585    /// Returns None if the intervals are not contiguous.
586    pub fn union(&self, other: &Interval) -> Option<Interval> {
587        if self.is_empty() {
588            return Some(other.clone());
589        }
590        if other.is_empty() {
591            return Some(self.clone());
592        }
593
594        // Check if intervals overlap or are adjacent
595        let intersects = !self.intersect(other).is_empty();
596        let adjacent_left = self.hi == other.lo && (!self.hi_open || !other.lo_open);
597        let adjacent_right = other.hi == self.lo && (!other.hi_open || !self.lo_open);
598
599        if !intersects && !adjacent_left && !adjacent_right {
600            return None;
601        }
602
603        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
604            Ordering::Less => (self.lo.clone(), self.lo_open),
605            Ordering::Greater => (other.lo.clone(), other.lo_open),
606            Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
607        };
608
609        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
610            Ordering::Greater => (self.hi.clone(), self.hi_open),
611            Ordering::Less => (other.hi.clone(), other.hi_open),
612            Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
613        };
614
615        Some(Interval {
616            lo,
617            hi,
618            lo_open,
619            hi_open,
620        })
621    }
622
623    /// Compute the power of an interval.
624    pub fn pow(&self, n: u32) -> Interval {
625        if self.is_empty() {
626            return Interval::empty();
627        }
628        if n == 0 {
629            return Interval::point(BigRational::one());
630        }
631        if n == 1 {
632            return self.clone();
633        }
634
635        if n.is_multiple_of(2) {
636            // Even power: result is non-negative
637            // [a,b]^n depends on whether interval contains 0
638            if self.contains_zero() {
639                // [min(|a|,|b|)^n, max(|a|,|b|)^n] but includes 0
640                let lo_abs = match &self.lo {
641                    Bound::Finite(r) => r.abs(),
642                    Bound::NegInf => return Interval::at_least(BigRational::zero()),
643                    Bound::PosInf => unreachable!(),
644                };
645                let hi_abs = match &self.hi {
646                    Bound::Finite(r) => r.abs(),
647                    Bound::PosInf => return Interval::at_least(BigRational::zero()),
648                    Bound::NegInf => unreachable!(),
649                };
650                let max_abs = lo_abs.max(hi_abs);
651                Interval {
652                    lo: Bound::Finite(BigRational::zero()),
653                    hi: Bound::Finite(pow_rational(&max_abs, n)),
654                    lo_open: false,
655                    hi_open: self.lo_open && self.hi_open,
656                }
657            } else if self.is_positive() {
658                // All positive: [a^n, b^n]
659                let lo_val = match &self.lo {
660                    Bound::Finite(r) => pow_rational(r, n),
661                    _ => return Interval::at_least(BigRational::zero()),
662                };
663                let hi_val = match &self.hi {
664                    Bound::Finite(r) => pow_rational(r, n),
665                    _ => return Interval::at_least(lo_val),
666                };
667                Interval {
668                    lo: Bound::Finite(lo_val),
669                    hi: Bound::Finite(hi_val),
670                    lo_open: self.lo_open,
671                    hi_open: self.hi_open,
672                }
673            } else {
674                // All negative: [b^n, a^n]
675                let lo_val = match &self.hi {
676                    Bound::Finite(r) => pow_rational(r, n),
677                    _ => return Interval::at_least(BigRational::zero()),
678                };
679                let hi_val = match &self.lo {
680                    Bound::Finite(r) => pow_rational(r, n),
681                    _ => return Interval::at_least(lo_val),
682                };
683                Interval {
684                    lo: Bound::Finite(lo_val),
685                    hi: Bound::Finite(hi_val),
686                    lo_open: self.hi_open,
687                    hi_open: self.lo_open,
688                }
689            }
690        } else {
691            // Odd power: preserves sign
692            // [a^n, b^n]
693            let lo_val = match &self.lo {
694                Bound::NegInf => Bound::NegInf,
695                Bound::PosInf => Bound::PosInf,
696                Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
697            };
698            let hi_val = match &self.hi {
699                Bound::NegInf => Bound::NegInf,
700                Bound::PosInf => Bound::PosInf,
701                Bound::Finite(r) => Bound::Finite(pow_rational(r, n)),
702            };
703            Interval {
704                lo: lo_val,
705                hi: hi_val,
706                lo_open: self.lo_open,
707                hi_open: self.hi_open,
708            }
709        }
710    }
711
712    /// Get the midpoint of the interval (if bounded).
713    pub fn midpoint(&self) -> Option<BigRational> {
714        match (&self.lo, &self.hi) {
715            (Bound::Finite(a), Bound::Finite(b)) => {
716                Some((a + b) / BigRational::from_integer(BigInt::from(2)))
717            }
718            _ => None,
719        }
720    }
721
722    /// Get the width of the interval (if bounded).
723    pub fn width(&self) -> Option<BigRational> {
724        match (&self.lo, &self.hi) {
725            (Bound::Finite(a), Bound::Finite(b)) => Some(b - a),
726            _ => None,
727        }
728    }
729
730    /// Split the interval at a point.
731    pub fn split(&self, at: &BigRational) -> (Interval, Interval) {
732        if !self.contains(at) {
733            return (self.clone(), Interval::empty());
734        }
735
736        let left = Interval {
737            lo: self.lo.clone(),
738            hi: Bound::Finite(at.clone()),
739            lo_open: self.lo_open,
740            hi_open: false,
741        };
742
743        let right = Interval {
744            lo: Bound::Finite(at.clone()),
745            hi: self.hi.clone(),
746            lo_open: false,
747            hi_open: self.hi_open,
748        };
749
750        (left, right)
751    }
752
753    /// Convex hull of two intervals (always returns a single interval).
754    /// This is the smallest interval containing both inputs.
755    pub fn hull(&self, other: &Interval) -> Interval {
756        if self.is_empty() {
757            return other.clone();
758        }
759        if other.is_empty() {
760            return self.clone();
761        }
762
763        let (lo, lo_open) = match self.lo.cmp_bound(&other.lo) {
764            Ordering::Less => (self.lo.clone(), self.lo_open),
765            Ordering::Greater => (other.lo.clone(), other.lo_open),
766            Ordering::Equal => (self.lo.clone(), self.lo_open && other.lo_open),
767        };
768
769        let (hi, hi_open) = match self.hi.cmp_bound(&other.hi) {
770            Ordering::Greater => (self.hi.clone(), self.hi_open),
771            Ordering::Less => (other.hi.clone(), other.hi_open),
772            Ordering::Equal => (self.hi.clone(), self.hi_open && other.hi_open),
773        };
774
775        Interval {
776            lo,
777            hi,
778            lo_open,
779            hi_open,
780        }
781    }
782
783    /// Tighten lower bound to be at least the given value.
784    /// Returns a new interval with the tightened bound, or empty if inconsistent.
785    pub fn tighten_lower(&self, new_lo: Bound, new_lo_open: bool) -> Interval {
786        if self.is_empty() {
787            return Interval::empty();
788        }
789
790        let (lo, lo_open) = match self.lo.cmp_bound(&new_lo) {
791            Ordering::Less => (new_lo, new_lo_open),
792            Ordering::Greater => (self.lo.clone(), self.lo_open),
793            Ordering::Equal => (self.lo.clone(), self.lo_open || new_lo_open),
794        };
795
796        Interval {
797            lo,
798            hi: self.hi.clone(),
799            lo_open,
800            hi_open: self.hi_open,
801        }
802    }
803
804    /// Tighten upper bound to be at most the given value.
805    /// Returns a new interval with the tightened bound, or empty if inconsistent.
806    pub fn tighten_upper(&self, new_hi: Bound, new_hi_open: bool) -> Interval {
807        if self.is_empty() {
808            return Interval::empty();
809        }
810
811        let (hi, hi_open) = match self.hi.cmp_bound(&new_hi) {
812            Ordering::Greater => (new_hi, new_hi_open),
813            Ordering::Less => (self.hi.clone(), self.hi_open),
814            Ordering::Equal => (self.hi.clone(), self.hi_open || new_hi_open),
815        };
816
817        Interval {
818            lo: self.lo.clone(),
819            hi,
820            lo_open: self.lo_open,
821            hi_open,
822        }
823    }
824
825    /// Propagate bounds from constraint: x + y ∈ result_interval.
826    /// Given x and result, tighten y's bounds.
827    /// Returns the tightened interval for y.
828    pub fn propagate_add(x: &Interval, result: &Interval) -> Interval {
829        // y ∈ result - x
830        result.sub(x)
831    }
832
833    /// Propagate bounds from constraint: x - y ∈ result_interval.
834    /// Given x and result, tighten y's bounds.
835    /// Returns the tightened interval for y.
836    pub fn propagate_sub_left(x: &Interval, result: &Interval) -> Interval {
837        // x - y = result => y = x - result
838        x.sub(result)
839    }
840
841    /// Propagate bounds from constraint: x - y ∈ result_interval.
842    /// Given y and result, tighten x's bounds.
843    /// Returns the tightened interval for x.
844    pub fn propagate_sub_right(y: &Interval, result: &Interval) -> Interval {
845        // x - y = result => x = result + y
846        result.add(y)
847    }
848
849    /// Propagate bounds from constraint: x * y ∈ result_interval.
850    /// Given x and result, tighten y's bounds (if x doesn't contain 0).
851    /// Returns None if x contains 0, otherwise returns the tightened interval for y.
852    pub fn propagate_mul(x: &Interval, result: &Interval) -> Option<Interval> {
853        // y ∈ result / x
854        Interval::div(result, x)
855    }
856
857    /// Propagate bounds from constraint: x / y ∈ result_interval.
858    /// Given x and result, tighten y's bounds (if result doesn't contain 0).
859    /// Returns None if propagation is impossible, otherwise returns the tightened interval for y.
860    pub fn propagate_div_left(x: &Interval, result: &Interval) -> Option<Interval> {
861        // x / y = result => y = x / result
862        Interval::div(x, result)
863    }
864
865    /// Propagate bounds from constraint: x / y ∈ result_interval.
866    /// Given y and result, tighten x's bounds.
867    /// Returns the tightened interval for x.
868    pub fn propagate_div_right(y: &Interval, result: &Interval) -> Interval {
869        // x / y = result => x = result * y
870        result.mul(y)
871    }
872
873    /// Check if this interval is a subset of another interval.
874    pub fn is_subset_of(&self, other: &Interval) -> bool {
875        if self.is_empty() {
876            return true;
877        }
878        if other.is_empty() {
879            return false;
880        }
881
882        // Check lower bound
883        let lo_ok = match self.lo.cmp_bound(&other.lo) {
884            Ordering::Less => false,
885            Ordering::Greater => true,
886            Ordering::Equal => !self.lo_open || other.lo_open,
887        };
888
889        // Check upper bound
890        let hi_ok = match self.hi.cmp_bound(&other.hi) {
891            Ordering::Greater => false,
892            Ordering::Less => true,
893            Ordering::Equal => !self.hi_open || other.hi_open,
894        };
895
896        lo_ok && hi_ok
897    }
898
899    /// Check if two intervals overlap (have non-empty intersection).
900    pub fn overlaps(&self, other: &Interval) -> bool {
901        !self.intersect(other).is_empty()
902    }
903
904    /// Widen the interval by a given amount on each side.
905    pub fn widen(&self, amount: &BigRational) -> Interval {
906        if self.is_empty() {
907            return Interval::empty();
908        }
909
910        let lo = match &self.lo {
911            Bound::Finite(r) => Bound::Finite(r - amount),
912            bound => bound.clone(),
913        };
914
915        let hi = match &self.hi {
916            Bound::Finite(r) => Bound::Finite(r + amount),
917            bound => bound.clone(),
918        };
919
920        Interval {
921            lo,
922            hi,
923            lo_open: self.lo_open,
924            hi_open: self.hi_open,
925        }
926    }
927}
928
929/// Compute r^n for a rational number.
930fn pow_rational(r: &BigRational, n: u32) -> BigRational {
931    if n == 0 {
932        return BigRational::one();
933    }
934
935    let mut result = BigRational::one();
936    let mut base = r.clone();
937    let mut exp = n;
938
939    while exp > 0 {
940        if exp & 1 == 1 {
941            result = &result * &base;
942        }
943        base = &base * &base;
944        exp >>= 1;
945    }
946
947    result
948}
949
950impl fmt::Display for Interval {
951    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
952        if self.is_empty() {
953            write!(f, "∅")
954        } else {
955            let lo_bracket = if self.lo_open { '(' } else { '[' };
956            let hi_bracket = if self.hi_open { ')' } else { ']' };
957            write!(f, "{}{}, {}{}", lo_bracket, self.lo, self.hi, hi_bracket)
958        }
959    }
960}
961
962impl Default for Interval {
963    fn default() -> Self {
964        Self::reals()
965    }
966}
967
968impl Neg for Interval {
969    type Output = Interval;
970
971    fn neg(self) -> Self::Output {
972        self.negate()
973    }
974}
975
976impl Neg for &Interval {
977    type Output = Interval;
978
979    fn neg(self) -> Self::Output {
980        self.negate()
981    }
982}
983
984impl Add for Interval {
985    type Output = Interval;
986
987    fn add(self, rhs: Self) -> Self::Output {
988        Interval::add(&self, &rhs)
989    }
990}
991
992impl Add<&Interval> for &Interval {
993    type Output = Interval;
994
995    fn add(self, rhs: &Interval) -> Self::Output {
996        Interval::add(self, rhs)
997    }
998}
999
1000impl Sub for Interval {
1001    type Output = Interval;
1002
1003    fn sub(self, rhs: Self) -> Self::Output {
1004        Interval::sub(&self, &rhs)
1005    }
1006}
1007
1008impl Sub<&Interval> for &Interval {
1009    type Output = Interval;
1010
1011    fn sub(self, rhs: &Interval) -> Self::Output {
1012        Interval::sub(self, rhs)
1013    }
1014}
1015
1016impl Mul for Interval {
1017    type Output = Interval;
1018
1019    fn mul(self, rhs: Self) -> Self::Output {
1020        Interval::mul(&self, &rhs)
1021    }
1022}
1023
1024impl Mul<&Interval> for &Interval {
1025    type Output = Interval;
1026
1027    fn mul(self, rhs: &Interval) -> Self::Output {
1028        Interval::mul(self, rhs)
1029    }
1030}
1031
1032impl Div for Interval {
1033    type Output = Option<Interval>;
1034
1035    fn div(self, rhs: Self) -> Self::Output {
1036        Interval::div(&self, &rhs)
1037    }
1038}
1039
1040impl Div<&Interval> for &Interval {
1041    type Output = Option<Interval>;
1042
1043    fn div(self, rhs: &Interval) -> Self::Output {
1044        Interval::div(self, rhs)
1045    }
1046}
1047
1048#[cfg(test)]
1049mod tests {
1050    use super::*;
1051
1052    fn rat(n: i64) -> BigRational {
1053        BigRational::from_integer(BigInt::from(n))
1054    }
1055
1056    #[test]
1057    fn test_interval_empty() {
1058        let i = Interval::empty();
1059        assert!(i.is_empty());
1060        assert!(!i.contains(&rat(0)));
1061    }
1062
1063    #[test]
1064    fn test_interval_point() {
1065        let i = Interval::point(rat(5));
1066        assert!(i.is_point());
1067        assert!(i.contains(&rat(5)));
1068        assert!(!i.contains(&rat(4)));
1069        assert!(i.is_positive());
1070    }
1071
1072    #[test]
1073    fn test_interval_closed() {
1074        let i = Interval::closed(rat(1), rat(5));
1075        assert!(i.contains(&rat(1)));
1076        assert!(i.contains(&rat(3)));
1077        assert!(i.contains(&rat(5)));
1078        assert!(!i.contains(&rat(0)));
1079        assert!(!i.contains(&rat(6)));
1080    }
1081
1082    #[test]
1083    fn test_interval_open() {
1084        let i = Interval::open(rat(1), rat(5));
1085        assert!(!i.contains(&rat(1)));
1086        assert!(i.contains(&rat(3)));
1087        assert!(!i.contains(&rat(5)));
1088    }
1089
1090    #[test]
1091    fn test_interval_add() {
1092        let a = Interval::closed(rat(1), rat(3));
1093        let b = Interval::closed(rat(2), rat(4));
1094        let c = Interval::add(&a, &b);
1095        assert!(c.contains(&rat(3)));
1096        assert!(c.contains(&rat(7)));
1097        assert!(!c.contains(&rat(2)));
1098        assert!(!c.contains(&rat(8)));
1099    }
1100
1101    #[test]
1102    fn test_interval_mul() {
1103        let a = Interval::closed(rat(2), rat(3));
1104        let b = Interval::closed(rat(4), rat(5));
1105        let c = Interval::mul(&a, &b);
1106        // [2,3] * [4,5] = [8, 15]
1107        assert!(c.contains(&rat(8)));
1108        assert!(c.contains(&rat(15)));
1109        assert!(!c.contains(&rat(7)));
1110        assert!(!c.contains(&rat(16)));
1111    }
1112
1113    #[test]
1114    fn test_interval_mul_mixed_signs() {
1115        let a = Interval::closed(rat(-2), rat(3));
1116        let b = Interval::closed(rat(-1), rat(2));
1117        let c = Interval::mul(&a, &b);
1118        // Products are: -2*-1=2, -2*2=-4, 3*-1=-3, 3*2=6
1119        // So result is [-4, 6]
1120        assert!(c.contains(&rat(-4)));
1121        assert!(c.contains(&rat(6)));
1122        assert!(c.contains(&rat(0)));
1123        assert!(!c.contains(&rat(-5)));
1124        assert!(!c.contains(&rat(7)));
1125    }
1126
1127    #[test]
1128    fn test_interval_negate() {
1129        let a = Interval::closed(rat(1), rat(5));
1130        let b = a.negate();
1131        assert!(b.contains(&rat(-5)));
1132        assert!(b.contains(&rat(-1)));
1133        assert!(!b.contains(&rat(0)));
1134    }
1135
1136    #[test]
1137    fn test_interval_intersect() {
1138        let a = Interval::closed(rat(1), rat(5));
1139        let b = Interval::closed(rat(3), rat(7));
1140        let c = a.intersect(&b);
1141        // [1,5] ∩ [3,7] = [3,5]
1142        assert!(c.contains(&rat(3)));
1143        assert!(c.contains(&rat(5)));
1144        assert!(!c.contains(&rat(2)));
1145        assert!(!c.contains(&rat(6)));
1146    }
1147
1148    #[test]
1149    fn test_interval_pow_even() {
1150        let a = Interval::closed(rat(-2), rat(3));
1151        let b = a.pow(2);
1152        // [-2,3]^2 = [0, 9]
1153        assert!(b.contains(&rat(0)));
1154        assert!(b.contains(&rat(9)));
1155        assert!(!b.contains(&rat(-1)));
1156        assert!(!b.contains(&rat(10)));
1157    }
1158
1159    #[test]
1160    fn test_interval_pow_odd() {
1161        let a = Interval::closed(rat(-2), rat(3));
1162        let b = a.pow(3);
1163        // [-2,3]^3 = [-8, 27]
1164        assert!(b.contains(&rat(-8)));
1165        assert!(b.contains(&rat(27)));
1166        assert!(!b.contains(&rat(-9)));
1167        assert!(!b.contains(&rat(28)));
1168    }
1169
1170    #[test]
1171    fn test_interval_sign() {
1172        assert_eq!(Interval::closed(rat(1), rat(5)).sign(), Some(1));
1173        assert_eq!(Interval::closed(rat(-5), rat(-1)).sign(), Some(-1));
1174        assert_eq!(Interval::closed(rat(-1), rat(1)).sign(), None);
1175        assert_eq!(Interval::point(rat(0)).sign(), Some(0));
1176    }
1177
1178    #[test]
1179    fn test_interval_unbounded() {
1180        let a = Interval::at_least(rat(0));
1181        assert!(a.is_non_negative());
1182        assert!(a.contains(&rat(0)));
1183        assert!(a.contains(&rat(1000)));
1184        assert!(!a.contains(&rat(-1)));
1185
1186        let b = Interval::less_than(rat(0));
1187        assert!(b.is_negative());
1188        assert!(!b.contains(&rat(0)));
1189        assert!(b.contains(&rat(-1)));
1190    }
1191
1192    #[test]
1193    fn test_interval_midpoint() {
1194        let i = Interval::closed(rat(2), rat(8));
1195        assert_eq!(i.midpoint(), Some(rat(5)));
1196    }
1197
1198    #[test]
1199    fn test_interval_div() {
1200        // [4, 12] / [2, 3] = [4/3, 6]
1201        let a = Interval::closed(rat(4), rat(12));
1202        let b = Interval::closed(rat(2), rat(3));
1203        let c = Interval::div(&a, &b).expect("Division should succeed");
1204        // 4/3 ≈ 1.333..., 12/2 = 6
1205        assert!(c.contains(&BigRational::new(BigInt::from(4), BigInt::from(3))));
1206        assert!(c.contains(&rat(6)));
1207    }
1208
1209    #[test]
1210    fn test_interval_div_by_zero() {
1211        // Division by interval containing zero should return None
1212        let a = Interval::closed(rat(1), rat(2));
1213        let b = Interval::closed(rat(-1), rat(1)); // Contains zero
1214        assert!(Interval::div(&a, &b).is_none());
1215    }
1216
1217    #[test]
1218    fn test_interval_div_positive() {
1219        // [6, 12] / [2, 3] = [2, 6]
1220        let a = Interval::closed(rat(6), rat(12));
1221        let b = Interval::closed(rat(2), rat(3));
1222        let c = Interval::div(&a, &b).expect("Division should succeed");
1223        assert!(c.contains(&rat(2)));
1224        assert!(c.contains(&rat(6)));
1225    }
1226
1227    #[test]
1228    fn test_interval_hull() {
1229        let a = Interval::closed(rat(1), rat(3));
1230        let b = Interval::closed(rat(5), rat(7));
1231        let hull = a.hull(&b);
1232        // Hull should be [1, 7]
1233        assert_eq!(hull.lo, Bound::Finite(rat(1)));
1234        assert_eq!(hull.hi, Bound::Finite(rat(7)));
1235        assert!(hull.contains(&rat(1)));
1236        assert!(hull.contains(&rat(4)));
1237        assert!(hull.contains(&rat(7)));
1238    }
1239
1240    #[test]
1241    fn test_interval_tighten_lower() {
1242        let i = Interval::closed(rat(1), rat(10));
1243        let tightened = i.tighten_lower(Bound::Finite(rat(5)), false);
1244        // Should become [5, 10]
1245        assert_eq!(tightened.lo, Bound::Finite(rat(5)));
1246        assert_eq!(tightened.hi, Bound::Finite(rat(10)));
1247        assert!(!tightened.contains(&rat(4)));
1248        assert!(tightened.contains(&rat(5)));
1249    }
1250
1251    #[test]
1252    fn test_interval_tighten_upper() {
1253        let i = Interval::closed(rat(1), rat(10));
1254        let tightened = i.tighten_upper(Bound::Finite(rat(6)), false);
1255        // Should become [1, 6]
1256        assert_eq!(tightened.lo, Bound::Finite(rat(1)));
1257        assert_eq!(tightened.hi, Bound::Finite(rat(6)));
1258        assert!(tightened.contains(&rat(6)));
1259        assert!(!tightened.contains(&rat(7)));
1260    }
1261
1262    #[test]
1263    fn test_interval_propagate_add() {
1264        // x + y = result, x = [1, 2], result = [5, 8]
1265        // Then y = result - x = [5, 8] - [1, 2] = [3, 7]
1266        let x = Interval::closed(rat(1), rat(2));
1267        let result = Interval::closed(rat(5), rat(8));
1268        let y = Interval::propagate_add(&x, &result);
1269        assert!(y.contains(&rat(3)));
1270        assert!(y.contains(&rat(7)));
1271        assert!(!y.contains(&rat(2)));
1272        assert!(!y.contains(&rat(8)));
1273    }
1274
1275    #[test]
1276    fn test_interval_propagate_sub() {
1277        // x - y = result, x = [10, 15], result = [2, 5]
1278        // Then y = x - result = [10, 15] - [2, 5] = [5, 13]
1279        let x = Interval::closed(rat(10), rat(15));
1280        let result = Interval::closed(rat(2), rat(5));
1281        let y = Interval::propagate_sub_left(&x, &result);
1282        assert!(y.contains(&rat(5)));
1283        assert!(y.contains(&rat(13)));
1284    }
1285
1286    #[test]
1287    fn test_interval_propagate_mul() {
1288        // x * y = result, x = [2, 3], result = [10, 18]
1289        // Then y = result / x = [10, 18] / [2, 3]
1290        let x = Interval::closed(rat(2), rat(3));
1291        let result = Interval::closed(rat(10), rat(18));
1292        let y = Interval::propagate_mul(&x, &result).expect("Propagation should succeed");
1293        // y should be roughly [10/3, 9] = [3.33..., 9]
1294        assert!(y.contains(&rat(5)));
1295    }
1296
1297    #[test]
1298    fn test_interval_is_subset_of() {
1299        let a = Interval::closed(rat(2), rat(5));
1300        let b = Interval::closed(rat(1), rat(10));
1301        assert!(a.is_subset_of(&b));
1302        assert!(!b.is_subset_of(&a));
1303
1304        let c = Interval::closed(rat(1), rat(10));
1305        assert!(c.is_subset_of(&c));
1306    }
1307
1308    #[test]
1309    fn test_interval_overlaps() {
1310        let a = Interval::closed(rat(1), rat(5));
1311        let b = Interval::closed(rat(3), rat(7));
1312        assert!(a.overlaps(&b));
1313        assert!(b.overlaps(&a));
1314
1315        let c = Interval::closed(rat(10), rat(15));
1316        assert!(!a.overlaps(&c));
1317    }
1318
1319    #[test]
1320    fn test_interval_widen() {
1321        let i = Interval::closed(rat(5), rat(10));
1322        let widened = i.widen(&rat(2));
1323        // Should become [3, 12]
1324        assert_eq!(widened.lo, Bound::Finite(rat(3)));
1325        assert_eq!(widened.hi, Bound::Finite(rat(12)));
1326        assert!(widened.contains(&rat(3)));
1327        assert!(widened.contains(&rat(12)));
1328        assert!(!widened.contains(&rat(2)));
1329        assert!(!widened.contains(&rat(13)));
1330    }
1331}