dsfb_semiconductor/sign/
mod.rs1#[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}