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, 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}