Skip to main content

use_optimization_constraint/
lib.rs

1#![forbid(unsafe_code)]
2//! Bounds and simple constraint helpers.
3//!
4//! The crate stays deliberately small and focuses on inclusive numeric bounds
5//! over `f64` values.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_optimization_constraint::{satisfies_all, Bounds};
11//!
12//! let constraints = [
13//!     Bounds { min: Some(0.0), max: Some(10.0) },
14//!     Bounds { min: Some(2.0), max: None },
15//! ];
16//!
17//! assert!(satisfies_all(4.0, &constraints));
18//! assert!(!satisfies_all(1.0, &constraints));
19//! ```
20
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct Bounds {
23    pub min: Option<f64>,
24    pub max: Option<f64>,
25}
26
27impl Bounds {
28    pub fn contains(&self, value: f64) -> bool {
29        if !self.is_valid() || !value.is_finite() {
30            return false;
31        }
32
33        if let Some(minimum) = self.min
34            && value < minimum
35        {
36            return false;
37        }
38
39        if let Some(maximum) = self.max
40            && value > maximum
41        {
42            return false;
43        }
44
45        true
46    }
47
48    pub fn clamp(&self, value: f64) -> f64 {
49        if !self.is_valid() || !value.is_finite() {
50            return value;
51        }
52
53        let mut clamped = value;
54        if let Some(minimum) = self.min {
55            clamped = clamped.max(minimum);
56        }
57        if let Some(maximum) = self.max {
58            clamped = clamped.min(maximum);
59        }
60
61        clamped
62    }
63
64    pub fn is_valid(&self) -> bool {
65        if self.min.is_some_and(|value| !value.is_finite())
66            || self.max.is_some_and(|value| !value.is_finite())
67        {
68            return false;
69        }
70
71        match (self.min, self.max) {
72            (Some(minimum), Some(maximum)) => minimum <= maximum,
73            _ => true,
74        }
75    }
76}
77
78pub fn within(value: f64, min: f64, max: f64) -> bool {
79    Bounds {
80        min: Some(min),
81        max: Some(max),
82    }
83    .contains(value)
84}
85
86pub fn outside(value: f64, min: f64, max: f64) -> bool {
87    let bounds = Bounds {
88        min: Some(min),
89        max: Some(max),
90    };
91
92    bounds.is_valid() && value.is_finite() && !bounds.contains(value)
93}
94
95pub fn satisfies_all(value: f64, constraints: &[Bounds]) -> bool {
96    if !value.is_finite() {
97        return false;
98    }
99
100    constraints
101        .iter()
102        .all(|constraint| constraint.contains(value))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::{Bounds, outside, satisfies_all, within};
108
109    #[test]
110    fn validates_and_checks_bounds() {
111        let bounds = Bounds {
112            min: Some(0.0),
113            max: Some(10.0),
114        };
115
116        assert!(bounds.is_valid());
117        assert!(bounds.contains(0.0));
118        assert!(bounds.contains(10.0));
119        assert!(!bounds.contains(11.0));
120        assert_eq!(bounds.clamp(-3.0), 0.0);
121        assert_eq!(bounds.clamp(12.0), 10.0);
122        assert_eq!(bounds.clamp(6.0), 6.0);
123    }
124
125    #[test]
126    fn handles_invalid_bounds() {
127        let invalid = Bounds {
128            min: Some(5.0),
129            max: Some(1.0),
130        };
131
132        assert!(!invalid.is_valid());
133        assert!(!invalid.contains(3.0));
134        assert_eq!(invalid.clamp(3.0), 3.0);
135        assert!(!within(3.0, 5.0, 1.0));
136        assert!(!outside(3.0, 5.0, 1.0));
137    }
138
139    #[test]
140    fn supports_unbounded_edges() {
141        let lower_only = Bounds {
142            min: Some(2.0),
143            max: None,
144        };
145        let upper_only = Bounds {
146            min: None,
147            max: Some(4.0),
148        };
149
150        assert!(lower_only.contains(3.0));
151        assert!(!lower_only.contains(1.0));
152        assert!(upper_only.contains(3.0));
153        assert!(!upper_only.contains(5.0));
154    }
155
156    #[test]
157    fn evaluates_constraint_sets() {
158        let constraints = [
159            Bounds {
160                min: Some(0.0),
161                max: Some(10.0),
162            },
163            Bounds {
164                min: Some(2.0),
165                max: Some(8.0),
166            },
167        ];
168
169        assert!(satisfies_all(5.0, &constraints));
170        assert!(!satisfies_all(1.0, &constraints));
171        assert!(satisfies_all(5.0, &[]));
172        assert!(!satisfies_all(f64::NAN, &constraints));
173        assert!(within(3.0, 1.0, 5.0));
174        assert!(outside(0.0, 1.0, 5.0));
175    }
176}