#![warn(missing_docs)]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
pub struct ReadmeDocTests;
pub mod contact_adjoint;
pub mod multidual;
pub mod rollout;
pub mod symbolic;
pub use rollout::{
AdjointGradients, AdjointRollout, CollisionMesh, ContactSetup, FinalStateObjective,
GroundContact, N_INERTIA_PARAMS, adjoint_rollout_gradient, inertia_params, rollout_objective,
};
pub use contact_adjoint::{
ConvexAdjointError, ConvexAdjointGradients, ConvexContactRollout, convex_adjoint_gradient,
convex_rollout_objective,
};
use phyz_math::{DMat, DVec};
use phyz_model::{Model, State};
use phyz_rigid::aba;
#[derive(Debug, Clone)]
pub struct StepJacobians {
pub dqnext_dq: DMat,
pub dqnext_dv: DMat,
pub dvnext_dq: DMat,
pub dvnext_dv: DMat,
pub dvnext_dctrl: DMat,
}
fn set_col(m: &mut DMat, j: usize, col: &DVec) {
for i in 0..col.len() {
m[(i, j)] = col[i];
}
}
pub fn finite_diff_jacobians(model: &Model, state: &State, eps: f64) -> StepJacobians {
let nq = model.nq;
let nv = model.nv;
let dt = model.dt;
let (_q_nom, _v_nom) = semi_implicit_euler_step(model, state, dt);
let mut dqnext_dq = DMat::zeros(nq, nq);
let mut dqnext_dv = DMat::zeros(nq, nv);
let mut dvnext_dq = DMat::zeros(nv, nq);
let mut dvnext_dv = DMat::zeros(nv, nv);
let mut dvnext_dctrl = DMat::zeros(nv, nv);
let inv_2eps = 1.0 / (2.0 * eps);
for j in 0..nq {
let mut s_plus = state.clone();
s_plus.q[j] += eps;
let (qp, vp) = semi_implicit_euler_step(model, &s_plus, dt);
let mut s_minus = state.clone();
s_minus.q[j] -= eps;
let (qm, vm) = semi_implicit_euler_step(model, &s_minus, dt);
set_col(&mut dqnext_dq, j, &(&(&qp - &qm) * inv_2eps));
set_col(&mut dvnext_dq, j, &(&(&vp - &vm) * inv_2eps));
}
for j in 0..nv {
let mut s_plus = state.clone();
s_plus.v[j] += eps;
let (qp, vp) = semi_implicit_euler_step(model, &s_plus, dt);
let mut s_minus = state.clone();
s_minus.v[j] -= eps;
let (qm, vm) = semi_implicit_euler_step(model, &s_minus, dt);
set_col(&mut dqnext_dv, j, &(&(&qp - &qm) * inv_2eps));
set_col(&mut dvnext_dv, j, &(&(&vp - &vm) * inv_2eps));
}
for j in 0..nv {
let mut s_plus = state.clone();
s_plus.ctrl[j] += eps;
let (_, vp) = semi_implicit_euler_step(model, &s_plus, dt);
let mut s_minus = state.clone();
s_minus.ctrl[j] -= eps;
let (_, vm) = semi_implicit_euler_step(model, &s_minus, dt);
set_col(&mut dvnext_dctrl, j, &(&(&vp - &vm) * inv_2eps));
}
StepJacobians {
dqnext_dq,
dqnext_dv,
dvnext_dq,
dvnext_dv,
dvnext_dctrl,
}
}
#[deprecated(
since = "0.1.0",
note = "this is not analytical — the ABA block uses finite differences; use `semi_implicit_step_jacobians`, or `symbolic::symbolic_step_jacobians` for exact derivatives"
)]
pub fn analytical_step_jacobians(model: &Model, state: &State) -> StepJacobians {
semi_implicit_step_jacobians(model, state)
}
pub fn semi_implicit_step_jacobians(model: &Model, state: &State) -> StepJacobians {
let nq = model.nq;
let nv = model.nv;
let dt = model.dt;
let eps = 1e-7;
let inv_2eps = 1.0 / (2.0 * eps);
let _qdd_nom = aba(model, state);
let mut dqdd_dq = DMat::zeros(nv, nq);
let mut dqdd_dv = DMat::zeros(nv, nv);
let mut dqdd_dctrl = DMat::zeros(nv, nv);
for j in 0..nq {
let mut sp = state.clone();
sp.q[j] += eps;
let qddp = aba(model, &sp);
let mut sm = state.clone();
sm.q[j] -= eps;
let qddm = aba(model, &sm);
set_col(&mut dqdd_dq, j, &(&(&qddp - &qddm) * inv_2eps));
}
for j in 0..nv {
let mut sp = state.clone();
sp.v[j] += eps;
let qddp = aba(model, &sp);
let mut sm = state.clone();
sm.v[j] -= eps;
let qddm = aba(model, &sm);
set_col(&mut dqdd_dv, j, &(&(&qddp - &qddm) * inv_2eps));
}
for j in 0..nv {
let mut sp = state.clone();
sp.ctrl[j] += eps;
let qddp = aba(model, &sp);
let mut sm = state.clone();
sm.ctrl[j] -= eps;
let qddm = aba(model, &sm);
set_col(&mut dqdd_dctrl, j, &(&(&qddp - &qddm) * inv_2eps));
}
let i_nv = DMat::identity(nv);
let i_nq = DMat::identity(nq);
let dvnext_dq = dqdd_dq.scale(dt);
let dvnext_dv = &i_nv + &dqdd_dv.scale(dt);
let dvnext_dctrl = dqdd_dctrl.scale(dt);
let dqnext_dq = &i_nq + &dvnext_dq.scale(dt);
let dqnext_dv = dvnext_dv.scale(dt);
StepJacobians {
dqnext_dq,
dqnext_dv,
dvnext_dq,
dvnext_dv,
dvnext_dctrl,
}
}
fn semi_implicit_euler_step(model: &Model, state: &State, dt: f64) -> (DVec, DVec) {
let qdd = aba(model, state);
let v_new = &state.v + &(&qdd * dt);
let q_new = &state.q + &(&v_new * dt);
(q_new, v_new)
}
#[cfg(test)]
mod tests {
use super::*;
use phyz_math::{GRAVITY, Mat3, SpatialInertia, SpatialTransform, Vec3};
use phyz_model::ModelBuilder;
fn make_pendulum() -> Model {
ModelBuilder::new()
.gravity(Vec3::new(0.0, -GRAVITY, 0.0))
.dt(0.001)
.add_revolute_body(
"link1",
-1,
SpatialTransform::identity(),
SpatialInertia::new(
1.0,
Vec3::new(0.0, -0.5, 0.0),
Mat3::from_diagonal(&Vec3::new(1.0 / 12.0, 0.0, 1.0 / 12.0)),
),
)
.build()
}
#[test]
fn test_jacobians_match() {
let model = make_pendulum();
let mut state = model.default_state();
state.q[0] = 0.3;
state.v[0] = 0.1;
let fd = finite_diff_jacobians(&model, &state, 1e-6);
let an = semi_implicit_step_jacobians(&model, &state);
let eps = 1e-4;
assert!(
(&fd.dqnext_dq - &an.dqnext_dq).norm() < eps,
"dqnext_dq mismatch: fd={:?}, an={:?}",
fd.dqnext_dq,
an.dqnext_dq
);
assert!(
(&fd.dvnext_dq - &an.dvnext_dq).norm() < eps,
"dvnext_dq mismatch"
);
assert!(
(&fd.dvnext_dv - &an.dvnext_dv).norm() < eps,
"dvnext_dv mismatch"
);
assert!(
(&fd.dvnext_dctrl - &an.dvnext_dctrl).norm() < eps,
"dvnext_dctrl mismatch"
);
}
}