use_optimization_constraint/
lib.rs1#![forbid(unsafe_code)]
2#[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}