Skip to main content

cadcore_math/
interval.rs

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