1use numra_core::Scalar;
10
11#[derive(Clone, Debug)]
13pub struct StepProposal<S: Scalar> {
14 pub h_new: S,
16 pub accept: bool,
18}
19
20pub trait StepController<S: Scalar> {
22 fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S>;
29
30 fn accept(&mut self, h: S, err: S);
32
33 fn reject(&mut self, h: S, err: S);
35
36 fn reset(&mut self);
38}
39
40#[derive(Clone, Debug)]
50pub struct PIController<S: Scalar> {
51 pub safety: S,
53 pub fac_min: S,
55 pub fac_max: S,
57 pub beta1: S,
59 pub beta2: S,
61 err_old: Option<S>,
63 h_old: Option<S>,
65}
66
67impl<S: Scalar> Default for PIController<S> {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl<S: Scalar> PIController<S> {
74 pub fn new() -> Self {
76 Self {
77 safety: S::from_f64(0.9),
78 fac_min: S::from_f64(0.2),
79 fac_max: S::from_f64(10.0),
80 beta1: S::from_f64(0.7),
81 beta2: S::from_f64(0.4),
82 err_old: None,
83 h_old: None,
84 }
85 }
86
87 pub fn with_params(safety: S, fac_min: S, fac_max: S, beta1: S, beta2: S) -> Self {
89 Self {
90 safety,
91 fac_min,
92 fac_max,
93 beta1,
94 beta2,
95 err_old: None,
96 h_old: None,
97 }
98 }
99
100 pub fn for_order(order: usize) -> Self {
102 let p = S::from_usize(order);
103 Self {
104 safety: S::from_f64(0.9),
105 fac_min: S::from_f64(0.2),
106 fac_max: S::from_f64(10.0),
107 beta1: S::from_f64(0.7) / p,
108 beta2: S::from_f64(0.4) / p,
109 err_old: None,
110 h_old: None,
111 }
112 }
113}
114
115impl<S: Scalar> StepController<S> for PIController<S> {
116 fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S> {
117 let p = S::from_usize(order);
118
119 let err_safe = err.max(S::EPSILON * S::from_f64(100.0));
121
122 let fac = match self.err_old {
124 Some(err_old) if err_old > S::ZERO => {
125 let err_old_safe = err_old.max(S::EPSILON * S::from_f64(100.0));
127 self.safety
128 * err_safe.powf(-self.beta1)
129 * (err_old_safe / err_safe).powf(self.beta2)
130 }
131 _ => {
132 self.safety * err_safe.powf(-S::ONE / p)
134 }
135 };
136
137 let fac = fac.clamp(self.fac_min, self.fac_max);
139
140 let h_new = h * fac;
141 let accept = err <= S::ONE;
142
143 StepProposal { h_new, accept }
144 }
145
146 fn accept(&mut self, h: S, err: S) {
147 self.err_old = Some(err);
148 self.h_old = Some(h);
149 }
150
151 fn reject(&mut self, _h: S, _err: S) {
152 self.err_old = None;
154 }
155
156 fn reset(&mut self) {
157 self.err_old = None;
158 self.h_old = None;
159 }
160}
161
162#[derive(Clone, Debug)]
166pub struct IController<S: Scalar> {
167 pub safety: S,
168 pub fac_min: S,
169 pub fac_max: S,
170}
171
172impl<S: Scalar> Default for IController<S> {
173 fn default() -> Self {
174 Self {
175 safety: S::from_f64(0.9),
176 fac_min: S::from_f64(0.2),
177 fac_max: S::from_f64(10.0),
178 }
179 }
180}
181
182impl<S: Scalar> StepController<S> for IController<S> {
183 fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S> {
184 let p = S::from_usize(order);
185 let err_safe = err.max(S::EPSILON * S::from_f64(100.0));
186
187 let fac = self.safety * err_safe.powf(-S::ONE / p);
188 let fac = fac.clamp(self.fac_min, self.fac_max);
189
190 let h_new = h * fac;
191 let accept = err <= S::ONE;
192
193 StepProposal { h_new, accept }
194 }
195
196 fn accept(&mut self, _h: S, _err: S) {}
197 fn reject(&mut self, _h: S, _err: S) {}
198 fn reset(&mut self) {}
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_pi_controller_accept() {
207 let mut ctrl = PIController::<f64>::for_order(5);
208
209 let prop = ctrl.propose(0.1, 0.5, 5);
211 assert!(prop.accept);
212 assert!(prop.h_new > 0.1); }
214
215 #[test]
216 fn test_pi_controller_reject() {
217 let mut ctrl = PIController::<f64>::for_order(5);
218
219 let prop = ctrl.propose(0.1, 2.0, 5);
221 assert!(!prop.accept);
222 assert!(prop.h_new < 0.1); }
224
225 #[test]
226 fn test_pi_controller_limits() {
227 let mut ctrl = PIController::<f64>::for_order(5);
228
229 let prop = ctrl.propose(0.1, 1e-10, 5);
231 assert!(prop.accept);
232 assert!(prop.h_new <= 0.1 * ctrl.fac_max);
233
234 let prop = ctrl.propose(0.1, 1e10, 5);
236 assert!(!prop.accept);
237 assert!(prop.h_new >= 0.1 * ctrl.fac_min);
238 }
239
240 #[test]
241 fn test_i_controller() {
242 let mut ctrl = IController::<f64>::default();
243
244 let prop = ctrl.propose(0.1, 0.5, 5);
246 assert!(prop.accept);
247 assert!(prop.h_new > 0.1);
248
249 let prop = ctrl.propose(0.1, 2.0, 5);
251 assert!(!prop.accept);
252 assert!(prop.h_new < 0.1);
253 }
254
255 #[test]
260 fn test_pi_controller_exact_one_error() {
261 let mut ctrl = PIController::<f64>::for_order(5);
262
263 let prop = ctrl.propose(0.1, 1.0, 5);
265 assert!(prop.accept);
266 assert!((prop.h_new - 0.1).abs() < 0.05);
268 }
269
270 #[test]
271 fn test_pi_controller_zero_error() {
272 let mut ctrl = PIController::<f64>::for_order(5);
273
274 let prop = ctrl.propose(0.1, 1e-15, 5);
276 assert!(prop.accept);
277 assert!(prop.h_new <= 0.1 * ctrl.fac_max);
278 }
279
280 #[test]
281 fn test_pi_controller_reset() {
282 let mut ctrl = PIController::<f64>::for_order(5);
283
284 let _ = ctrl.propose(0.1, 0.5, 5);
286 ctrl.accept(0.1, 0.5);
287
288 ctrl.reset();
290
291 let prop = ctrl.propose(0.1, 0.5, 5);
293 assert!(prop.accept);
294 }
295
296 #[test]
297 fn test_pi_controller_consecutive_accepts() {
298 let mut ctrl = PIController::<f64>::for_order(5);
299
300 for _ in 0..5 {
302 let prop = ctrl.propose(0.1, 0.5, 5);
303 assert!(prop.accept);
304 ctrl.accept(0.1, 0.5);
305 }
306 }
307
308 #[test]
309 fn test_pi_controller_reject_clears_memory() {
310 let mut ctrl = PIController::<f64>::for_order(5);
311
312 let _ = ctrl.propose(0.1, 0.5, 5);
314 ctrl.accept(0.1, 0.5);
315
316 let _ = ctrl.propose(0.1, 5.0, 5);
318 ctrl.reject(0.1, 5.0);
319
320 assert!(ctrl.err_old.is_none());
322 }
323
324 #[test]
325 fn test_i_controller_stateless() {
326 let mut ctrl = IController::<f64>::default();
327
328 let prop1 = ctrl.propose(0.1, 0.5, 5);
330 ctrl.accept(0.1, 0.5);
331
332 let prop2 = ctrl.propose(0.1, 0.5, 5);
333
334 assert!((prop1.h_new - prop2.h_new).abs() < 1e-15);
335 }
336
337 #[test]
338 fn test_controller_with_order_1() {
339 let mut ctrl = PIController::<f64>::for_order(1);
340
341 let prop = ctrl.propose(0.1, 0.5, 1);
343 assert!(prop.accept);
344 }
345
346 #[test]
347 fn test_controller_large_error_multiple() {
348 let mut ctrl = PIController::<f64>::for_order(5);
349
350 for _ in 0..3 {
352 let prop = ctrl.propose(0.1, 100.0, 5);
353 assert!(!prop.accept);
354 assert!(prop.h_new >= 0.1 * ctrl.fac_min);
356 ctrl.reject(0.1, 100.0);
357 }
358 }
359
360 #[test]
361 fn test_step_proposal_fields() {
362 let prop = StepProposal {
363 h_new: 0.05,
364 accept: false,
365 };
366 assert!((prop.h_new - 0.05).abs() < 1e-15);
367 assert!(!prop.accept);
368 }
369
370 #[test]
371 fn test_custom_params() {
372 let ctrl = PIController::<f64>::with_params(
373 0.8, 0.1, 5.0, 0.5, 0.3, );
379
380 assert!((ctrl.safety - 0.8).abs() < 1e-15);
381 assert!((ctrl.fac_min - 0.1).abs() < 1e-15);
382 assert!((ctrl.fac_max - 5.0).abs() < 1e-15);
383 }
384}