Fixed event handling to properly keep the state changes, plus a few other fixes
This commit is contained in:
1 parent
55884190fa
commit
c5768ad9c0
8 files changed
+1064
-83
No files matched your search
@@ -23,7 +23,8 @@ See [benchmark report](VERN7_BENCHMARK_REPORT.md) for detailed performance analy
|
||||
### Other Features
|
||||
|
||||
- **Adaptive time stepping** with PI controller
|
||||
- **Callback events** with zero-crossing detection
|
||||
- **Callback events** with zero-crossing detection, whose effects can change the state, change the
|
||||
parameters, or end the solution at the event
|
||||
- **Dense output interpolation** at any time point
|
||||
- **Parameters** in derivative and callback functions
|
||||
- **Lazy computation** of extra interpolation stages (Vern7)
|
||||
@@ -71,19 +72,68 @@ let ode = ODE::new(&derivative, 0.0, 6.3, y0, params);
|
||||
let dp45 = DormandPrince45::new().a_tol(1e-12).r_tol(1e-6);
|
||||
let controller = PIController::default();
|
||||
|
||||
let value_too_high = FnCallback {
|
||||
let mut value_too_high = FnCallback {
|
||||
event: &|t: f64, _y: &Vector2<f64>, _p: &Params| { 5.0 - t },
|
||||
effect: &stop,
|
||||
};
|
||||
|
||||
// Solve the problem
|
||||
let mut problem = Problem::new(ode, dp45, controller).with_callback(&value_too_high);
|
||||
let mut problem = Problem::new(ode, dp45, controller).with_callback(&mut value_too_high);
|
||||
let solution = problem.solve();
|
||||
|
||||
// Can interpolate solutions to whatever you want
|
||||
let _interpolated_answer = solution.interpolate(4.4);
|
||||
```
|
||||
|
||||
## Events that change the state
|
||||
|
||||
A callback watches a function of the state for a zero crossing. When one is found the step is
|
||||
shortened to land on the crossing, the state there is recorded, and the effect runs with the ODE at
|
||||
that point — so the effect can move the state, and the integration continues from where it leaves it.
|
||||
|
||||
That is what it takes to integrate through a discontinuity. A rotating body parameterized by
|
||||
Modified Rodrigues Parameters, for instance, has a singularity at a full turn, avoided by swapping
|
||||
the parameter vector for `-sigma / |sigma|^2` — the same attitude, different parameters — whenever
|
||||
`|sigma|` passes 1:
|
||||
|
||||
```rust
|
||||
use nalgebra::{Matrix3, Vector3};
|
||||
use ordinary_diffeq::prelude::*;
|
||||
|
||||
/// MRP kinematics for a constant angular velocity.
|
||||
fn attitude_rate(_t: f64, s: Vector3<f64>, omega: &Vector3<f64>) -> Vector3<f64> {
|
||||
let skew = Matrix3::new(0.0, -s[2], s[1], s[2], 0.0, -s[0], -s[1], s[0], 0.0);
|
||||
let b = Matrix3::identity() * (1.0 - s.dot(&s)) + skew * 2.0 + s * s.transpose() * 2.0;
|
||||
b * omega * 0.25
|
||||
}
|
||||
|
||||
let mut shadow_set = FnCallback {
|
||||
event: &|_t: f64, s: &Vector3<f64>, _p: &Vector3<f64>| 1.0 - s.norm(),
|
||||
effect: &|ode: &mut ODE<'_, f64, 3, Vector3<f64>>| {
|
||||
ode.y = -ode.y / ode.y.norm_squared();
|
||||
},
|
||||
};
|
||||
|
||||
let ode = ODE::new(&attitude_rate, 0.0, 5.0, Vector3::zeros(), Vector3::new(0.0, 0.0, 1.0));
|
||||
let vern7 = Vern7::new().a_tol(1e-12).r_tol(1e-12);
|
||||
let mut problem = Problem::new(ode, vern7, PIController::default())
|
||||
.with_callback(&mut shadow_set);
|
||||
let solution = problem.solve();
|
||||
|
||||
// The parameters never approach the singularity, having swapped at half a turn.
|
||||
assert!(solution.states.iter().all(|s| s.norm() <= 1.0 + 1e-9));
|
||||
```
|
||||
|
||||
A jump like that one puts the same time in the solution twice, holding the state on each side of it,
|
||||
and makes the step size controller forget a history that described the solution before the jump.
|
||||
Interpolating at the jump reports the state the effect left, the one the trajectory goes on with.
|
||||
`effect` takes `&mut self`, so a callback that implements the trait itself rather than going through
|
||||
`FnCallback` can record that it has fired. `stop` ends the solution at the crossing, with the
|
||||
crossing as its last point and the problem's own `t_end` left alone.
|
||||
|
||||
If two events fall inside one step the earlier one fires and the step ends there; the later one is
|
||||
found again on the step after it.
|
||||
|
||||
## Choosing the integrator at runtime
|
||||
|
||||
The integrator is normally a type parameter, fixed when the code is compiled. `Method` is an enum
|
||||
|
||||
Reference in new issue
Block a user