1use crate::residual::residual_norm;
25use crate::types::SignTuple;
26
27#[inline]
36pub fn compute_sign_tuple(norms: &[f64], k: usize) -> SignTuple {
37 if k == 0 || norms.is_empty() {
38 return SignTuple::ZERO;
39 }
40
41 let norm = if k < norms.len() { residual_norm(norms[k]) } else { 0.0 };
42
43 let drift = if k >= 1 && k < norms.len() {
45 norms[k] - norms[k - 1]
46 } else {
47 0.0
48 };
49
50 let slew = if k >= 2 && k < norms.len() {
52 let drift_prev = norms[k - 1] - norms[k - 2];
53 drift - drift_prev
54 } else {
55 0.0
56 };
57
58 SignTuple { norm, drift, slew }
59}
60
61#[inline]
63pub fn drift_persistence(norms: &[f64], k: usize, window: usize) -> f64 {
64 if k < 1 || window == 0 {
65 return 0.0;
66 }
67 let start = k.saturating_sub(window);
68 let mut positive_count: u32 = 0;
69 let mut total: u32 = 0;
70 let mut i = start + 1;
71 while i <= k && i < norms.len() {
72 let drift = norms[i] - norms[i - 1];
73 if drift > 0.0 {
74 positive_count += 1;
75 }
76 total += 1;
77 i += 1;
78 }
79 if total == 0 { 0.0 } else { positive_count as f64 / total as f64 }
80}
81
82#[inline]
85pub fn boundary_density(states: &[u8], k: usize, window: usize) -> f64 {
86 if window == 0 || k == 0 {
87 return 0.0;
88 }
89 let start = k.saturating_sub(window);
90 let mut boundary_count: u32 = 0;
91 let mut total: u32 = 0;
92 let mut i = start;
93 while i <= k && i < states.len() {
94 if states[i] == 1 {
95 boundary_count += 1;
96 }
97 total += 1;
98 i += 1;
99 }
100 if total == 0 { 0.0 } else { boundary_count as f64 / total as f64 }
101}
102
103#[inline]
105pub fn slew_density(norms: &[f64], k: usize, window: usize, delta_s: f64) -> f64 {
106 if k < 2 || window == 0 {
107 return 0.0;
108 }
109 let start = if k > window { k - window } else { 2 };
110 let start = if start < 2 { 2 } else { start };
111 let mut slew_count: u32 = 0;
112 let mut total: u32 = 0;
113 let mut i = start;
114 while i <= k && i < norms.len() {
115 let drift_cur = norms[i] - norms[i - 1];
116 let drift_prev = norms[i - 1] - norms[i - 2];
117 let slew = drift_cur - drift_prev;
118 if slew > delta_s || slew < -delta_s {
119 slew_count += 1;
120 }
121 total += 1;
122 i += 1;
123 }
124 if total == 0 { 0.0 } else { slew_count as f64 / total as f64 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_sign_tuple_basic() {
133 let norms = [0.0, 1.0, 3.0, 2.0];
134 let s = compute_sign_tuple(&norms, 2);
135 assert!((s.norm - 3.0).abs() < 1e-12);
136 assert!((s.drift - 2.0).abs() < 1e-12); let drift_prev = 1.0; assert!((s.slew - (2.0 - drift_prev)).abs() < 1e-12); }
140
141 #[test]
142 fn test_sign_tuple_zero_index() {
143 let norms = [1.0, 2.0];
144 let s = compute_sign_tuple(&norms, 0);
145 assert_eq!(s, SignTuple::ZERO);
146 }
147
148 #[test]
149 fn test_drift_persistence() {
150 let norms = [0.0, 1.0, 2.0, 3.0, 4.0];
152 assert!((drift_persistence(&norms, 4, 4) - 1.0).abs() < 1e-12);
153 }
154}