kizzasi_logic/constraint/
sliding_window.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone)]
12pub struct SlidingWindowConstraint {
13 name: String,
14 window_size: usize,
15 constraint_fn: SlidingWindowFn,
16 buffer: Vec<f32>,
17 weight: f32,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub enum SlidingWindowFn {
23 MeanInRange { lo: f32, hi: f32 },
25 MaxVariance { max_var: f32 },
27 MaxRange { max_range: f32 },
29 BoundedVariation { max_variation: f32 },
31 AllInRange { lo: f32, hi: f32 },
33 AnyInRange { lo: f32, hi: f32 },
35 TrendInRange { min_slope: f32, max_slope: f32 },
37}
38
39impl SlidingWindowConstraint {
40 pub fn new(name: &str, window_size: usize, constraint_fn: SlidingWindowFn) -> Self {
42 Self {
43 name: name.to_string(),
44 window_size,
45 constraint_fn,
46 buffer: Vec::with_capacity(window_size),
47 weight: 1.0,
48 }
49 }
50
51 pub fn with_weight(mut self, weight: f32) -> Self {
53 self.weight = weight;
54 self
55 }
56
57 pub fn push_and_check(&mut self, value: f32) -> (bool, f32) {
61 self.buffer.push(value);
63 if self.buffer.len() > self.window_size {
64 self.buffer.remove(0);
65 }
66
67 if self.buffer.len() < self.window_size {
69 return (true, 0.0);
70 }
71
72 self.check_window()
73 }
74
75 pub(crate) fn check_window(&self) -> (bool, f32) {
77 match &self.constraint_fn {
78 SlidingWindowFn::MeanInRange { lo, hi } => {
79 let mean: f32 = self.buffer.iter().sum::<f32>() / self.buffer.len() as f32;
80 if mean >= *lo && mean <= *hi {
81 (true, 0.0)
82 } else if mean < *lo {
83 (false, lo - mean)
84 } else {
85 (false, mean - hi)
86 }
87 }
88 SlidingWindowFn::MaxVariance { max_var } => {
89 let n = self.buffer.len() as f32;
90 let mean: f32 = self.buffer.iter().sum::<f32>() / n;
91 let var: f32 = self.buffer.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / n;
92 if var <= *max_var {
93 (true, 0.0)
94 } else {
95 (false, var - max_var)
96 }
97 }
98 SlidingWindowFn::MaxRange { max_range } => {
99 let min = self.buffer.iter().cloned().reduce(f32::min).unwrap_or(0.0);
100 let max = self.buffer.iter().cloned().reduce(f32::max).unwrap_or(0.0);
101 let range = max - min;
102 if range <= *max_range {
103 (true, 0.0)
104 } else {
105 (false, range - max_range)
106 }
107 }
108 SlidingWindowFn::BoundedVariation { max_variation } => {
109 let variation: f32 = self.buffer.windows(2).map(|w| (w[1] - w[0]).abs()).sum();
110 if variation <= *max_variation {
111 (true, 0.0)
112 } else {
113 (false, variation - max_variation)
114 }
115 }
116 SlidingWindowFn::AllInRange { lo, hi } => {
117 let violation: f32 = self
118 .buffer
119 .iter()
120 .map(|&x| {
121 if x < *lo {
122 lo - x
123 } else if x > *hi {
124 x - hi
125 } else {
126 0.0
127 }
128 })
129 .sum();
130 (violation == 0.0, violation)
131 }
132 SlidingWindowFn::AnyInRange { lo, hi } => {
133 let any_in_range = self.buffer.iter().any(|&x| x >= *lo && x <= *hi);
134 if any_in_range {
135 (true, 0.0)
136 } else {
137 let min_dist = self
139 .buffer
140 .iter()
141 .map(|&x| if x < *lo { lo - x } else { x - hi })
142 .reduce(f32::min)
143 .unwrap_or(0.0);
144 (false, min_dist)
145 }
146 }
147 SlidingWindowFn::TrendInRange {
148 min_slope,
149 max_slope,
150 } => {
151 let n = self.buffer.len() as f32;
153 let mean_i = (n - 1.0) / 2.0;
154 let mean_x: f32 = self.buffer.iter().sum::<f32>() / n;
155
156 let cov: f32 = self
157 .buffer
158 .iter()
159 .enumerate()
160 .map(|(i, &x)| (i as f32 - mean_i) * (x - mean_x))
161 .sum();
162 let var_i: f32 = (0..self.buffer.len())
163 .map(|i| (i as f32 - mean_i).powi(2))
164 .sum();
165
166 let slope = if var_i > f32::EPSILON {
167 cov / var_i
168 } else {
169 0.0
170 };
171
172 if slope >= *min_slope && slope <= *max_slope {
173 (true, 0.0)
174 } else if slope < *min_slope {
175 (false, min_slope - slope)
176 } else {
177 (false, slope - max_slope)
178 }
179 }
180 }
181 }
182
183 pub fn reset(&mut self) {
185 self.buffer.clear();
186 }
187
188 pub fn window(&self) -> &[f32] {
190 &self.buffer
191 }
192
193 pub fn name(&self) -> &str {
195 &self.name
196 }
197
198 pub fn window_size(&self) -> usize {
200 self.window_size
201 }
202
203 pub fn weight(&self) -> f32 {
205 self.weight
206 }
207
208 pub fn is_ready(&self) -> bool {
210 self.buffer.len() >= self.window_size
211 }
212}
213
214#[derive(Debug, Default)]
216pub struct SlidingWindowChecker {
217 constraints: Vec<SlidingWindowConstraint>,
218}
219
220impl SlidingWindowChecker {
221 pub fn new() -> Self {
223 Self::default()
224 }
225
226 pub fn add(&mut self, constraint: SlidingWindowConstraint) {
228 self.constraints.push(constraint);
229 }
230
231 pub fn push_and_check(&mut self, value: f32) -> Vec<(String, bool, f32)> {
233 self.constraints
234 .iter_mut()
235 .map(|c| {
236 let (sat, viol) = c.push_and_check(value);
237 (c.name().to_string(), sat, viol * c.weight())
238 })
239 .collect()
240 }
241
242 pub fn total_violation(&self) -> f32 {
244 self.constraints
245 .iter()
246 .filter(|c| c.is_ready())
247 .map(|c| {
248 let (_, viol) = c.check_window();
249 viol * c.weight()
250 })
251 .sum()
252 }
253
254 pub fn reset(&mut self) {
256 for c in &mut self.constraints {
257 c.reset();
258 }
259 }
260
261 pub fn all_satisfied(&self) -> bool {
263 self.constraints
264 .iter()
265 .filter(|c| c.is_ready())
266 .all(|c| c.check_window().0)
267 }
268}