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//! [](https://crates.io/crates/generic-interval)
24//! [](https://docs.rs/generic-interval/)
25//! [](https://opensource.org/license/mit/)
26//! [](https://github.com/kenba/interval-rs/actions)
27//! [](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};
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 does not include 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//! let result = intersection(a, b);
62//! assert!(result.is_none());
63//!
64//! let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
65//! let result = intersection(a, c).unwrap();
66//! assert_eq!(Metres(4.0), result.lower());
67//! assert_eq!(Metres(4.0), result.upper());
68//! ```
69//!
70//! ## License
71//!
72//! `generic-interval` is provided under a MIT license, see [LICENSE](LICENSE).
73
74#![cfg_attr(not(test), no_std)]
75
76use core::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign};
77use num_traits::{Num, NumCast};
78use serde::{Deserialize, Serialize};
79
80/// Return the minimum of two values.
81///
82/// * `a`, `b` the values.
83///
84/// returns the minimum of two values.
85#[must_use]
86pub fn min<T>(a: T, b: T) -> T
87where
88 T: Copy + PartialOrd,
89{
90 if b < a { b } else { a }
91}
92
93/// Return the maximum of two values.
94///
95/// * `a`, `b` the values.
96///
97/// returns the maximum of two values.
98#[must_use]
99pub fn max<T>(a: T, b: T) -> T
100where
101 T: Copy + PartialOrd,
102{
103 if b < a { a } else { b }
104}
105
106/// A closed interval (endpoints included).
107#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
108pub struct Interval<T> {
109 lower: T,
110 upper: T,
111}
112
113impl<T: Copy + PartialOrd> Interval<T> {
114 #[must_use]
115 pub const fn new(lower: T, upper: T) -> Self {
116 Self { lower, upper }
117 }
118
119 #[must_use]
120 pub const fn lower(&self) -> T {
121 self.lower
122 }
123
124 #[must_use]
125 pub const fn upper(&self) -> T {
126 self.upper
127 }
128
129 #[must_use]
130 pub fn is_empty(&self) -> bool {
131 self.upper.lt(&self.lower)
132 }
133
134 /// Return whether the value is inside the `Interval` inclusively.
135 ///
136 /// * `value` the value.
137 ///
138 /// returns true if lower <= value <= upper.
139 #[must_use]
140 pub fn is_inside(&self, value: &T) -> bool {
141 self.lower.le(value) && self.upper.ge(value)
142 }
143
144 /// Return whether the value is inside the `Interval`.
145 ///
146 /// * `value` the value.
147 ///
148 /// returns true if lower <= value < upper.
149 #[must_use]
150 pub fn is_within(&self, value: &T) -> bool {
151 self.lower.le(value) && self.upper.gt(value)
152 }
153}
154
155impl<T: Copy + PartialOrd + Sub<Output = T>> Interval<T> {
156 #[must_use]
157 pub fn width(&self) -> T {
158 self.upper - self.lower
159 }
160}
161
162impl<T: Default> Default for Interval<T> {
163 fn default() -> Self {
164 Self {
165 lower: T::default(),
166 upper: T::default(),
167 }
168 }
169}
170
171impl<
172 T: Num
173 + NumCast
174 + Copy
175 + PartialOrd
176 + Add<Output = T>
177 + Div<Output = T>
178 + Mul<Output = T>
179 + Sub<Output = T>,
180> Interval<T>
181{
182 #[allow(clippy::missing_panics_doc)]
183 #[must_use]
184 pub fn mean(&self) -> T {
185 let two: T = num_traits::cast(2).expect("Could not convert 2 to T");
186 self.lower + self.width() / two
187 }
188}
189
190impl<T: Copy + PartialOrd> TryFrom<(T, T)> for Interval<T> {
191 type Error = &'static str;
192
193 /// Attempt to create an Interval from a pair of values in lower, upper order.
194 ///
195 /// return a valid `Interval` or `Err` if upper < lower.
196 fn try_from(params: (T, T)) -> Result<Self, Self::Error> {
197 let v = Self {
198 lower: params.0,
199 upper: params.1,
200 };
201 if v.is_empty() {
202 Err("Invalid interval")
203 } else {
204 Ok(v)
205 }
206 }
207}
208
209impl<T: Add<Output = T>> Add for Interval<T> {
210 type Output = Self;
211
212 fn add(self, other: Self) -> Self::Output {
213 Self {
214 lower: self.lower + other.lower,
215 upper: self.upper + other.upper,
216 }
217 }
218}
219
220impl<T: Copy + Add<Output = T>> AddAssign for Interval<T> {
221 fn add_assign(&mut self, other: Self) {
222 *self = *self + other;
223 }
224}
225
226impl<T: Sub<Output = T>> Sub for Interval<T> {
227 type Output = Self;
228
229 fn sub(self, other: Self) -> Self::Output {
230 Self {
231 lower: self.lower - other.lower,
232 upper: self.upper - other.upper,
233 }
234 }
235}
236
237impl<T: Copy + Sub<Output = T>> SubAssign for Interval<T> {
238 fn sub_assign(&mut self, other: Self) {
239 *self = *self - other;
240 }
241}
242
243/// Calculate the overlap of the two `Interval`s.
244///
245/// * `a`, `b` the `Interval`s.
246///
247/// returns the overlap, or None if the overlap is not valid.
248///
249/// # Examples
250/// ```
251/// use generic_interval::{Interval, overlap};
252///
253/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
254/// pub struct Metres(pub f64);
255///
256/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
257/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
258///
259/// let result = overlap(a, b);
260/// assert!(result.is_empty());
261///
262/// let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
263/// let result = overlap(a, c);
264/// assert_eq!(Metres(4.0), result.lower());
265/// assert_eq!(Metres(4.0), result.upper());
266/// ```
267pub fn overlap<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Interval<T> {
268 Interval {
269 lower: max(a.lower(), b.lower()),
270 upper: min(a.upper(), b.upper()),
271 }
272}
273
274/// Calculate the intersection of the two `Interval`s.
275///
276/// * `a`, `b` the `Interval`s.
277///
278/// returns the intersection, or None if the intersection is not valid.
279///
280/// # Examples
281/// ```
282/// use generic_interval::{Interval, intersection};
283///
284/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
285/// pub struct Metres(pub f64);
286///
287/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
288/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
289///
290/// let result = intersection(a, b);
291/// assert!(result.is_none());
292///
293/// let c = Interval::try_from((Metres(4.0), Metres(9.0))).unwrap();
294/// let result = intersection(a, c).unwrap();
295/// assert_eq!(Metres(4.0), result.lower());
296/// assert_eq!(Metres(4.0), result.upper());
297/// ```
298pub fn intersection<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Option<Interval<T>> {
299 let v = overlap(a, b);
300 if v.is_empty() { None } else { Some(v) }
301}
302
303/// Calculate the union of the two `Interval`s.
304/// Note: it is called `hull` because it does not match the precise definition
305/// of a `union` of sets.
306///
307/// * `a`, `b` the `Interval`s.
308///
309/// returns the union the the `Interval`s.
310///
311/// # Examples
312/// ```
313/// use generic_interval::{Interval, hull};
314///
315/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
316/// pub struct Metres(pub f64);
317///
318/// let a = Interval::try_from((Metres(1.0), Metres(4.0))).unwrap();
319/// let b = Interval::try_from((Metres(6.0), Metres(9.0))).unwrap();
320///
321/// let result = hull(a, b);
322/// assert_eq!(Metres(1.0), result.lower());
323/// assert_eq!(Metres(9.0), result.upper());
324/// ```
325pub fn hull<T: Copy + PartialOrd>(a: Interval<T>, b: Interval<T>) -> Interval<T> {
326 Interval {
327 lower: min(a.lower(), b.lower()),
328 upper: max(a.upper(), b.upper()),
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use chrono::DateTime;
336 use serde_json;
337
338 #[test]
339 fn test_min_and_max_f64() {
340 // min -ve and +ve
341 assert_eq!(min(-1.0 + f64::EPSILON, -1.0), -1.0);
342 assert_eq!(min(1.0, 1.0 + f64::EPSILON), 1.0);
343 // max -ve and +ve
344 assert_eq!(max(-1.0, -1.0 - f64::EPSILON), -1.0);
345 assert_eq!(max(1.0 - f64::EPSILON, 1.0), 1.0);
346 }
347
348 #[test]
349 fn test_interval_f64() {
350 let bad_interval = Interval::try_from((4.0, 3.0));
351 assert_eq!(Err("Invalid interval"), bad_interval);
352
353 let zero = Interval::<f64>::default();
354 assert_eq!(0.0, zero.lower());
355 assert_eq!(0.0, zero.upper());
356 assert!(!zero.is_empty());
357
358 let interval = Interval::try_from((1.0, 4.0)).unwrap();
359
360 assert_eq!(1.0, interval.lower());
361 assert_eq!(4.0, interval.upper());
362 assert!(!interval.is_empty());
363 println!("interval: {:?}", interval);
364
365 assert_eq!(3.0, interval.width());
366 assert_eq!(2.5, interval.mean());
367
368 assert!(!interval.is_inside(&0.9));
369 assert!(interval.is_inside(&1.0));
370 assert!(interval.is_inside(&4.0));
371 assert!(!interval.is_inside(&4.1));
372
373 assert!(!interval.is_within(&0.9));
374 assert!(interval.is_within(&1.0));
375 assert!(!interval.is_within(&4.0));
376
377 let serialized = serde_json::to_string(&interval).unwrap();
378 println!("serialized interval: {:?}", serialized);
379 let deserialized: Interval<f64> = serde_json::from_str(&serialized).unwrap();
380 assert_eq!(interval, deserialized);
381
382 let interval2 = Interval::new(5.0, 9.0);
383 let result = interval + interval2;
384 assert_eq!(6.0, result.lower());
385 assert_eq!(13.0, result.upper());
386 assert!(!result.is_empty());
387
388 let mut result = interval;
389 result += interval2;
390 assert_eq!(6.0, result.lower());
391 assert_eq!(13.0, result.upper());
392 assert!(!result.is_empty());
393
394 let result = interval - interval2;
395 assert_eq!(-4.0, result.lower());
396 assert_eq!(-5.0, result.upper());
397 assert!(result.is_empty());
398
399 let mut result = interval;
400 result -= interval2;
401 assert_eq!(-4.0, result.lower());
402 assert_eq!(-5.0, result.upper());
403 assert!(result.is_empty());
404
405 let result = intersection(interval, interval2);
406 assert!(result.is_none());
407
408 let interval3 = Interval::new(4.0, 9.0);
409 let result = intersection(interval, interval3);
410 assert!(result.is_some());
411 let result = result.unwrap();
412 assert_eq!(4.0, result.lower());
413 assert_eq!(4.0, result.upper());
414
415 let result = hull(interval, interval2);
416 assert_eq!(1.0, result.lower());
417 assert_eq!(9.0, result.upper());
418 assert!(!result.is_empty());
419 }
420
421 #[test]
422 fn test_interval_date_time() {
423 let start_time = DateTime::from_timestamp(1431648000, 0).expect("invalid timestamp");
424 let finish_time = DateTime::from_timestamp(1431649000, 0).expect("invalid timestamp");
425 let bad_interval = Interval::try_from((finish_time, start_time));
426 assert_eq!(Err("Invalid interval"), bad_interval);
427
428 let interval = Interval::try_from((start_time, finish_time)).unwrap();
429
430 assert_eq!(start_time, interval.lower());
431 assert_eq!(finish_time, interval.upper());
432 assert!(!interval.is_empty());
433 println!("interval: {:?}", interval);
434
435 let start_time2 = DateTime::from_timestamp(1431649500, 0).expect("invalid timestamp");
436 let finish_time2: DateTime<chrono::Utc> =
437 DateTime::from_timestamp(1431650000, 0).expect("invalid timestamp");
438 let interval2 = Interval::new(start_time2, finish_time2);
439 assert!(!interval2.is_empty());
440
441 let result = intersection(interval, interval2);
442 assert!(result.is_none());
443
444 let overall = hull(interval, interval2);
445 assert_eq!(start_time, overall.lower());
446 assert_eq!(finish_time2, overall.upper());
447
448 let start_time3 = DateTime::from_timestamp(1431648500, 0).expect("invalid timestamp");
449 let finish_time3: DateTime<chrono::Utc> =
450 DateTime::from_timestamp(1431651000, 0).expect("invalid timestamp");
451 let interval3 = Interval::new(start_time3, finish_time3);
452 assert!(!interval3.is_empty());
453
454 let result = intersection(interval, interval3);
455 assert!(result.is_some());
456 }
457}