kasuari/
strength.rs

1//! Contains useful constants and functions for producing strengths for use in the constraint
2//! solver. Each constraint added to the solver has an associated strength specifying the precedence
3//! the solver should impose when choosing which constraints to enforce. It will try to enforce all
4//! constraints, but if that is impossible the lowest strength constraints are the first to be
5//! violated.
6//!
7//! Strengths are simply real numbers. The strongest legal strength is 1,001,001,000.0. The weakest
8//! is 0.0. For convenience constants are declared for commonly used strengths. These are
9//! [`REQUIRED`], [`STRONG`], [`MEDIUM`] and [`WEAK`]. Feel free to multiply these by other values
10//! to get intermediate strengths. Note that the solver will clip given strengths to the legal
11//! range.
12//!
13//! [`REQUIRED`] signifies a constraint that cannot be violated under any circumstance. Use this
14//! special strength sparingly, as the solver will fail completely if it find that not all of the
15//! [`REQUIRED`] constraints can be satisfied. The other strengths represent fallible constraints.
16//! These should be the most commonly used strenghts for use cases where violating a constraint is
17//! acceptable or even desired.
18//!
19//! The solver will try to get as close to satisfying the constraints it violates as possible,
20//! strongest first. This behaviour can be used (for example) to provide a "default" value for a
21//! variable should no other stronger constraints be put upon it.
22
23use core::ops;
24
25#[derive(Debug, Copy, Clone, PartialEq)]
26pub struct Strength(f64);
27
28impl Strength {
29    /// The required strength for a constraint. This is the strongest possible strength.
30    pub const REQUIRED: Strength = Strength(1_001_001_000.0);
31
32    /// A strong strength for a constraint. This is weaker than `REQUIRED` but stronger than
33    /// `MEDIUM`.
34    pub const STRONG: Strength = Strength(1_000_000.0);
35
36    /// A medium strength for a constraint. This is weaker than `STRONG` but stronger than `WEAK`.
37    pub const MEDIUM: Strength = Strength(1_000.0);
38
39    /// A weak strength for a constraint. This is weaker than `MEDIUM` but stronger than `0.0`.
40    pub const WEAK: Strength = Strength(1.0);
41
42    /// The weakest possible strength for a constraint. This is weaker than `WEAK`.
43    pub const ZERO: Strength = Strength(0.0);
44
45    /// Create a new strength with the given value, clipped to the legal range (0.0, REQUIRED)
46    #[inline]
47    pub const fn new(value: f64) -> Self {
48        Self(value.clamp(0.0, Self::REQUIRED.value()))
49    }
50
51    /// Create a constraint as a linear combination of STRONG, MEDIUM and WEAK strengths.
52    ///
53    /// Each weight is multiplied by the multiplier, clamped to the legal range and then multiplied
54    /// by the corresponding strength. The resulting strengths are then summed.
55    #[inline]
56    pub const fn create(strong: f64, medium: f64, weak: f64, multiplier: f64) -> Self {
57        let strong = (strong * multiplier).clamp(0.0, 1000.0) * Self::STRONG.value();
58        let medium = (medium * multiplier).clamp(0.0, 1000.0) * Self::MEDIUM.value();
59        let weak = (weak * multiplier).clamp(0.0, 1000.0) * Self::WEAK.value();
60        Self::new(strong + medium + weak)
61    }
62
63    /// The value of the strength
64    #[inline]
65    pub const fn value(&self) -> f64 {
66        self.0
67    }
68
69    /// Add two strengths together, clamping the result to the legal range
70    #[inline]
71    pub const fn add(self, rhs: Self) -> Self {
72        Self::new(self.0 + rhs.0)
73    }
74
75    /// Subtract one strength from another, clipping the result to the legal range
76    #[inline]
77    pub const fn sub(self, rhs: Self) -> Self {
78        Self::new(self.0 - rhs.0)
79    }
80
81    /// Multiply a strength by a scalar, clipping the result to the legal range
82    #[inline]
83    pub const fn mul_f64(self, rhs: f64) -> Self {
84        Self::new(self.0 * rhs)
85    }
86
87    /// Multiply a strength by a scalar, clipping the result to the legal range
88    #[inline]
89    pub const fn mul_f32(self, rhs: f32) -> Self {
90        Self::new(self.0 * rhs as f64)
91    }
92
93    /// Divide a strength by a scalar, clipping the result to the legal range
94    #[inline]
95    pub const fn div_f64(self, rhs: f64) -> Self {
96        Self::new(self.0 / rhs)
97    }
98
99    /// Divide a strength by a scalar, clipping the result to the legal range
100    #[inline]
101    pub const fn div_f32(self, rhs: f32) -> Self {
102        Self::new(self.0 / rhs as f64)
103    }
104}
105
106impl ops::Add<Strength> for Strength {
107    type Output = Self;
108
109    /// Add two strengths together, clipping the result to the legal range
110    #[inline]
111    fn add(self, rhs: Self) -> Self {
112        Self::add(self, rhs)
113    }
114}
115
116impl ops::Sub<Strength> for Strength {
117    type Output = Strength;
118
119    /// Subtract one strength from another, clipping the result to the legal range
120    #[inline]
121    fn sub(self, rhs: Strength) -> Strength {
122        Self::sub(self, rhs)
123    }
124}
125
126impl ops::AddAssign<Strength> for Strength {
127    /// Perform an in-place addition of two strengths, clipping the result to the legal range
128    #[inline]
129    fn add_assign(&mut self, rhs: Self) {
130        *self = *self + rhs;
131    }
132}
133
134impl ops::SubAssign<Strength> for Strength {
135    /// Perform an in-place subtraction of two strengths, clipping the result to the legal range
136    #[inline]
137    fn sub_assign(&mut self, rhs: Self) {
138        *self = *self - rhs;
139    }
140}
141
142impl ops::Mul<f64> for Strength {
143    type Output = Strength;
144
145    /// Multiply a strength by a scalar, clipping the result to the legal range
146    #[inline]
147    fn mul(self, rhs: f64) -> Strength {
148        self.mul_f64(rhs)
149    }
150}
151
152impl ops::Mul<Strength> for f64 {
153    type Output = Strength;
154
155    /// Multiply a scalar by a strength, clipping the result to the legal range
156    #[inline]
157    fn mul(self, rhs: Strength) -> Strength {
158        rhs.mul_f64(self)
159    }
160}
161
162impl ops::MulAssign<f64> for Strength {
163    /// Perform an in-place multiplication of a strength by a scalar, clipping the result to the
164    /// legal range
165    #[inline]
166    fn mul_assign(&mut self, rhs: f64) {
167        *self = *self * rhs;
168    }
169}
170
171impl core::cmp::Ord for Strength {
172    #[inline]
173    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
174        self.0.partial_cmp(&other.0).unwrap()
175    }
176}
177
178impl core::cmp::PartialOrd for Strength {
179    #[inline]
180    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
181        Some(self.cmp(other))
182    }
183}
184
185impl core::cmp::Eq for Strength {}
186
187#[cfg(test)]
188mod tests {
189    use rstest::rstest;
190
191    use super::*;
192
193    #[rstest]
194    #[case::under(-1.0, Strength::ZERO)]
195    #[case::min(0.0, Strength::ZERO)]
196    #[case::weak(1.0, Strength::WEAK)]
197    #[case::medium(1_000.0, Strength::MEDIUM)]
198    #[case::strong(1_000_000.0, Strength::STRONG)]
199    #[case::required(1_001_001_000.0, Strength::REQUIRED)]
200    #[case::over(1_001_001_001.0, Strength::REQUIRED)]
201    fn new(#[case] value: f64, #[case] expected: Strength) {
202        let strength = Strength::new(value);
203        assert_eq!(strength, expected);
204    }
205
206    #[rstest]
207    #[case::all_zeroes(0.0, 0.0, 0.0, 1.0, Strength::ZERO)]
208    #[case::weak(0.0, 0.0, 1.0, 1.0, Strength::WEAK)]
209    #[case::medium(0.0, 1.0, 0.0, 1.0, Strength::MEDIUM)]
210    #[case::strong(1.0, 0.0, 0.0, 1.0, Strength::STRONG)]
211    #[case::weak_clip(0.0, 0.0, 1000.0, 2.0, Strength::MEDIUM)]
212    #[case::medium_clip(0.0, 1000.0, 0.0, 2.0, Strength::STRONG)]
213    #[case::strong_clip(1000.0, 0.0, 0.0, 2.0, 1000.0 * Strength::STRONG)]
214    #[case::all_non_zero(1.0, 1.0, 1.0, 1.0, Strength::STRONG + Strength::MEDIUM + Strength::WEAK)]
215    #[case::multiplier(1.0, 1.0, 1.0, 2.0, 2.0 * (Strength::STRONG + Strength::MEDIUM + Strength::WEAK))]
216    #[case::max(1000.0, 1000.0, 1000.0, 1.0, Strength::REQUIRED)]
217    fn create(
218        #[case] strong: f64,
219        #[case] medium: f64,
220        #[case] weak: f64,
221        #[case] multiplier: f64,
222        #[case] expected: Strength,
223    ) {
224        let strength = Strength::create(strong, medium, weak, multiplier);
225        assert_eq!(strength, expected);
226    }
227
228    #[rstest]
229    #[case::zero_plus_zero(Strength::ZERO, Strength::ZERO, Strength::ZERO)]
230    #[case::zero_plus_weak(Strength::ZERO, Strength::WEAK, Strength::WEAK)]
231    #[case::weak_plus_zero(Strength::WEAK, Strength::ZERO, Strength::WEAK)]
232    #[case::weak_plus_weak(Strength::WEAK, Strength::WEAK, Strength::new(2.0))]
233    #[case::weak_plus_medium(Strength::WEAK, Strength::MEDIUM, Strength::new(1001.0))]
234    #[case::medium_plus_strong(Strength::MEDIUM, Strength::STRONG, Strength::new(1_001_000.0))]
235    #[case::strong_plus_required(Strength::STRONG, Strength::REQUIRED, Strength::REQUIRED)]
236    fn add(#[case] lhs: Strength, #[case] rhs: Strength, #[case] expected: Strength) {
237        let result = lhs + rhs;
238        assert_eq!(result, expected);
239    }
240
241    #[rstest]
242    #[case::zero_plus_zero(Strength::ZERO, Strength::ZERO, Strength::ZERO)]
243    #[case::zero_plus_weak(Strength::ZERO, Strength::WEAK, Strength::WEAK)]
244    #[case::weak_plus_zero(Strength::WEAK, Strength::ZERO, Strength::WEAK)]
245    #[case::weak_plus_weak(Strength::WEAK, Strength::WEAK, Strength::new(2.0))]
246    #[case::weak_plus_medium(Strength::WEAK, Strength::MEDIUM, Strength::new(1001.0))]
247    #[case::medium_plus_strong(Strength::MEDIUM, Strength::STRONG, Strength::new(1_001_000.0))]
248    #[case::saturate_high(Strength::STRONG, Strength::REQUIRED, Strength::REQUIRED)]
249    fn add_assign(#[case] lhs: Strength, #[case] rhs: Strength, #[case] expected: Strength) {
250        let mut result = lhs;
251        result += rhs;
252        assert_eq!(result, expected);
253    }
254
255    #[rstest]
256    #[case::saturate_low(Strength::ZERO, Strength::WEAK, Strength::ZERO)]
257    #[case::zero_minus_zero(Strength::ZERO, Strength::ZERO, Strength::ZERO)]
258    #[case::weak_minus_zero(Strength::WEAK, Strength::ZERO, Strength::WEAK)]
259    #[case::weak_minus_weak(Strength::WEAK, Strength::WEAK, Strength::ZERO)]
260    #[case::medium_minus_weak(Strength::MEDIUM, Strength::WEAK, Strength::new(999.0))]
261    #[case::strong_minus_medium(Strength::STRONG, Strength::MEDIUM, Strength::new(999_000.0))]
262    #[case::required_minus_strong(
263        Strength::REQUIRED,
264        Strength::STRONG,
265        Strength::new(1_000_001_000.0)
266    )]
267    #[case::required_minus_required(Strength::REQUIRED, Strength::REQUIRED, Strength::ZERO)]
268    fn sub(#[case] lhs: Strength, #[case] rhs: Strength, #[case] expected: Strength) {
269        let result = lhs - rhs;
270        assert_eq!(result, expected);
271    }
272
273    #[rstest]
274    #[case::saturate_low(Strength::ZERO, Strength::WEAK, Strength::ZERO)]
275    #[case::zero_minus_zero(Strength::ZERO, Strength::ZERO, Strength::ZERO)]
276    #[case::weak_minus_zero(Strength::WEAK, Strength::ZERO, Strength::WEAK)]
277    #[case::weak_minus_weak(Strength::WEAK, Strength::WEAK, Strength::ZERO)]
278    #[case::medium_minus_weak(Strength::MEDIUM, Strength::WEAK, Strength::new(999.0))]
279    #[case::strong_minus_medium(Strength::STRONG, Strength::MEDIUM, Strength::new(999_000.0))]
280    #[case::required_minus_strong(
281        Strength::REQUIRED,
282        Strength::STRONG,
283        Strength::new(1_000_001_000.0)
284    )]
285    #[case::required_minus_required(Strength::REQUIRED, Strength::REQUIRED, Strength::ZERO)]
286    fn sub_assign(#[case] lhs: Strength, #[case] rhs: Strength, #[case] expected: Strength) {
287        let mut result = lhs;
288        result -= rhs;
289        assert_eq!(result, expected);
290    }
291
292    #[rstest]
293    #[case::negative(Strength::WEAK, -1.0, Strength::ZERO)]
294    #[case::zero_mul_zero(Strength::ZERO, 0.0, Strength::ZERO)]
295    #[case::zero_mul_one(Strength::ZERO, 1.0, Strength::ZERO)]
296    #[case::weak_mul_zero(Strength::WEAK, 0.0, Strength::ZERO)]
297    #[case::weak_mul_one(Strength::WEAK, 1.0, Strength::WEAK)]
298    #[case::weak_mul_two(Strength::WEAK, 2.0, Strength::new(2.0))]
299    #[case::medium_mul_half(Strength::MEDIUM, 0.5, Strength::new(500.0))]
300    #[case::strong_mul_two(Strength::STRONG, 2.0, Strength::new(2_000_000.0))]
301    #[case::required_mul_half(Strength::REQUIRED, 0.5, Strength::new(500_500_500.0))]
302    fn mul(#[case] lhs: Strength, #[case] rhs: f64, #[case] expected: Strength) {
303        let result = lhs * rhs;
304        assert_eq!(result, expected);
305    }
306
307    #[rstest]
308    #[case::negative(Strength::WEAK, -1.0, Strength::ZERO)]
309    #[case::zero_mul_zero(Strength::ZERO, 0.0, Strength::ZERO)]
310    #[case::zero_mul_one(Strength::ZERO, 1.0, Strength::ZERO)]
311    #[case::weak_mul_zero(Strength::WEAK, 0.0, Strength::ZERO)]
312    #[case::weak_mul_one(Strength::WEAK, 1.0, Strength::WEAK)]
313    #[case::weak_mul_two(Strength::WEAK, 2.0, Strength::new(2.0))]
314    #[case::medium_mul_half(Strength::MEDIUM, 0.5, Strength::new(500.0))]
315    #[case::strong_mul_two(Strength::STRONG, 2.0, Strength::new(2_000_000.0))]
316    #[case::required_mul_half(Strength::REQUIRED, 0.5, Strength::new(500_500_500.0))]
317    fn mul_assign(#[case] lhs: Strength, #[case] rhs: f64, #[case] expected: Strength) {
318        let mut result = lhs;
319        result *= rhs;
320        assert_eq!(result, expected);
321    }
322}