generic_interval/
lib.rs

1// Copyright (c) 2024-2025 Ken Barker
2
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation the
6// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7// sell copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! # generic-interval
22//!
23//! [![crates.io](https://img.shields.io/crates/v/generic-interval.svg)](https://crates.io/crates/generic-interval)
24//! [![docs.io](https://docs.rs/generic-interval/badge.svg)](https://docs.rs/generic-interval/)
25//! [![License](https://img.shields.io/badge/License-MIT-blue)](https://opensource.org/license/mit/)
26//! [![Rust](https://github.com/kenba/interval-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/kenba/interval-rs/actions)
27//! [![codecov](https://codecov.io/gh/kenba/interval-rs/graph/badge.svg?token=O271HGMYY5)](https://codecov.io/gh/kenba/interval-rs)
28//!
29//! A generic closed interval library.
30//!
31//! An interval is a pair of numbers which represents all the numbers between them.\
32//! Intervals are considered closed so the bounds are included.\
33//! Intervals are written [a, b] to represent all the numbers between a and b
34//! inclusive, a ≤ b.
35//!
36//! The library is designed to be used with any types that implement the
37//! `Copy` and `PartialOrd` traits including the floating point types:
38//! `f64` and `f32` and arithmetic types in
39//! [new types](https://doc.rust-lang.org/rust-by-example/generics/new_types.html).
40//!
41//! The library is declared [no_std](https://docs.rust-embedded.org/book/intro/no-std.html)
42//! so it can be used in embedded applications.
43//!
44//! ## Examples
45//!
46//! ```
47//! use generic_interval::{Interval, hull, intersection, overlap, overlaps};
48//!
49//! // An example new-type based on f64
50//! #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
51//! pub struct Metres(pub f64);
52//!
53//! let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
54//! let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
55//!
56//! // Note: the hull includes 4.0-6.0
57//! let result = hull(a, b);
58//! assert_eq!(Metres(1.0), result.lower());
59//! assert_eq!(Metres(9.0), result.upper());
60//!
61//! // Note: overlap may return an empty interval
62//! // while overlaps is the same as intersection
63//! let result = intersection(a, b);
64//! assert!(result.is_none());
65//! let result = overlaps(a, b);
66//! assert!(result.is_none());
67//! let result = overlap(a, b);
68//! assert!(result.is_empty());
69//!
70//! let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
71//! let result = intersection(a, c).unwrap();
72//! assert_eq!(Metres(4.0), result.lower());
73//! assert_eq!(Metres(4.0), result.upper());
74//! ```
75//!
76//! ## License
77//!
78//! `generic-interval` is provided under a MIT license, see [LICENSE](../LICENSE).
79
80#![cfg_attr(not(test), no_std)]
81
82use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
83use num_traits::{Num, NumCast};
84use serde::{Deserialize, Serialize};
85
86/// Return the minimum of two values.
87///
88/// * `a`, `b` the values.
89///
90/// returns the minimum of two values.
91#[must_use]
92pub fn min<T>(a: T, b: T) -> T
93where
94    T: Copy + PartialOrd,
95{
96    if b < a { b } else { a }
97}
98
99/// Return the maximum of two values.
100///
101/// * `a`, `b` the values.
102///
103/// returns the maximum of two values.
104#[must_use]
105pub fn max<T>(a: T, b: T) -> T
106where
107    T: Copy + PartialOrd,
108{
109    if b < a { a } else { b }
110}
111
112/// A closed interval (endpoints included).
113#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
114pub struct Interval<T> {
115    lower: T,
116    upper: T,
117}
118
119impl<T: Copy + PartialOrd> Interval<T> {
120    #[must_use]
121    pub const fn new(lower: T, upper: T) -> Self {
122        Self { lower, upper }
123    }
124
125    #[must_use]
126    pub const fn lower(&self) -> T {
127        self.lower
128    }
129
130    pub const fn set_lower(&mut self, lower: T) {
131        self.lower = lower;
132    }
133
134    #[must_use]
135    pub const fn upper(&self) -> T {
136        self.upper
137    }
138
139    pub const fn set_upper(&mut self, upper: T) {
140        self.upper = upper;
141    }
142
143    #[must_use]
144    pub fn is_empty(&self) -> bool {
145        self.upper.lt(&self.lower)
146    }
147
148    /// Return whether the value is inside the `Interval` inclusively.
149    ///
150    /// * `value` the value.
151    ///
152    /// returns true if lower <= value <= upper.
153    #[must_use]
154    pub fn is_inside(&self, value: &T) -> bool {
155        self.lower.le(value) && self.upper.ge(value)
156    }
157
158    /// Return whether the value is inside the `Interval`.
159    ///
160    /// * `value` the value.
161    ///
162    /// returns true if lower <= value < upper.
163    #[must_use]
164    pub fn is_within(&self, value: &T) -> bool {
165        self.lower.le(value) && self.upper.gt(value)
166    }
167}
168
169impl<T: Copy + PartialOrd + Sub<Output = T>> Interval<T> {
170    #[must_use]
171    pub fn width(&self) -> T {
172        self.upper - self.lower
173    }
174}
175
176impl<T: Default> Default for Interval<T> {
177    fn default() -> Self {
178        Self {
179            lower: T::default(),
180            upper: T::default(),
181        }
182    }
183}
184
185impl<
186    T: Num
187        + NumCast
188        + Copy
189        + PartialOrd
190        + Add<Output = T>
191        + Div<Output = T>
192        + Mul<Output = T>
193        + Sub<Output = T>,
194> Interval<T>
195{
196    #[allow(clippy::missing_panics_doc)]
197    #[must_use]
198    pub fn mean(&self) -> T {
199        let two: T = num_traits::cast(2).expect("Could not convert 2 to T");
200        self.lower + self.width() / two
201    }
202}
203
204impl<T: Copy + PartialOrd> TryFrom<(T, T)> for Interval<T> {
205    type Error = &'static str;
206
207    /// Attempt to create an Interval from a pair of values in lower, upper order.
208    ///
209    /// return a valid `Interval` or `Err` if upper < lower.
210    fn try_from(params: (T, T)) -> Result<Self, Self::Error> {
211        let v = Self {
212            lower: params.0,
213            upper: params.1,
214        };
215        if v.is_empty() {
216            Err("Invalid interval")
217        } else {
218            Ok(v)
219        }
220    }
221}
222
223impl<T: Add<Output = T>> Add for Interval<T> {
224    type Output = Self;
225
226    fn add(self, other: Self) -> Self::Output {
227        Self {
228            lower: self.lower + other.lower,
229            upper: self.upper + other.upper,
230        }
231    }
232}
233
234impl<T: Copy + Add<Output = T>> AddAssign for Interval<T> {
235    fn add_assign(&mut self, other: Self) {
236        *self = *self + other;
237    }
238}
239
240impl<T: Sub<Output = T>> Sub for Interval<T> {
241    type Output = Self;
242
243    fn sub(self, other: Self) -> Self::Output {
244        Self {
245            lower: self.lower - other.lower,
246            upper: self.upper - other.upper,
247        }
248    }
249}
250
251impl<T: Copy + Sub<Output = T>> SubAssign for Interval<T> {
252    fn sub_assign(&mut self, other: Self) {
253        *self = *self - other;
254    }
255}
256
257/// Calculate the overlap of the two `Interval`s.
258///
259/// * `a`, `b` the `Interval`s.
260///
261/// returns the overlap, i.e. the max lower and min upper.
262///
263/// # Examples
264/// ```
265/// use generic_interval::{Interval, overlap};
266///
267/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
268/// pub struct Metres(pub f64);
269///
270/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
271/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
272///
273/// let result = overlap(a, b);
274/// assert!(result.is_empty());
275///
276/// let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
277/// let result = overlap(a, c);
278/// assert_eq!(Metres(4.0), result.lower());
279/// assert_eq!(Metres(4.0), result.upper());
280/// ```
281pub fn overlap<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Interval<T> {
282    Interval {
283        lower: max(a.lower(), b.lower()),
284        upper: min(a.upper(), b.upper()),
285    }
286}
287
288/// Calculate the if and where two `Interval`s overlap..
289///
290/// * `a`, `b` the `Interval`s.
291///
292/// returns the overlap, i.e. or None if the overlap is not valid.
293///
294/// # Examples
295/// ```
296/// use generic_interval::{Interval, overlaps};
297///
298/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
299/// pub struct Metres(pub f64);
300///
301/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
302/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
303///
304/// let result = overlaps(a, b);
305/// assert!(result.is_none());
306///
307/// let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
308/// let result = overlaps(a, c).unwrap();
309/// assert_eq!(Metres(4.0), result.lower());
310/// assert_eq!(Metres(4.0), result.upper());
311/// ```
312pub fn overlaps<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Option<Interval<T>> {
313    let v = overlap(a, b);
314    if v.is_empty() { None } else { Some(v) }
315}
316
317/// Calculate the intersection of the two `Interval`s.
318///
319/// * `a`, `b` the `Interval`s.
320///
321/// returns the intersection, or None if the intersection is not valid.
322///
323/// # Examples
324/// ```
325/// use generic_interval::{Interval, intersection};
326///
327/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
328/// pub struct Metres(pub f64);
329///
330/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
331/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
332///
333/// let result = intersection(a, b);
334/// assert!(result.is_none());
335///
336/// let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
337/// let result = intersection(a, c).unwrap();
338/// assert_eq!(Metres(4.0), result.lower());
339/// assert_eq!(Metres(4.0), result.upper());
340/// ```
341pub fn intersection<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Option<Interval<T>> {
342    overlaps(a, b)
343}
344
345/// Calculate the union of the two `Interval`s.
346/// Note: it is called `hull` because it does not match the precise definition
347/// of a `union` of sets.
348///
349/// * `a`, `b` the `Interval`s.
350///
351/// returns the union the the `Interval`s.
352///
353/// # Examples
354/// ```
355/// use generic_interval::{Interval, hull};
356///
357/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
358/// pub struct Metres(pub f64);
359///
360/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
361/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
362///
363/// let result = hull(a, b);
364/// assert_eq!(Metres(1.0), result.lower());
365/// assert_eq!(Metres(9.0), result.upper());
366/// ```
367pub fn hull<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Interval<T> {
368    Interval {
369        lower: min(a.lower(), b.lower()),
370        upper: max(a.upper(), b.upper()),
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377    use chrono::DateTime;
378    use serde_json;
379
380    #[test]
381    fn test_min_and_max_f64() {
382        // min -ve and +ve
383        assert_eq!(min(-1.0 + f64::EPSILON, -1.0), -1.0);
384        assert_eq!(min(1.0, 1.0 + f64::EPSILON), 1.0);
385        // max -ve and +ve
386        assert_eq!(max(-1.0, -1.0 - f64::EPSILON), -1.0);
387        assert_eq!(max(1.0 - f64::EPSILON, 1.0), 1.0);
388    }
389
390    #[test]
391    fn test_interval_f64() {
392        let bad_interval = Interval::try_from((4.0, 3.0));
393        assert_eq!(Err("Invalid interval"), bad_interval);
394
395        let zero = Interval::<f64>::default();
396        assert_eq!(0.0, zero.lower());
397        assert_eq!(0.0, zero.upper());
398        assert!(!zero.is_empty());
399
400        let interval = Interval::try_from((1.0, 4.0)).unwrap();
401
402        assert_eq!(1.0, interval.lower());
403        assert_eq!(4.0, interval.upper());
404        assert!(!interval.is_empty());
405        println!("interval: {:?}", interval);
406
407        assert_eq!(3.0, interval.width());
408        assert_eq!(2.5, interval.mean());
409
410        assert!(!interval.is_inside(&0.9));
411        assert!(interval.is_inside(&1.0));
412        assert!(interval.is_inside(&4.0));
413        assert!(!interval.is_inside(&4.1));
414
415        assert!(!interval.is_within(&0.9));
416        assert!(interval.is_within(&1.0));
417        assert!(!interval.is_within(&4.0));
418
419        let serialized = serde_json::to_string(&interval).unwrap();
420        println!("serialized interval: {:?}", serialized);
421        let deserialized: Interval<f64> = serde_json::from_str(&serialized).unwrap();
422        assert_eq!(interval, deserialized);
423
424        let interval2 = Interval::new(5.0, 9.0);
425        let result = interval + interval2;
426        assert_eq!(6.0, result.lower());
427        assert_eq!(13.0, result.upper());
428        assert!(!result.is_empty());
429
430        let mut result = interval;
431        result += interval2;
432        assert_eq!(6.0, result.lower());
433        assert_eq!(13.0, result.upper());
434        assert!(!result.is_empty());
435
436        let result = interval - interval2;
437        assert_eq!(-4.0, result.lower());
438        assert_eq!(-5.0, result.upper());
439        assert!(result.is_empty());
440
441        let mut result = interval;
442        result -= interval2;
443        assert_eq!(-4.0, result.lower());
444        assert_eq!(-5.0, result.upper());
445
446        assert!(result.is_empty());
447        result.set_lower(1.0);
448        assert_eq!(1.0, result.lower());
449        result.set_upper(2.0);
450        assert_eq!(2.0, result.upper());
451
452        let result = intersection(interval, interval2);
453        assert!(result.is_none());
454
455        let interval3 = Interval::new(4.0, 9.0);
456        let result = intersection(interval, interval3);
457        assert!(result.is_some());
458        let result = result.unwrap();
459        assert_eq!(4.0, result.lower());
460        assert_eq!(4.0, result.upper());
461
462        let result = hull(interval, interval2);
463        assert_eq!(1.0, result.lower());
464        assert_eq!(9.0, result.upper());
465        assert!(!result.is_empty());
466    }
467
468    #[test]
469    fn test_interval_date_time() {
470        let start_time = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp");
471        let finish_time = DateTime::from_timestamp(1431649000, 0).expect("invalid timestamp");
472        let bad_interval = Interval::try_from((finish_time, start_time));
473        assert_eq!(Err("Invalid interval"), bad_interval);
474
475        let interval = Interval::try_from((start_time, finish_time)).unwrap();
476
477        assert_eq!(start_time, interval.lower());
478        assert_eq!(finish_time, interval.upper());
479        assert!(!interval.is_empty());
480        println!("interval: {:?}", interval);
481
482        let start_time2 = DateTime::from_timestamp(1431649500, 0).expect("invalid timestamp");
483        let finish_time2: DateTime<chrono::Utc> =
484            DateTime::from_timestamp(1431650000, 0).expect("invalid timestamp");
485        let interval2 = Interval::new(start_time2, finish_time2);
486        assert!(!interval2.is_empty());
487
488        let result = intersection(interval, interval2);
489        assert!(result.is_none());
490
491        let overall = hull(interval, interval2);
492        assert_eq!(start_time, overall.lower());
493        assert_eq!(finish_time2, overall.upper());
494
495        let start_time3 = DateTime::from_timestamp(1431648500, 0).expect("invalid timestamp");
496        let finish_time3: DateTime<chrono::Utc> =
497            DateTime::from_timestamp(1431651000, 0).expect("invalid timestamp");
498        let interval3 = Interval::new(start_time3, finish_time3);
499        assert!(!interval3.is_empty());
500
501        let result = intersection(interval, interval3);
502        assert!(result.is_some());
503    }
504}