Skip to content

Commit f4cdad4

Browse files
authored
Merge pull request #1207 from EnergySystemsModellingLab/fix-assetcapacity-semantics
Fix `AssetCapacity` `Eq`/`Ord` semantics
2 parents 68cd34e + fb95c7a commit f4cdad4

1 file changed

Lines changed: 128 additions & 12 deletions

File tree

src/asset/capacity.rs

Lines changed: 128 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ pub enum AssetCapacity {
1313
Discrete(u32, Capacity),
1414
}
1515

16+
impl AssetCapacity {
17+
/// Return the smaller of `self` or `other`.
18+
///
19+
/// # Panics
20+
///
21+
/// Panics if the comparison is not meaningful. This happens if either `AssetCapacity` contains
22+
/// a NaN value, one is discrete and the other continuous or if both are discrete and the unit
23+
/// size differs.
24+
pub fn min(self, other: AssetCapacity) -> AssetCapacity {
25+
match self.partial_cmp(&other) {
26+
None => panic!("Comparing invalid AssetCapacity values ({self:?} and {other:?})"),
27+
Some(Ordering::Greater) => other,
28+
_ => self,
29+
}
30+
}
31+
}
32+
1633
impl Add for AssetCapacity {
1734
type Output = Self;
1835

@@ -49,23 +66,15 @@ impl Sub for AssetCapacity {
4966
}
5067
}
5168

52-
impl Eq for AssetCapacity {}
53-
5469
impl PartialOrd for AssetCapacity {
5570
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
56-
Some(self.cmp(other))
57-
}
58-
}
59-
60-
impl Ord for AssetCapacity {
61-
fn cmp(&self, other: &Self) -> Ordering {
6271
match (self, other) {
63-
(AssetCapacity::Continuous(a), AssetCapacity::Continuous(b)) => a.total_cmp(b),
72+
(AssetCapacity::Continuous(a), AssetCapacity::Continuous(b)) => a.partial_cmp(b),
6473
(AssetCapacity::Discrete(units1, size1), AssetCapacity::Discrete(units2, size2)) => {
65-
Self::check_same_unit_size(*size1, *size2);
66-
units1.cmp(units2)
74+
// NB: Also returns `None` if either is NaN
75+
(*size1 == *size2).then(|| units1.cmp(units2))
6776
}
68-
_ => panic!("Cannot compare different types of AssetCapacity ({self:?} and {other:?})"),
77+
_ => None,
6978
}
7079
}
7180
}
@@ -213,4 +222,111 @@ mod tests {
213222
let got = orig.apply_limit_factor(factor);
214223
assert_eq!(got, AssetCapacity::Discrete(expected_units, unit_size));
215224
}
225+
226+
#[rstest]
227+
#[case::less(
228+
AssetCapacity::Continuous(Capacity(4.0)),
229+
AssetCapacity::Continuous(Capacity(6.0)),
230+
Some(Ordering::Less)
231+
)]
232+
#[case::equal(
233+
AssetCapacity::Continuous(Capacity(4.0)),
234+
AssetCapacity::Continuous(Capacity(4.0)),
235+
Some(Ordering::Equal)
236+
)]
237+
#[case::greater(
238+
AssetCapacity::Continuous(Capacity(6.0)),
239+
AssetCapacity::Continuous(Capacity(4.0)),
240+
Some(Ordering::Greater)
241+
)]
242+
fn partial_cmp_continuous(
243+
#[case] left: AssetCapacity,
244+
#[case] right: AssetCapacity,
245+
#[case] expected: Option<Ordering>,
246+
) {
247+
assert_eq!(left.partial_cmp(&right), expected);
248+
assert_eq!(left == right, expected == Some(Ordering::Equal));
249+
}
250+
251+
#[rstest]
252+
#[case::less(
253+
AssetCapacity::Discrete(2, Capacity(3.0)),
254+
AssetCapacity::Discrete(4, Capacity(3.0)),
255+
Some(Ordering::Less)
256+
)]
257+
#[case::equal(
258+
AssetCapacity::Discrete(4, Capacity(3.0)),
259+
AssetCapacity::Discrete(4, Capacity(3.0)),
260+
Some(Ordering::Equal)
261+
)]
262+
#[case::greater(
263+
AssetCapacity::Discrete(5, Capacity(3.0)),
264+
AssetCapacity::Discrete(4, Capacity(3.0)),
265+
Some(Ordering::Greater)
266+
)]
267+
fn partial_cmp_discrete_with_matching_unit_size(
268+
#[case] left: AssetCapacity,
269+
#[case] right: AssetCapacity,
270+
#[case] expected: Option<Ordering>,
271+
) {
272+
assert_eq!(left.partial_cmp(&right), expected);
273+
assert_eq!(left == right, expected == Some(Ordering::Equal));
274+
}
275+
276+
#[rstest]
277+
#[case::mixed_types(
278+
AssetCapacity::Continuous(Capacity(4.0)),
279+
AssetCapacity::Discrete(4, Capacity(1.0))
280+
)]
281+
#[case::different_unit_sizes(
282+
AssetCapacity::Discrete(4, Capacity(1.0)),
283+
AssetCapacity::Discrete(4, Capacity(2.0))
284+
)]
285+
#[case::nan_continuous(
286+
AssetCapacity::Continuous(Capacity(f64::NAN)),
287+
AssetCapacity::Continuous(Capacity(4.0))
288+
)]
289+
fn partial_cmp_returns_none_for_invalid_comparisons(
290+
#[case] left: AssetCapacity,
291+
#[case] right: AssetCapacity,
292+
) {
293+
assert_eq!(left.partial_cmp(&right), None);
294+
assert!(left != right);
295+
}
296+
297+
#[rstest]
298+
#[case::continuous(
299+
AssetCapacity::Continuous(Capacity(4.0)),
300+
AssetCapacity::Continuous(Capacity(6.0)),
301+
AssetCapacity::Continuous(Capacity(4.0))
302+
)]
303+
#[case::discrete(
304+
AssetCapacity::Discrete(2, Capacity(3.0)),
305+
AssetCapacity::Discrete(4, Capacity(3.0)),
306+
AssetCapacity::Discrete(2, Capacity(3.0))
307+
)]
308+
fn min_returns_smaller_capacity(
309+
#[case] left: AssetCapacity,
310+
#[case] right: AssetCapacity,
311+
#[case] expected: AssetCapacity,
312+
) {
313+
assert_eq!(left.min(right), expected);
314+
}
315+
316+
#[rstest]
317+
#[case::mixed_types(
318+
AssetCapacity::Continuous(Capacity(4.0)),
319+
AssetCapacity::Discrete(4, Capacity(1.0))
320+
)]
321+
#[case::different_unit_sizes(
322+
AssetCapacity::Discrete(4, Capacity(1.0)),
323+
AssetCapacity::Discrete(4, Capacity(2.0))
324+
)]
325+
#[should_panic(expected = "Comparing invalid AssetCapacity values")]
326+
fn min_panics_for_invalid_comparisons(
327+
#[case] left: AssetCapacity,
328+
#[case] right: AssetCapacity,
329+
) {
330+
let _ = left.min(right);
331+
}
216332
}

0 commit comments

Comments
 (0)