arnalisa/bins/
last_calm_point.rs

1//! A bin that calculates the last calm point of a curve.
2//! The calm point means that the `y` input value has not changed by
3//! more than `y_max_delta` since this point.
4//!
5//! ```text
6//!   ┌───[last_calm_point]───┐
7//!  ⇒│x                calm_x│⇒
8//!  ⇒│y                calm_y│⇒
9//!  ⇒│y_max_delta            │
10//!   └───────────────────────┘
11//! ```
12
13use super::{
14    build_names, BinBuildEnvironment, BinDescription, Calculator,
15    FetchItem, GetCalibration, Item, Iteration, Result, Scope, SinkBin,
16    SinkNames, SourceBin, SourceId, SourceNames, SourceSinkBinDescription,
17    WriteDotSimple, SINK_X, SINK_Y,
18};
19use crate::error;
20use crate::R64;
21use indexmap::{IndexMap, IndexSet};
22
23static BIN_TYPE: &str = "last_calm_point";
24static SINK_Y_MAX_DELTA: &str = "y_max_delta";
25static SOURCE_CALM_X: &str = "calm_x";
26static SOURCE_CALM_Y: &str = "calm_y";
27
28#[derive(Debug, Clone)]
29struct Point {
30    x: R64,
31    y: R64,
32}
33
34/// A bin calculates the last calm point of a curve.
35#[derive(Debug)]
36pub struct Bin {
37    scope: Scope,
38
39    source_x: Box<dyn FetchItem>,
40    source_y: Box<dyn FetchItem>,
41    source_y_max_delta: Box<dyn FetchItem>,
42
43    curve: IndexMap<R64, R64>,
44    min: Option<Point>,
45    max: Option<Point>,
46    last_calm: Option<Point>,
47
48    result_calm_x: Item,
49    result_calm_y: Item,
50}
51
52impl SinkBin for Bin {}
53
54impl SourceBin for Bin {
55    fn get_source_data(&self, source: &SourceId) -> Result<Item> {
56        if source.id == SOURCE_CALM_X {
57            Ok(self.result_calm_x.clone())
58        } else if source.id == SOURCE_CALM_Y {
59            Ok(self.result_calm_y.clone())
60        } else {
61            error::MissingSourceName {
62                scope: self.scope.clone(),
63                name: source.id.to_string(),
64                bin_type: BIN_TYPE.to_string(),
65            }
66            .fail()
67        }
68    }
69}
70
71impl Calculator for Bin {
72    fn calculate(&mut self, _iteration: &Iteration) -> Result<()> {
73        let x = self.source_x.fetch_item(&self.scope)?;
74        let y = self.source_y.fetch_item(&self.scope)?;
75        let y_max_delta =
76            self.source_y_max_delta.fetch_item(&self.scope)?;
77
78        match (x.to_float(), y.to_float(), y_max_delta.to_float()) {
79            (Ok(_), Ok(_), Ok(y_max_delta)) if y_max_delta <= 0f64 => {
80                // Clear when y_max_delta is zero or lower
81                self.clear();
82            }
83            (Ok(_), Err(_), _)
84            | (Err(_), Ok(_), _)
85            | (Err(_), Err(_), _) => {
86                // Do nothing when either X or Y is not a float
87            }
88            (Ok(_), Ok(_), Err(_)) => {
89                // Clear when y_max_delta is not a float
90                self.clear();
91            }
92            (Ok(x), Ok(y), Ok(y_max_delta)) => {
93                let point = Point { x, y };
94                self.add_point(&point);
95
96                if self.current_delta() > y_max_delta
97                    || self.last_calm.is_none()
98                {
99                    self.update_calculation(y_max_delta);
100                }
101            }
102        }
103
104        let (calm_x, calm_y) = match self.last_calm {
105            Some(Point { ref x, ref y }) => {
106                (Item::from(*x), Item::from(*y))
107            }
108            None => (Item::Nothing, Item::Nothing),
109        };
110        self.result_calm_x = calm_x;
111        self.result_calm_y = calm_y;
112        Ok(())
113    }
114}
115
116impl Bin {
117    fn clear(&mut self) {
118        self.curve.clear();
119        self.min = None;
120        self.max = None;
121        self.last_calm = None;
122    }
123
124    fn add_point(&mut self, p: &Point) {
125        self.curve.insert(p.x, p.y);
126        self.curve.sort_keys();
127
128        self.min = Self::min_point(&self.min, &Some(p.clone()));
129        self.max = Self::max_point(&self.max, &Some(p.clone()));
130    }
131
132    fn min_point(a: &Option<Point>, b: &Option<Point>) -> Option<Point> {
133        match (a, b) {
134            (&Some(ref a), &Some(ref b)) => {
135                if a.y < b.y {
136                    Some(a.clone())
137                } else {
138                    Some(b.clone())
139                }
140            }
141            (&Some(ref a), &None) => Some(a.clone()),
142            (&None, &Some(ref b)) => Some(b.clone()),
143            (&None, &None) => None,
144        }
145    }
146
147    fn max_point(a: &Option<Point>, b: &Option<Point>) -> Option<Point> {
148        match (a, b) {
149            (&Some(ref a), &Some(ref b)) => {
150                if a.y > b.y {
151                    Some(a.clone())
152                } else {
153                    Some(b.clone())
154                }
155            }
156            (&Some(ref a), &None) => Some(a.clone()),
157            (&None, &Some(ref b)) => Some(b.clone()),
158            (&None, &None) => None,
159        }
160    }
161
162    fn delta(a: &Option<Point>, b: &Option<Point>) -> R64 {
163        use decorum::Real;
164        match (a, b) {
165            (&Some(ref a), &Some(ref b)) => {
166                (a.clone().y - b.clone().y).abs()
167            }
168            _ => R64::from(0f64),
169        }
170    }
171
172    fn current_delta(&self) -> R64 {
173        use decorum::Real;
174        match (&self.min, &self.max) {
175            (
176                &Some(Point {
177                    x: ref _min_x,
178                    y: ref min_y,
179                }),
180                &Some(Point {
181                    x: ref _max_x,
182                    y: ref max_y,
183                }),
184            ) => (*max_y - *min_y).abs(),
185            _ => R64::from(0f64),
186        }
187    }
188
189    fn update_calculation(&mut self, max_delta: R64) {
190        let mut min = None;
191        let mut max = None;
192
193        let mut keep = true;
194
195        let mut recalculated = self
196            .curve
197            .iter()
198            .rev()
199            .filter_map(|(x, y)| {
200                if keep {
201                    let point = Point { x: *x, y: *y };
202
203                    let delta: R64 = {
204                        let mut delta = R64::from(0f64);
205
206                        delta = delta
207                            .max(Self::delta(&Some(point.clone()), &min));
208                        delta = delta
209                            .max(Self::delta(&Some(point.clone()), &max));
210                        delta = delta.max(Self::delta(&min, &max));
211                        delta
212                    };
213                    min = Self::min_point(&min, &Some(point.clone()));
214                    max = Self::max_point(&max, &Some(point.clone()));
215
216                    keep = delta <= max_delta;
217                    if keep {
218                        Some((*x, *y))
219                    } else {
220                        None
221                    }
222                } else {
223                    None
224                }
225            })
226            .collect::<IndexMap<R64, R64>>();
227        recalculated.sort_keys();
228
229        self.min = min;
230        self.max = max;
231        self.curve = recalculated;
232        self.last_calm = self
233            .curve
234            .iter()
235            .map(|(x, y)| Point { x: *x, y: *y })
236            .next();
237    }
238}
239
240/// Description of the last_calm_point bin.
241#[derive(Clone, Debug, Serialize, Deserialize)]
242pub struct Description;
243
244impl BinDescription for Description {
245    type Bin = Bin;
246
247    fn check_validity(
248        &self,
249        _scope: &Scope,
250        _get_calibration: &mut dyn GetCalibration,
251    ) -> Result<()> {
252        Ok(())
253    }
254
255    fn bin_type(&self) -> &'static str {
256        BIN_TYPE
257    }
258}
259
260impl SinkNames for Description {
261    fn sink_names(&self) -> IndexSet<String> {
262        build_names(&[SINK_X, SINK_Y, SINK_Y_MAX_DELTA])
263    }
264}
265
266impl SourceNames for Description {
267    fn source_names(&self) -> Result<IndexSet<String>> {
268        Ok(build_names(&[SOURCE_CALM_X, SOURCE_CALM_Y]))
269    }
270}
271
272impl SourceSinkBinDescription for Description {
273    fn build_bin(
274        &self,
275        scope: &Scope,
276        env: &mut dyn BinBuildEnvironment,
277    ) -> Result<Self::Bin> {
278        Ok(Bin {
279            scope: scope.clone(),
280
281            source_x: env.resolve(SINK_X)?,
282            source_y: env.resolve(SINK_Y)?,
283            source_y_max_delta: env.resolve(SINK_Y_MAX_DELTA)?,
284
285            curve: IndexMap::new(),
286            min: None,
287            max: None,
288            last_calm: None,
289            result_calm_x: Item::Nothing,
290            result_calm_y: Item::Nothing,
291        })
292    }
293}
294
295impl WriteDotSimple for Description {}
296
297#[cfg(test)]
298mod tests {
299    use super::Description;
300    use crate::bins::{directsource, verificationsink};
301    use crate::Item as I;
302    use crate::{run_bin, Result};
303    use indexmap::indexset;
304
305    #[test]
306    fn simulate() -> Result<()> {
307        let input = directsource::Description {
308            columns: indexset![
309                "x".to_string(),
310                "y".to_string(),
311                "y_max_delta".to_string(),
312            ],
313            rows: vec![
314                vec![I::from(0.0f64), I::from(9.0f64), I::from(3.0f64)],
315                vec![I::from(1.0f64), I::from(0.0f64), I::from(3.0f64)],
316                vec![I::from(2.0f64), I::from(3.0f64), I::from(3.0f64)],
317                vec![I::from(3.0f64), I::from(7.0f64), I::from(3.0f64)],
318                vec![I::from(4.0f64), I::from(4.0f64), I::from(3.0f64)],
319                vec![I::from(5.0f64), I::from(5.0f64), I::from(3.0f64)],
320                vec![I::from(6.0f64), I::from(5.0f64), I::from(3.0f64)],
321                vec![I::from(7.0f64), I::from(5.0f64), I::from(3.0f64)],
322                vec![I::from(8.0f64), I::from(5.0f64), I::from(3.0f64)],
323                vec![I::from(9.0f64), I::from(5.0f64), I::from(3.0f64)],
324                vec![I::from(10.0f64), I::from(0.0f64), I::from(3.0f64)],
325                vec![I::from(11.0f64), I::from(0.0f64), I::from(3.0f64)],
326                vec![I::from(12.0f64), I::from(0.0f64), I::from(3.0f64)],
327                vec![I::from(13.0f64), I::from(0.0f64), I::from(3.0f64)],
328                vec![I::from(14.0f64), I::from(0.0f64), I::from(3.0f64)],
329                vec![I::from(15.0f64), I::from(3.0f64), I::from(3.0f64)],
330                vec![I::from(16.0f64), I::from(0.0f64), I::from(3.0f64)],
331                vec![I::from(17.0f64), I::from(3.0f64), I::from(3.0f64)],
332                vec![I::from(18.0f64), I::from(0.0f64), I::from(3.0f64)],
333                vec![I::from(19.0f64), I::from(3.0f64), I::from(3.0f64)],
334            ]
335            .into(),
336        };
337        let verification = verificationsink::Description {
338            columns: indexset!["calm_x".to_string(), "calm_y".to_string()],
339            expected: vec![
340                vec![I::from(0.0f64), I::from(9.0f64)],
341                vec![I::from(1.0f64), I::from(0.0f64)],
342                vec![I::from(1.0f64), I::from(0.0f64)],
343                vec![I::from(3.0f64), I::from(7.0f64)],
344                vec![I::from(3.0f64), I::from(7.0f64)],
345                vec![I::from(3.0f64), I::from(7.0f64)],
346                vec![I::from(3.0f64), I::from(7.0f64)],
347                vec![I::from(3.0f64), I::from(7.0f64)],
348                vec![I::from(3.0f64), I::from(7.0f64)],
349                vec![I::from(3.0f64), I::from(7.0f64)],
350                vec![I::from(10.0f64), I::from(0.0f64)],
351                vec![I::from(10.0f64), I::from(0.0f64)],
352                vec![I::from(10.0f64), I::from(0.0f64)],
353                vec![I::from(10.0f64), I::from(0.0f64)],
354                vec![I::from(10.0f64), I::from(0.0f64)],
355                vec![I::from(10.0f64), I::from(0.0f64)],
356                vec![I::from(10.0f64), I::from(0.0f64)],
357                vec![I::from(10.0f64), I::from(0.0f64)],
358                vec![I::from(10.0f64), I::from(0.0f64)],
359                vec![I::from(10.0f64), I::from(0.0f64)],
360            ]
361            .into(),
362        };
363
364        run_bin(&input, &Description {}, &verification)
365    }
366}