Skip to main content

harmos_storage/signal/waveform/
edge.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17
18// Third Party Imports
19
20// Local Imports
21
22/* === Constants === */
23
24/* === Macros === */
25
26/* === Type Aliases === */
27
28/* === Structs === */
29
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct Edge {
32    pub at: f64,
33    pub rising: bool,
34}
35
36/* === Enums === */
37
38#[derive(Clone, Copy)]
39enum Level {
40    Beneath,
41    Between,
42    Above,
43}
44
45/* === Traits === */
46
47/* === Implementations === */
48
49/* === Public Functions === */
50
51#[must_use]
52pub fn edges(x: &[f64], y: &[f64], low: f64, high: f64) -> Vec<Edge> {
53    let length = x.len().min(y.len());
54    if length < 2 {
55        return Vec::new();
56    }
57
58    let lowest = y[..length].iter().copied().fold(f64::INFINITY, f64::min);
59    let highest = y[..length].iter().copied().fold(f64::NEG_INFINITY, f64::max);
60    let range = highest - lowest;
61    if !range.is_finite() || range < f64::EPSILON {
62        return Vec::new();
63    }
64
65    let (low, high) = (lowest + range * low, lowest + range * high);
66    let mut state = level(y[0], low, high);
67    let mut found = Vec::new();
68
69    for index in 1..length {
70        let (previous, current) = (y[index - 1], y[index]);
71
72        match state {
73            Level::Beneath if current > high => {
74                found.push(Edge { at: crossed(x, index, high, previous, current), rising: true });
75                state = Level::Above;
76            }
77            Level::Above if current < low => {
78                found.push(Edge { at: crossed(x, index, low, previous, current), rising: false });
79                state = Level::Beneath;
80            }
81            Level::Between => state = level(current, low, high),
82            Level::Beneath | Level::Above => {}
83        }
84    }
85
86    found
87}
88
89#[must_use]
90pub fn crossing(x: &[f64], y: &[f64], target: f64) -> Option<f64> {
91    let length = x.len().min(y.len());
92
93    (1..length).find_map(|index| {
94        let (previous, current) = (y[index - 1], y[index]);
95        let straddles = (previous <= target && target <= current) || (current <= target && target <= previous);
96
97        straddles.then(|| crossed(x, index, target, previous, current))
98    })
99}
100
101/* === Private Functions === */
102
103fn level(value: f64, low: f64, high: f64) -> Level {
104    if value > high {
105        Level::Above
106    } else if value < low {
107        Level::Beneath
108    } else {
109        Level::Between
110    }
111}
112
113fn crossed(x: &[f64], index: usize, target: f64, previous: f64, current: f64) -> f64 {
114    let (before, after) = (x[index - 1], x[index]);
115    let rise = current - previous;
116
117    if rise.abs() < f64::EPSILON {
118        return before;
119    }
120
121    before + (target - previous) / rise * (after - before)
122}
123
124/* === Tests === */