1use phyz_math::DVec;
9use phyz_model::{Model, State};
10
11#[derive(Debug, Clone)]
18pub struct PiController {
19 pub tolerance: f64,
21 pub alpha: f64,
23 pub beta: f64,
25 pub dt_min: f64,
27 pub dt_max: f64,
29 prev_error: f64,
31}
32
33impl PiController {
34 pub fn new(tolerance: f64, dt_min: f64, dt_max: f64) -> Self {
36 Self {
37 tolerance,
38 alpha: 0.4,
39 beta: 0.2,
40 dt_min,
41 dt_max,
42 prev_error: tolerance, }
44 }
45
46 pub fn adjust(&mut self, current_dt: f64, error: f64) -> (f64, bool) {
50 let safety = 0.9;
52
53 let p_term = if error > 1e-14 {
55 (self.tolerance / error).powf(self.alpha)
56 } else {
57 2.0 };
59
60 let i_term = if self.prev_error > 1e-14 {
62 (self.tolerance / self.prev_error).powf(self.beta)
63 } else {
64 1.0
65 };
66
67 let factor = safety * p_term * i_term;
68
69 let factor = factor.clamp(0.1, 5.0);
71
72 let new_dt = (current_dt * factor).clamp(self.dt_min, self.dt_max);
73
74 let accept = error <= self.tolerance;
76
77 self.prev_error = error;
78
79 (new_dt, accept)
80 }
81
82 pub fn reset(&mut self) {
84 self.prev_error = self.tolerance;
85 }
86}
87
88pub struct EmbeddedRkError;
93
94impl EmbeddedRkError {
95 pub fn estimate(
99 model: &Model,
100 state: &State,
101 derivatives_fn: impl Fn(&Model, &State) -> (DVec, DVec),
102 ) -> f64 {
103 let dt = model.dt;
104
105 let state_rk4 = Self::rk4_step(model, state, dt, &derivatives_fn);
107
108 let state_rk5 = Self::rk5_step(model, state, dt, &derivatives_fn);
110
111 let mut max_error = 0.0_f64;
113
114 for i in 0..state.q.len() {
115 let err = (state_rk4.q[i] - state_rk5.q[i]).abs();
116 let scale = state_rk4.q[i].abs().max(1e-6);
117 max_error = max_error.max(err / scale);
118 }
119
120 for i in 0..state.v.len() {
121 let err = (state_rk4.v[i] - state_rk5.v[i]).abs();
122 let scale = state_rk4.v[i].abs().max(1e-6);
123 max_error = max_error.max(err / scale);
124 }
125
126 max_error
127 }
128
129 fn rk4_step(
130 model: &Model,
131 state: &State,
132 dt: f64,
133 derivatives_fn: &impl Fn(&Model, &State) -> (DVec, DVec),
134 ) -> State {
135 let mut result = state.clone();
136
137 let (dq1, dv1) = derivatives_fn(model, state);
139
140 let mut s2 = state.clone();
142 s2.q += &dq1 * (dt / 2.0);
143 s2.v += &dv1 * (dt / 2.0);
144 let (dq2, dv2) = derivatives_fn(model, &s2);
145
146 let mut s3 = state.clone();
148 s3.q += &dq2 * (dt / 2.0);
149 s3.v += &dv2 * (dt / 2.0);
150 let (dq3, dv3) = derivatives_fn(model, &s3);
151
152 let mut s4 = state.clone();
154 s4.q += &dq3 * dt;
155 s4.v += &dv3 * dt;
156 let (dq4, dv4) = derivatives_fn(model, &s4);
157
158 result.q += &(&dq1 + &dq2 * 2.0 + &dq3 * 2.0 + &dq4) * (dt / 6.0);
160 result.v += &(&dv1 + &dv2 * 2.0 + &dv3 * 2.0 + &dv4) * (dt / 6.0);
161
162 result
163 }
164
165 fn rk5_step(
166 model: &Model,
167 state: &State,
168 dt: f64,
169 derivatives_fn: &impl Fn(&Model, &State) -> (DVec, DVec),
170 ) -> State {
171 let mut result = state.clone();
172
173 let (dq1, dv1) = derivatives_fn(model, state);
176
177 let mut s2 = state.clone();
179 s2.q += &dq1 * (dt / 5.0);
180 s2.v += &dv1 * (dt / 5.0);
181 let (dq2, dv2) = derivatives_fn(model, &s2);
182
183 let mut s3 = state.clone();
185 s3.q += &(&dq1 * (3.0 / 40.0) + &dq2 * (9.0 / 40.0)) * dt;
186 s3.v += &(&dv1 * (3.0 / 40.0) + &dv2 * (9.0 / 40.0)) * dt;
187 let (dq3, dv3) = derivatives_fn(model, &s3);
188
189 let mut s4 = state.clone();
191 s4.q += &(&dq1 * (3.0 / 10.0) - &dq2 * (9.0 / 10.0) + &dq3 * (6.0 / 5.0)) * dt;
192 s4.v += &(&dv1 * (3.0 / 10.0) - &dv2 * (9.0 / 10.0) + &dv3 * (6.0 / 5.0)) * dt;
193 let (dq4, dv4) = derivatives_fn(model, &s4);
194
195 let mut s5 = state.clone();
197 s5.q += &(&dq1 * (-11.0 / 54.0) + &dq2 * (5.0 / 2.0) - &dq3 * (70.0 / 27.0)
198 + &dq4 * (35.0 / 27.0))
199 * dt;
200 s5.v += &(&dv1 * (-11.0 / 54.0) + &dv2 * (5.0 / 2.0) - &dv3 * (70.0 / 27.0)
201 + &dv4 * (35.0 / 27.0))
202 * dt;
203 let (dq5, dv5) = derivatives_fn(model, &s5);
204
205 result.q += &(&dq1 * (37.0 / 378.0)
207 + &dq3 * (250.0 / 621.0)
208 + &dq4 * (125.0 / 594.0)
209 + &dq5 * (512.0 / 1771.0))
210 * dt;
211 result.v += &(&dv1 * (37.0 / 378.0)
212 + &dv3 * (250.0 / 621.0)
213 + &dv4 * (125.0 / 594.0)
214 + &dv5 * (512.0 / 1771.0))
215 * dt;
216
217 result
218 }
219}
220
221pub struct AdaptiveTimeStep {
225 pub controller: PiController,
226 pub rejected_steps: usize,
228 pub accepted_steps: usize,
230}
231
232impl AdaptiveTimeStep {
233 pub fn new(tolerance: f64, dt_min: f64, dt_max: f64) -> Self {
235 Self {
236 controller: PiController::new(tolerance, dt_min, dt_max),
237 rejected_steps: 0,
238 accepted_steps: 0,
239 }
240 }
241
242 pub fn step<F>(&mut self, model: &mut Model, state: &mut State, step_fn: F) -> (bool, f64)
246 where
247 F: Fn(&Model, &mut State),
248 {
249 let error = EmbeddedRkError::estimate(model, state, |m, s| {
251 use phyz_rigid::aba;
252 let qdd = aba(m, s);
253 (s.v.clone(), qdd)
254 });
255
256 let (new_dt, accept) = self.controller.adjust(model.dt, error);
258
259 if accept {
260 step_fn(model, state);
262 self.accepted_steps += 1;
263 } else {
264 self.rejected_steps += 1;
266 }
267
268 model.dt = new_dt;
270
271 (accept, new_dt)
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_pi_controller() {
281 let mut controller = PiController::new(1e-4, 1e-6, 1e-2);
282
283 let (new_dt, accept) = controller.adjust(0.001, 1e-5);
285 assert!(accept);
286 assert!(new_dt > 0.001);
287
288 let (new_dt, accept) = controller.adjust(0.001, 1e-3);
290 assert!(!accept);
291 assert!(new_dt < 0.001);
292 }
293
294 #[test]
295 fn test_pi_controller_clamping() {
296 let mut controller = PiController::new(1e-4, 1e-5, 1e-3);
297
298 let (new_dt, _) = controller.adjust(1e-3, 1e-10);
300 assert!((new_dt - 1e-3).abs() < 1e-10);
301
302 let (new_dt, _) = controller.adjust(1e-5, 1.0);
304 assert!((new_dt - 1e-5).abs() < 1e-10);
305 }
306}