mabi_scenario/
generator.rs1use rand::prelude::*;
4use rand_distr::{Normal, Uniform};
5
6use crate::schema::PatternConfig;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PatternType {
11 Constant,
12 Sine,
13 Cosine,
14 Ramp,
15 Step,
16 Random,
17 Noise,
18 Follow,
19 Replay,
20}
21
22pub struct PatternGenerator {
24 config: PatternConfig,
25 rng: StdRng,
26 start_time: std::time::Instant,
27 last_value: f64,
28}
29
30impl PatternGenerator {
31 pub fn new(config: PatternConfig) -> Self {
33 Self {
34 config,
35 rng: StdRng::from_entropy(),
36 start_time: std::time::Instant::now(),
37 last_value: 0.0,
38 }
39 }
40
41 pub fn reset(&mut self) {
43 self.start_time = std::time::Instant::now();
44 self.last_value = 0.0;
45 }
46
47 pub fn generate(&mut self) -> f64 {
49 let elapsed = self.start_time.elapsed().as_secs_f64();
50 self.generate_at(elapsed)
51 }
52
53 pub fn generate_at(&mut self, time_secs: f64) -> f64 {
55 let value = match &self.config {
56 PatternConfig::Constant { value } => *value,
57
58 PatternConfig::Sine {
59 amplitude,
60 offset,
61 period_secs,
62 phase,
63 } => {
64 let angle = (2.0 * std::f64::consts::PI * time_secs / period_secs) + phase;
65 offset + amplitude * angle.sin()
66 }
67
68 PatternConfig::Cosine {
69 amplitude,
70 offset,
71 period_secs,
72 phase,
73 } => {
74 let angle = (2.0 * std::f64::consts::PI * time_secs / period_secs) + phase;
75 offset + amplitude * angle.cos()
76 }
77
78 PatternConfig::Ramp {
79 start,
80 end,
81 duration_secs,
82 repeat,
83 } => {
84 let t = if *repeat {
85 (time_secs % duration_secs) / duration_secs
86 } else {
87 (time_secs / duration_secs).min(1.0)
88 };
89 start + (end - start) * t
90 }
91
92 PatternConfig::Step {
93 levels,
94 step_duration_secs,
95 } => {
96 if levels.is_empty() {
97 0.0
98 } else {
99 let total_duration = step_duration_secs * levels.len() as f64;
100 let t = time_secs % total_duration;
101 let index = (t / step_duration_secs) as usize;
102 levels[index.min(levels.len() - 1)]
103 }
104 }
105
106 PatternConfig::Random {
107 min,
108 max,
109 distribution,
110 } => {
111 match distribution.as_str() {
112 "uniform" => {
113 let dist = Uniform::new(*min, *max);
114 dist.sample(&mut self.rng)
115 }
116 "normal" | "gaussian" => {
117 let mean = (min + max) / 2.0;
118 let std_dev = (max - min) / 6.0; let dist = Normal::new(mean, std_dev).unwrap();
120 dist.sample(&mut self.rng).clamp(*min, *max)
121 }
122 _ => {
123 let dist = Uniform::new(*min, *max);
124 dist.sample(&mut self.rng)
125 }
126 }
127 }
128
129 PatternConfig::Noise { mean, std_dev } => {
130 let dist = Normal::new(*mean, *std_dev).unwrap();
131 dist.sample(&mut self.rng)
132 }
133
134 PatternConfig::Follow { offset, gain, .. } => {
135 self.last_value * gain + offset
137 }
138
139 PatternConfig::Replay { .. } => {
140 self.last_value
142 }
143 };
144
145 self.last_value = value;
146 value
147 }
148
149 pub fn set_source_value(&mut self, value: f64) {
151 self.last_value = value;
152 }
153
154 pub fn pattern_type(&self) -> PatternType {
156 match &self.config {
157 PatternConfig::Constant { .. } => PatternType::Constant,
158 PatternConfig::Sine { .. } => PatternType::Sine,
159 PatternConfig::Cosine { .. } => PatternType::Cosine,
160 PatternConfig::Ramp { .. } => PatternType::Ramp,
161 PatternConfig::Step { .. } => PatternType::Step,
162 PatternConfig::Random { .. } => PatternType::Random,
163 PatternConfig::Noise { .. } => PatternType::Noise,
164 PatternConfig::Follow { .. } => PatternType::Follow,
165 PatternConfig::Replay { .. } => PatternType::Replay,
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_constant_pattern() {
176 let mut gen = PatternGenerator::new(PatternConfig::Constant { value: 42.0 });
177 assert!((gen.generate() - 42.0).abs() < 0.001);
178 }
179
180 #[test]
181 fn test_sine_pattern() {
182 let mut gen = PatternGenerator::new(PatternConfig::Sine {
183 amplitude: 10.0,
184 offset: 20.0,
185 period_secs: 4.0,
186 phase: 0.0,
187 });
188
189 let v0 = gen.generate_at(0.0);
191 assert!((v0 - 20.0).abs() < 0.001);
192
193 let v1 = gen.generate_at(1.0);
195 assert!((v1 - 30.0).abs() < 0.001);
196 }
197
198 #[test]
199 fn test_ramp_pattern() {
200 let mut gen = PatternGenerator::new(PatternConfig::Ramp {
201 start: 0.0,
202 end: 100.0,
203 duration_secs: 10.0,
204 repeat: false,
205 });
206
207 assert!((gen.generate_at(0.0) - 0.0).abs() < 0.001);
208 assert!((gen.generate_at(5.0) - 50.0).abs() < 0.001);
209 assert!((gen.generate_at(10.0) - 100.0).abs() < 0.001);
210 assert!((gen.generate_at(15.0) - 100.0).abs() < 0.001); }
212
213 #[test]
214 fn test_step_pattern() {
215 let mut gen = PatternGenerator::new(PatternConfig::Step {
216 levels: vec![10.0, 20.0, 30.0],
217 step_duration_secs: 1.0,
218 });
219
220 assert!((gen.generate_at(0.5) - 10.0).abs() < 0.001);
221 assert!((gen.generate_at(1.5) - 20.0).abs() < 0.001);
222 assert!((gen.generate_at(2.5) - 30.0).abs() < 0.001);
223 }
224
225 #[test]
226 fn test_random_pattern() {
227 let mut gen = PatternGenerator::new(PatternConfig::Random {
228 min: 0.0,
229 max: 100.0,
230 distribution: "uniform".to_string(),
231 });
232
233 for _ in 0..100 {
234 let value = gen.generate();
235 assert!(value >= 0.0 && value <= 100.0);
236 }
237 }
238}