fts_core/models/curve/pwl/
point.rs

1use std::cmp::Ordering;
2
3/// A representation of a point for use in defining piecewise-linear curves
4///
5/// Each point consists of:
6/// - A rate (quantity per time unit)
7/// - A price (value per unit)
8///
9/// Points are used to define the vertices of piecewise-linear demand curves.
10#[derive(Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(inline))]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Point {
14    /// The rate (quantity per time) coordinate
15    pub rate: f64,
16    /// The price (value per unit) coordinate
17    pub price: f64,
18}
19
20// We define a partial ordering for point so that demand curve validation is:
21// All consecutive pairs of points satisfy pt0 <= pt1
22// This means: pt0.rate <= pt1.rate AND pt0.price >= pt1.price
23impl PartialOrd for Point {
24    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
25        let rate_ord = self.rate.partial_cmp(&other.rate)?;
26        let price_ord = self.price.partial_cmp(&other.price)?.reverse();
27        // Note the reversed state for price!
28
29        if rate_ord == Ordering::Equal {
30            Some(price_ord)
31        } else if price_ord == Ordering::Equal {
32            Some(rate_ord)
33        } else if rate_ord == price_ord {
34            Some(price_ord)
35        } else {
36            None
37        }
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    fn pt(x: f64, y: f64) -> Point {
46        Point { rate: x, price: y }
47    }
48
49    #[test]
50    fn test_good_cmp() {
51        let a = pt(0.0, 10.0);
52        let b = pt(10.0, 0.0);
53        assert!(a < b);
54        assert!(a <= b);
55        assert!(b > a);
56        assert!(b >= a);
57        assert!(a == a);
58    }
59
60    #[test]
61    fn test_bad_cmp() {
62        let a = pt(0.0, 10.0);
63        let b = pt(10.0, 20.0);
64        assert!(a.partial_cmp(&b).is_none());
65        assert!(b.partial_cmp(&a).is_none());
66    }
67
68    #[test]
69    fn test_nan() {
70        let a = pt(f64::NAN, 10.0);
71        let b = pt(10.0, f64::NAN);
72        assert!(a.partial_cmp(&b).is_none());
73        assert!(b.partial_cmp(&a).is_none());
74    }
75}