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 {
75                    at: crossed(x, index, high, previous, current),
76                    rising: true,
77                });
78                state = Level::Above;
79            }
80            Level::Above if current < low => {
81                found.push(Edge {
82                    at: crossed(x, index, low, previous, current),
83                    rising: false,
84                });
85                state = Level::Beneath;
86            }
87            Level::Between => state = level(current, low, high),
88            Level::Beneath | Level::Above => {}
89        }
90    }
91
92    found
93}
94
95#[must_use]
96pub fn crossing(x: &[f64], y: &[f64], target: f64) -> Option<f64> {
97    let length = x.len().min(y.len());
98
99    (1..length).find_map(|index| {
100        let (previous, current) = (y[index - 1], y[index]);
101        let straddles = (previous <= target && target <= current) || (current <= target && target <= previous);
102
103        straddles.then(|| crossed(x, index, target, previous, current))
104    })
105}
106
107/* === Private Functions === */
108
109fn level(value: f64, low: f64, high: f64) -> Level {
110    if value > high {
111        Level::Above
112    } else if value < low {
113        Level::Beneath
114    } else {
115        Level::Between
116    }
117}
118
119fn crossed(x: &[f64], index: usize, target: f64, previous: f64, current: f64) -> f64 {
120    let (before, after) = (x[index - 1], x[index]);
121    let rise = current - previous;
122
123    if rise.abs() < f64::EPSILON {
124        return before;
125    }
126
127    before + (target - previous) / rise * (after - before)
128}
129
130/* === Tests === */