use phyz_math::DVec;
use phyz_model::{Model, State};
pub type ForceFn = dyn Fn(&Model, &State) -> DVec;
pub struct RRespaIntegrator {
pub dt_inner: f64,
pub dt_outer: f64,
}
impl RRespaIntegrator {
pub fn new(dt_inner: f64, dt_outer: f64) -> Self {
assert!(dt_outer >= dt_inner, "dt_outer must be >= dt_inner");
assert!(
(dt_outer / dt_inner).fract() < 1e-10,
"dt_outer must be integer multiple of dt_inner"
);
Self { dt_inner, dt_outer }
}
pub fn step(
&self,
model: &Model,
state: &mut State,
slow_forces: &ForceFn,
fast_forces: &ForceFn,
) {
let n_inner = (self.dt_outer / self.dt_inner).round() as usize;
let f_slow = slow_forces(model, state);
let acc_slow = self.forces_to_acceleration(model, &f_slow);
state.v += &acc_slow * (self.dt_outer / 2.0);
for _ in 0..n_inner {
let f_fast = fast_forces(model, state);
let acc_fast = self.forces_to_acceleration(model, &f_fast);
state.v += &acc_fast * self.dt_inner;
state.q += &state.v * self.dt_inner;
}
let f_slow = slow_forces(model, state);
let acc_slow = self.forces_to_acceleration(model, &f_slow);
state.v += &acc_slow * (self.dt_outer / 2.0);
state.time += self.dt_outer;
}
fn forces_to_acceleration(&self, _model: &Model, forces: &DVec) -> DVec {
forces.clone()
}
}
pub fn split_forces_gravity(model: &Model, state: &State) -> (DVec, DVec) {
use phyz_rigid::{aba, forward_kinematics};
let (xforms, _) = forward_kinematics(model, state);
let mut gravity_forces = DVec::zeros(model.nv);
for (i, body) in model.bodies.iter().enumerate() {
let mass = body.inertia.mass;
let com_local = body.inertia.com;
let xf = &xforms[i];
let _com_world = xf.rot.transpose() * com_local + xf.pos;
let f_gravity = model.gravity * mass;
if i < model.nv {
gravity_forces[i] = f_gravity.norm();
}
}
let total_acc = aba(model, state);
let total_forces = &total_acc * model.dt;
let fast_forces = &total_forces - &gravity_forces;
(gravity_forces, fast_forces)
}
pub fn split_forces_stiffness(
_model: &Model,
_state: &State,
_stiffness_threshold: f64,
) -> (Box<ForceFn>, Box<ForceFn>) {
let slow: Box<ForceFn> = Box::new(move |_m, _s| {
DVec::zeros(_m.nv)
});
let fast: Box<ForceFn> = Box::new(move |_m, _s| {
DVec::zeros(_m.nv)
});
(slow, fast)
}
#[cfg(test)]
mod tests {
use super::*;
use phyz_math::{GRAVITY, Mat3, SpatialInertia, SpatialTransform, Vec3};
use phyz_model::ModelBuilder;
#[test]
fn test_rrespa_timestep_ratio() {
let integrator = RRespaIntegrator::new(0.001, 0.01);
assert_eq!((integrator.dt_outer / integrator.dt_inner).round(), 10.0);
}
#[test]
#[should_panic(expected = "dt_outer must be >= dt_inner")]
fn test_rrespa_invalid_dt() {
RRespaIntegrator::new(0.01, 0.001);
}
#[test]
fn test_rrespa_step() {
let model = ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.01)
.add_revolute_body(
"pendulum",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
1.0,
Vec3::new(0.0, -0.5, 0.0),
Mat3::from_diagonal(&Vec3::new(0.083, 0.0, 0.083)),
),
)
.build();
let mut state = model.default_state();
state.q[0] = 0.1;
let integrator = RRespaIntegrator::new(0.001, 0.01);
let slow_forces: Box<ForceFn> = Box::new(|m, _s| {
let mut f = DVec::zeros(m.nv);
f[0] = -m.gravity.norm() * 0.5; f
});
let fast_forces: Box<ForceFn> = Box::new(|m, _s| {
DVec::zeros(m.nv)
});
integrator.step(
&model,
&mut state,
slow_forces.as_ref(),
fast_forces.as_ref(),
);
assert!(state.time > 0.0);
}
}