Skip to main content

dsfb_semiconductor/sign/
mod.rs

1#[cfg(feature = "std")]
2use crate::error::Result;
3use crate::input::residual_stream::ResidualStream;
4use serde::{Deserialize, Serialize};
5#[cfg(feature = "std")]
6use std::collections::BTreeMap;
7#[cfg(not(feature = "std"))]
8use alloc::{collections::BTreeMap, string::{String, ToString}, vec::Vec};
9
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
11pub struct Sign {
12    pub r: f64,
13    pub d: f64,
14    pub s: f64,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18pub struct FeatureSignPoint {
19    pub timestamp: f64,
20    pub feature_id: String,
21    pub r: f64,
22    pub d: f64,
23    pub s: f64,
24}
25
26impl FeatureSignPoint {
27    pub fn sign(&self) -> Sign {
28        Sign {
29            r: self.r,
30            d: self.d,
31            s: self.s,
32        }
33    }
34}
35
36pub fn build_feature_signs(stream: &ResidualStream) -> Vec<FeatureSignPoint> {
37    let mut grouped = BTreeMap::<&str, Vec<_>>::new();
38    for sample in stream.samples() {
39        grouped
40            .entry(sample.feature_id.as_str())
41            .or_default()
42            .push(sample);
43    }
44
45    let mut rows = Vec::new();
46    for (feature_id, samples) in grouped {
47        let mut previous_r = None;
48        let mut previous_d = 0.0;
49        for sample in samples {
50            let r = sample.value;
51            let d = previous_r.map(|value| r - value).unwrap_or(0.0);
52            let s = if previous_r.is_some() {
53                d - previous_d
54            } else {
55                0.0
56            };
57            rows.push(FeatureSignPoint {
58                timestamp: sample.timestamp,
59                feature_id: feature_id.to_string(),
60                r,
61                d,
62                s,
63            });
64            previous_r = Some(r);
65            previous_d = d;
66        }
67    }
68
69    rows.sort_by(|left, right| {
70        left.timestamp
71            .total_cmp(&right.timestamp)
72            .then_with(|| left.feature_id.cmp(&right.feature_id))
73    });
74    rows
75}
76
77#[cfg(feature = "std")]
78pub fn write_feature_signs_csv(
79    path: &std::path::Path,
80    rows: &[FeatureSignPoint],
81) -> Result<()> {
82    let mut writer = csv::Writer::from_path(path)?;
83    for row in rows {
84        writer.serialize(row)?;
85    }
86    writer.flush()?;
87    Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::input::residual_stream::{ResidualSample, ResidualStream};
94
95    #[test]
96    fn sign_construction_uses_first_and_second_difference() {
97        let stream = ResidualStream::new(vec![
98            ResidualSample {
99                timestamp: 0.0,
100                feature_id: "S001".into(),
101                value: 1.0,
102            },
103            ResidualSample {
104                timestamp: 1.0,
105                feature_id: "S001".into(),
106                value: 2.5,
107            },
108            ResidualSample {
109                timestamp: 2.0,
110                feature_id: "S001".into(),
111                value: 4.0,
112            },
113        ]);
114        let rows = build_feature_signs(&stream);
115        assert_eq!(
116            rows[0].sign(),
117            Sign {
118                r: 1.0,
119                d: 0.0,
120                s: 0.0
121            }
122        );
123        assert_eq!(
124            rows[1].sign(),
125            Sign {
126                r: 2.5,
127                d: 1.5,
128                s: 1.5
129            }
130        );
131        assert_eq!(
132            rows[2].sign(),
133            Sign {
134                r: 4.0,
135                d: 1.5,
136                s: 0.0
137            }
138        );
139    }
140}