Skip to main content

cadcore_math/
interval.rs

1//! Closed interval `[lo, hi]` on the real line.
2
3use std::fmt;
4use crate::EPS;
5
6/// A closed real interval `[lo, hi]`.
7#[derive(Clone, Copy, PartialEq)]
8pub struct Interval {
9    /// Lower bound.
10    pub lo: f64,
11    /// Upper bound.
12    pub hi: f64,
13}
14
15impl Interval {
16    /// The empty interval (lo > hi).
17    pub const EMPTY: Self = Self { lo: f64::INFINITY, hi: f64::NEG_INFINITY };
18
19    /// The whole real line.
20    pub const UNIVERSE: Self = Self { lo: f64::NEG_INFINITY, hi: f64::INFINITY };
21
22    /// Construct `[lo, hi]`.  Panics in debug if `lo > hi`.
23    #[inline]
24    pub fn new(lo: f64, hi: f64) -> Self {
25        debug_assert!(lo <= hi, "Interval::new: lo ({lo}) > hi ({hi})");
26        Self { lo, hi }
27    }
28
29    /// Construct from any two values, sorting them.
30    #[inline]
31    pub fn from_unordered(a: f64, b: f64) -> Self {
32        Self { lo: a.min(b), hi: a.max(b) }
33    }
34
35    /// Smallest interval containing both `self` and `other`.
36    #[inline]
37    pub fn union(self, other: Self) -> Self {
38        Self { lo: self.lo.min(other.lo), hi: self.hi.max(other.hi) }
39    }
40
41    /// Intersection (may be empty).
42    #[inline]
43    pub fn intersect(self, other: Self) -> Self {
44        Self {
45            lo: self.lo.max(other.lo),
46            hi: self.hi.min(other.hi),
47        }
48    }
49
50    /// Length of the interval.
51    #[inline]
52    pub fn length(self) -> f64 { (self.hi - self.lo).max(0.0) }
53
54    /// `true` if the interval contains no points.
55    #[inline]
56    pub fn is_empty(self) -> bool { self.lo > self.hi + EPS }
57
58    /// `true` if `v` is inside (inclusive with tolerance).
59    #[inline]
60    pub fn contains(self, v: f64) -> bool { v >= self.lo - EPS && v <= self.hi + EPS }
61
62    /// Clamp `v` to the interval.
63    #[inline]
64    pub fn clamp(self, v: f64) -> f64 { v.max(self.lo).min(self.hi) }
65
66    /// Midpoint.
67    #[inline]
68    pub fn midpoint(self) -> f64 { 0.5 * (self.lo + self.hi) }
69
70    /// Expand both ends by `margin`.
71    #[inline]
72    pub fn expand(self, margin: f64) -> Self { Self::new(self.lo - margin, self.hi + margin) }
73}
74
75impl fmt::Debug for Interval {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(f, "[{:.6}, {:.6}]", self.lo, self.hi)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn union_span() {
87        let a = Interval::new(0.0, 3.0);
88        let b = Interval::new(2.0, 5.0);
89        let u = a.union(b);
90        assert_eq!(u.lo, 0.0);
91        assert_eq!(u.hi, 5.0);
92    }
93
94    #[test]
95    fn intersection_empty() {
96        let a = Interval::new(0.0, 1.0);
97        let b = Interval::new(2.0, 3.0);
98        assert!(a.intersect(b).is_empty());
99    }
100}