extern crate nalgebra as na;
use crate::math_functions::*;
use na::{
Isometry3, Matrix1, Matrix3, Matrix4, Matrix6, Quaternion, SMatrix, SVector, Translation3,
UnitQuaternion, Vector1, Vector3, Vector6, U6,
};
#[derive(Clone, Debug)]
pub enum Axis {
X,
Y,
Z,
}
#[derive(Clone, Debug)]
pub enum JointType {
Revolute(Axis),
Prismatic(Axis),
SixDOF,
}
fn joint_dim(joint_type: &JointType) -> usize {
match joint_type {
JointType::Revolute(_) | JointType::Prismatic(_) => 1,
JointType::SixDOF => 6,
}
}
fn mass_properties_from_mass6(mass6: &Matrix6<f64>) -> (f64, Vector3<f64>) {
let mass = mass6.fixed_view::<3, 3>(0, 0).trace() / 3.0;
let r_com = if mass.abs() > f64::EPSILON {
let skew_r = -mass6.fixed_view::<3, 3>(0, 3).into_owned() / mass;
Vector3::new(skew_r[(2, 1)], skew_r[(0, 2)], skew_r[(1, 0)])
} else {
Vector3::zeros()
};
(mass, r_com)
}
#[derive(Clone, Debug)]
pub struct Topology {
pub offset_matrices: Vec<Isometry3<f64>>, pub joint_types: Vec<JointType>,
pub parent: Vec<u16>,
}
#[derive(Clone, Debug, Default)]
pub struct LinkProperties {
pub mass6: Option<Matrix6<f64>>,
pub added_mass6: Option<Matrix6<f64>>,
pub mass: Option<f64>,
pub r_com: Option<Vector3<f64>>,
pub inertia3: Option<Matrix3<f64>>,
pub volume: Option<f64>,
pub r_cob: Option<Vector3<f64>>,
}
#[derive(Clone, Debug)]
pub struct Environment {
pub gravity: Vector3<f64>,
pub rho: f64,
}
impl Default for Environment {
fn default() -> Self {
Environment {
gravity: Vector3::zeros(),
rho: 1025.0,
}
}
}
#[derive(Clone, Debug)]
pub struct MultiBodyConfig<const NUM_BODIES: usize, const NUM_DOFS: usize> {
pub topology: Topology,
pub link_props: Option<Vec<LinkProperties>>,
pub env: Environment,
}
pub type BodyRegressorFn<'a, const NUM_PARAMS: usize> = dyn Fn(
&Isometry3<f64>, &Vector6<f64>, &Vector6<f64>, &Vector6<f64>, ) -> SMatrix<f64, 6, NUM_PARAMS>
+ 'a;
#[derive(Clone, Debug)]
pub enum JointKinArg {
Scalar(f64),
SixDOF(Vector6<f64>),
}
#[derive(Clone, Debug)]
pub enum JointRegressorOut<const NUM_PARAMS: usize> {
Row(SMatrix<f64, 1, NUM_PARAMS>),
Matrix(SMatrix<f64, 6, NUM_PARAMS>),
}
pub type JointRegressorFn<'a, const NUM_PARAMS: usize> = dyn Fn(
&Isometry3<f64>, JointKinArg, JointKinArg, JointKinArg, ) -> JointRegressorOut<NUM_PARAMS>
+ 'a;
pub type RigidBodyForcesFn<'a, const NUM_BODIES: usize> =
dyn Fn(&[Isometry3<f64>], &[Vector6<f64>]) -> SMatrix<f64, 6, NUM_BODIES> + 'a;
pub trait IntoHomogeneousConfigurationVec {
fn into(&self) -> Vec<Isometry3<f64>>;
}
impl IntoHomogeneousConfigurationVec for Isometry3<f64> {
fn into(&self) -> Vec<Isometry3<f64>> {
vec![*self]
}
}
impl IntoHomogeneousConfigurationVec for Vec<Isometry3<f64>> {
fn into(&self) -> Vec<Isometry3<f64>> {
self.clone()
}
}
pub struct MultiBody<const NUM_BODIES: usize, const NUM_DOFS: usize> {
offset_matrices: Vec<Isometry3<f64>>,
mass_matrices: Vec<Matrix6<f64>>,
joint_types: Vec<JointType>,
parent: Vec<u16>,
ancestors: Vec<Vec<usize>>,
Phi: SMatrix<f64, 6, NUM_DOFS>,
joint_dims: SVector<usize, NUM_BODIES>,
joint_size_offsets: Vec<usize>,
gravity: Vector3<f64>,
r_com: Option<Vec<Vector3<f64>>>,
mass: Option<Vec<f64>>,
r_cob: Option<Vec<Vector3<f64>>>,
volume: Option<Vec<f64>>,
rho: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct DynamicsState<const NUM_BODIES: usize, const NUM_DOFS: usize> {
pub conf: Vec<Isometry3<f64>>,
pub mu: SVector<f64, NUM_DOFS>,
}
#[derive(Clone, Copy)]
pub struct DynamicsStepInput<'a, const NUM_BODIES: usize, const NUM_DOFS: usize> {
pub rigid_body_forces: &'a RigidBodyForcesFn<'a, NUM_BODIES>,
pub thruster_forces: &'a [Vector6<f64>],
pub eta: &'a SVector<f64, NUM_DOFS>,
pub lin_vel_current: &'a Vector3<f64>,
pub lin_accel_current: &'a Vector3<f64>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IntegrationMethod {
SemiImplicitEuler,
Rk4,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct IntegrationOptions {
pub dt: f64,
pub method: IntegrationMethod,
}
#[derive(Clone, Debug)]
pub struct ForwardDynamicsWorkspace<const NUM_BODIES: usize> {
h: Vec<Isometry3<f64>>,
Ad_h_inv_cache: Vec<Matrix6<f64>>,
nu: Vec<Vector6<f64>>,
alpha: Vec<Vector6<f64>>,
a_e: Vec<Vector3<f64>>,
b: Vec<Vector6<f64>>,
M_a: Vec<Matrix6<f64>>,
v_inv_scalar: Vec<f64>,
v_inv_matrix: Vec<Matrix6<f64>>,
U_scalar: Vec<Vector6<f64>>,
U_matrix: Vec<Matrix6<f64>>,
u_scalar: Vec<f64>,
u_matrix: Vec<Vector6<f64>>,
joint_is_sixdof: Vec<bool>,
}
impl<const NUM_BODIES: usize> ForwardDynamicsWorkspace<NUM_BODIES> {
pub fn new() -> Self {
Self {
h: vec![Isometry3::<f64>::identity(); NUM_BODIES],
Ad_h_inv_cache: vec![Matrix6::<f64>::zeros(); NUM_BODIES],
nu: vec![Vector6::<f64>::zeros(); NUM_BODIES],
alpha: vec![Vector6::<f64>::zeros(); NUM_BODIES],
a_e: vec![Vector3::<f64>::zeros(); NUM_BODIES],
b: vec![Vector6::<f64>::zeros(); NUM_BODIES],
M_a: vec![Matrix6::<f64>::zeros(); NUM_BODIES],
v_inv_scalar: vec![0.0; NUM_BODIES],
v_inv_matrix: vec![Matrix6::<f64>::zeros(); NUM_BODIES],
U_scalar: vec![Vector6::<f64>::zeros(); NUM_BODIES],
U_matrix: vec![Matrix6::<f64>::zeros(); NUM_BODIES],
u_scalar: vec![0.0; NUM_BODIES],
u_matrix: vec![Vector6::<f64>::zeros(); NUM_BODIES],
joint_is_sixdof: vec![false; NUM_BODIES],
}
}
}
impl<const NUM_BODIES: usize> Default for ForwardDynamicsWorkspace<NUM_BODIES> {
fn default() -> Self {
Self::new()
}
}
impl<const NUM_BODIES: usize, const NUM_DOFS: usize> TryFrom<MultiBodyConfig<NUM_BODIES, NUM_DOFS>>
for MultiBody<NUM_BODIES, NUM_DOFS>
{
type Error = &'static str;
fn try_from(cfg: MultiBodyConfig<NUM_BODIES, NUM_DOFS>) -> Result<Self, Self::Error> {
let nb = NUM_BODIES;
if cfg.topology.offset_matrices.len() != nb {
return Err("offset_matrices length mismatch");
}
if cfg.topology.joint_types.len() != nb {
return Err("joint_types length mismatch");
}
if cfg.topology.parent.len() != nb {
return Err("parent length mismatch");
}
for (i, parent_entry) in cfg.topology.parent.iter().enumerate() {
if *parent_entry == 0 {
continue;
}
let parent_idx = usize::from(*parent_entry - 1);
if parent_idx >= nb {
return Err("parent index out of range");
}
if parent_idx >= i {
return Err("parent must reference an earlier body");
}
}
let expected_dofs: usize = cfg.topology.joint_types.iter().map(joint_dim).sum();
if expected_dofs != NUM_DOFS {
return Err("NUM_DOFS must equal sum of joint dimensions");
}
let MultiBodyConfig {
topology,
link_props,
env,
} = cfg;
let Topology {
offset_matrices,
joint_types,
parent,
} = topology;
let mut joint_dims = SVector::<usize, NUM_BODIES>::zeros();
let mut Phi = SMatrix::<f64, 6, NUM_DOFS>::zeros();
let mut joint_size_offsets = 0;
let mut joint_offset_vec = vec![0; NUM_BODIES];
for i in 0..NUM_BODIES {
joint_offset_vec[i] = joint_size_offsets;
match &joint_types[i] {
JointType::Revolute(axis) => {
let Phi_i = match axis {
Axis::X => Vector6::new(0.0, 0.0, 0.0, 1.0, 0.0, 0.0),
Axis::Y => Vector6::new(0.0, 0.0, 0.0, 0.0, 1.0, 0.0),
Axis::Z => Vector6::new(0.0, 0.0, 0.0, 0.0, 0.0, 1.0),
};
Phi.fixed_view_mut::<6, 1>(0, i + joint_size_offsets)
.copy_from(&Phi_i);
joint_dims[i] = 1;
}
JointType::Prismatic(axis) => {
let Phi_i = match axis {
Axis::X => Vector6::new(1.0, 0.0, 0.0, 0.0, 0.0, 0.0),
Axis::Y => Vector6::new(0.0, 1.0, 0.0, 0.0, 0.0, 0.0),
Axis::Z => Vector6::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0),
};
Phi.fixed_view_mut::<6, 1>(0, i + joint_size_offsets)
.copy_from(&Phi_i);
joint_dims[i] = 1;
}
JointType::SixDOF => {
Phi.fixed_view_mut::<6, 6>(0, i + joint_size_offsets)
.copy_from(&Matrix6::identity());
joint_dims[i] = 6;
joint_size_offsets += joint_dims[i] - 1;
}
}
}
let link_props = link_props.unwrap_or_else(|| vec![LinkProperties::default(); NUM_BODIES]);
if link_props.len() != nb {
return Err("link_props length mismatch");
}
let mut mass_matrices = vec![Matrix6::zeros(); NUM_BODIES];
let mut mass_vec = vec![0.0f64; NUM_BODIES];
let mut r_com_vec = vec![Vector3::zeros(); NUM_BODIES];
let mut volume_vec = vec![0.0f64; NUM_BODIES];
let mut r_cob_vec = vec![Vector3::zeros(); NUM_BODIES];
for i in 0..NUM_BODIES {
let lp = &link_props[i];
let mut mm = if let Some(m6) = lp.mass6 {
let (mass_from_m6, r_com_from_m6) = mass_properties_from_mass6(&m6);
mass_vec[i] = lp.mass.unwrap_or(mass_from_m6);
r_com_vec[i] = lp.r_com.unwrap_or(r_com_from_m6);
m6
} else {
match (lp.mass, lp.r_com, lp.inertia3) {
(Some(m), Some(rc), Some(inertia3)) => {
mass_vec[i] = m;
r_com_vec[i] = rc;
let mut mass_matrix = Matrix6::zeros();
mass_matrix
.fixed_view_mut::<3, 3>(0, 0)
.copy_from(&(m * Matrix3::identity()));
mass_matrix
.fixed_view_mut::<3, 3>(0, 3)
.copy_from(&(-m * skew(&rc)));
mass_matrix
.fixed_view_mut::<3, 3>(3, 0)
.copy_from(&(m * skew(&rc)));
mass_matrix
.fixed_view_mut::<3, 3>(3, 3)
.copy_from(&inertia3);
mass_matrix
}
(None, None, None) => Matrix6::zeros(),
_ => return Err("mass, r_com, and inertia3 must all be provided together"),
}
};
if let Some(am) = lp.added_mass6 {
mm += am;
}
mass_matrices[i] = mm;
if let Some(v) = lp.volume {
volume_vec[i] = v;
}
if let Some(rcb) = lp.r_cob {
r_cob_vec[i] = rcb;
}
}
let parent_vec = parent; let ancestors_build = {
let mut anc: Vec<Vec<usize>> = vec![Vec::new(); NUM_BODIES];
for j in 0..NUM_BODIES {
let mut p = (parent_vec[j] as i32) - 1;
while p >= 0 {
anc[j].push(p as usize);
p = (parent_vec[p as usize] as i32) - 1;
}
anc[j].reverse();
}
anc
};
let volume = Some(volume_vec);
let r_cob = Some(r_cob_vec);
let rho = Some(env.rho);
Ok(MultiBody {
offset_matrices,
mass_matrices,
joint_types,
parent: parent_vec.clone(),
ancestors: ancestors_build,
Phi,
joint_dims,
joint_size_offsets: joint_offset_vec,
gravity: env.gravity,
r_com: Some(r_com_vec),
mass: Some(mass_vec),
r_cob,
volume,
rho,
})
}
}
impl<const NUM_BODIES: usize, const NUM_DOFS: usize> MultiBody<NUM_BODIES, NUM_DOFS> {
pub fn from_config(cfg: MultiBodyConfig<NUM_BODIES, NUM_DOFS>) -> Result<Self, &'static str> {
Self::try_from(cfg)
}
#[deprecated(note = "Use MultiBody::from_config(...) or TryFrom<MultiBodyConfig>")]
#[allow(clippy::too_many_arguments)]
pub fn new(
offset_matrices: Vec<Isometry3<f64>>,
mass_matrices: Option<Vec<Matrix6<f64>>>,
added_mass: Option<Vec<Matrix6<f64>>>,
inertia_matrices: Option<Vec<Matrix3<f64>>>,
joint_types: Vec<JointType>,
parent: Vec<u16>,
gravity: Vector3<f64>,
r_com: Option<Vec<Vector3<f64>>>,
r_cob: Option<Vec<Vector3<f64>>>,
mass: Option<Vec<f64>>,
volume: Option<Vec<f64>>,
rho: Option<f64>,
) -> Result<MultiBody<NUM_BODIES, NUM_DOFS>, &'static str> {
if let Some(values) = &mass_matrices {
if values.len() != NUM_BODIES {
return Err("mass_matrices length mismatch");
}
}
if let Some(values) = &added_mass {
if values.len() != NUM_BODIES {
return Err("added_mass length mismatch");
}
}
if let Some(values) = &inertia_matrices {
if values.len() != NUM_BODIES {
return Err("inertia_matrices length mismatch");
}
}
if let Some(values) = &r_com {
if values.len() != NUM_BODIES {
return Err("r_com length mismatch");
}
}
if let Some(values) = &r_cob {
if values.len() != NUM_BODIES {
return Err("r_cob length mismatch");
}
}
if let Some(values) = &mass {
if values.len() != NUM_BODIES {
return Err("mass length mismatch");
}
}
if let Some(values) = &volume {
if values.len() != NUM_BODIES {
return Err("volume length mismatch");
}
}
let topology = Topology {
offset_matrices,
joint_types,
parent,
};
let mut link_props = Vec::with_capacity(NUM_BODIES);
for i in 0..NUM_BODIES {
let lp = LinkProperties {
mass6: mass_matrices.as_ref().map(|v| v[i]),
added_mass6: added_mass.as_ref().map(|v| v[i]),
mass: mass.as_ref().map(|v| v[i]),
r_com: r_com.as_ref().map(|v| v[i]),
inertia3: inertia_matrices.as_ref().map(|v| v[i]),
volume: volume.as_ref().map(|v| v[i]),
r_cob: r_cob.as_ref().map(|v| v[i]),
};
link_props.push(lp);
}
let env = Environment {
gravity,
rho: rho.unwrap_or(1025.0),
};
let cfg = MultiBodyConfig::<NUM_BODIES, NUM_DOFS> {
topology,
link_props: Some(link_props),
env,
};
let mut multibody = MultiBody::<NUM_BODIES, NUM_DOFS>::try_from(cfg)?;
multibody.rho = rho;
Ok(multibody)
}
pub fn minimal_to_homogeneous_configuration<Configuration, const D: usize>(
&self,
six_dof_vars: &Configuration,
scalar_joint_vars: &SVector<f64, D>,
) -> Vec<Isometry3<f64>>
where
Configuration: IntoHomogeneousConfigurationVec,
{
self.try_minimal_to_homogeneous_configuration(six_dof_vars, scalar_joint_vars)
.expect("configuration coordinate count does not match topology")
}
pub fn try_minimal_to_homogeneous_configuration<Configuration, const D: usize>(
&self,
six_dof_vars: &Configuration,
scalar_joint_vars: &SVector<f64, D>,
) -> Result<Vec<Isometry3<f64>>, &'static str>
where
Configuration: IntoHomogeneousConfigurationVec,
{
let six_dof_vars = six_dof_vars.into();
let expected_six_dof = self
.joint_types
.iter()
.filter(|joint_type| matches!(joint_type, JointType::SixDOF))
.count();
if six_dof_vars.len() != expected_six_dof {
return Err("six_dof_vars length mismatch");
}
let expected_scalar_dofs = self
.joint_types
.iter()
.filter(|joint_type| !matches!(joint_type, JointType::SixDOF))
.map(joint_dim)
.sum::<usize>();
if D != expected_scalar_dofs {
return Err("scalar_joint_vars length mismatch");
}
let mut j = 0;
let mut k = 0;
let mut conf: Vec<Isometry3<f64>> = vec![Isometry3::identity(); NUM_BODIES];
for (i, conf_i) in conf.iter_mut().enumerate().take(NUM_BODIES) {
match &self.joint_types[i] {
JointType::Revolute(axis) => {
let mut temp = Isometry3::identity();
temp.rotation = match axis {
Axis::X => UnitQuaternion::from_axis_angle(
&Vector3::x_axis(),
scalar_joint_vars[j],
),
Axis::Y => UnitQuaternion::from_axis_angle(
&Vector3::y_axis(),
scalar_joint_vars[j],
),
Axis::Z => UnitQuaternion::from_axis_angle(
&Vector3::z_axis(),
scalar_joint_vars[j],
),
};
*conf_i = temp;
j += 1;
}
JointType::Prismatic(axis) => {
let mut temp = Isometry3::identity();
temp.translation = match axis {
Axis::X => Translation3::new(scalar_joint_vars[j], 0.0, 0.0),
Axis::Y => Translation3::new(0.0, scalar_joint_vars[j], 0.0),
Axis::Z => Translation3::new(0.0, 0.0, scalar_joint_vars[j]),
};
*conf_i = temp;
j += 1;
}
JointType::SixDOF => {
*conf_i = six_dof_vars[k];
k += 1;
}
}
}
Ok(conf)
}
pub fn step_dynamics(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
options: IntegrationOptions,
) -> DynamicsState<NUM_BODIES, NUM_DOFS> {
match self.try_step_dynamics(state, input, options) {
Ok(state) => state,
Err(err) => panic!("{}", err),
}
}
pub fn try_step_dynamics(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
options: IntegrationOptions,
) -> Result<DynamicsState<NUM_BODIES, NUM_DOFS>, &'static str> {
self.validate_dynamics_step(state, input, options)?;
let mut workspace = ForwardDynamicsWorkspace::<NUM_BODIES>::new();
let next_state = match options.method {
IntegrationMethod::SemiImplicitEuler => {
self.step_dynamics_euler(state, input, options.dt, &mut workspace)
}
IntegrationMethod::Rk4 => {
self.step_dynamics_rk4(state, input, options.dt, &mut workspace)
}
};
Ok(next_state)
}
fn validate_dynamics_step(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
options: IntegrationOptions,
) -> Result<(), &'static str> {
if !options.dt.is_finite() || options.dt < 0.0 {
return Err("dt must be finite and non-negative");
}
if state.conf.len() != NUM_BODIES {
return Err("conf length mismatch");
}
if input.thruster_forces.len() != NUM_BODIES {
return Err("thruster_forces length mismatch");
}
Ok(())
}
fn step_dynamics_euler(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
dt: f64,
workspace: &mut ForwardDynamicsWorkspace<NUM_BODIES>,
) -> DynamicsState<NUM_BODIES, NUM_DOFS> {
let acceleration = self.dynamics_acceleration(state, input, workspace);
let mu = state.mu + dt * acceleration;
let conf = self.advance_configuration(&state.conf, &mu, dt);
DynamicsState { conf, mu }
}
fn step_dynamics_rk4(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
dt: f64,
workspace: &mut ForwardDynamicsWorkspace<NUM_BODIES>,
) -> DynamicsState<NUM_BODIES, NUM_DOFS> {
let k1_mu = self.dynamics_acceleration(state, input, workspace);
let k1_conf_velocity = state.mu;
let state2 = DynamicsState {
conf: self.advance_configuration(&state.conf, &k1_conf_velocity, 0.5 * dt),
mu: state.mu + 0.5 * dt * k1_mu,
};
let k2_mu = self.dynamics_acceleration(&state2, input, workspace);
let k2_conf_velocity = state2.mu;
let state3 = DynamicsState {
conf: self.advance_configuration(&state.conf, &k2_conf_velocity, 0.5 * dt),
mu: state.mu + 0.5 * dt * k2_mu,
};
let k3_mu = self.dynamics_acceleration(&state3, input, workspace);
let k3_conf_velocity = state3.mu;
let state4 = DynamicsState {
conf: self.advance_configuration(&state.conf, &k3_conf_velocity, dt),
mu: state.mu + dt * k3_mu,
};
let k4_mu = self.dynamics_acceleration(&state4, input, workspace);
let k4_conf_velocity = state4.mu;
let mu = state.mu + (dt / 6.0) * (k1_mu + 2.0 * k2_mu + 2.0 * k3_mu + k4_mu);
let conf = self.advance_configuration_rk4(
&state.conf,
&k1_conf_velocity,
&k2_conf_velocity,
&k3_conf_velocity,
&k4_conf_velocity,
dt,
);
DynamicsState { conf, mu }
}
fn dynamics_acceleration(
&self,
state: &DynamicsState<NUM_BODIES, NUM_DOFS>,
input: DynamicsStepInput<'_, NUM_BODIES, NUM_DOFS>,
workspace: &mut ForwardDynamicsWorkspace<NUM_BODIES>,
) -> SVector<f64, NUM_DOFS> {
self.forward_dynamics_ab_with_workspace(
&state.conf,
&state.mu,
input.rigid_body_forces,
input.thruster_forces,
input.eta,
input.lin_vel_current,
input.lin_accel_current,
workspace,
)
}
fn advance_configuration(
&self,
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
dt: f64,
) -> Vec<Isometry3<f64>> {
let mut next_conf = Vec::with_capacity(NUM_BODIES);
for (i, conf_i) in conf.iter().enumerate().take(NUM_BODIES) {
next_conf.push(*conf_i * self.joint_delta(i, mu, dt));
}
next_conf
}
fn advance_configuration_rk4(
&self,
conf: &[Isometry3<f64>],
k1: &SVector<f64, NUM_DOFS>,
k2: &SVector<f64, NUM_DOFS>,
k3: &SVector<f64, NUM_DOFS>,
k4: &SVector<f64, NUM_DOFS>,
dt: f64,
) -> Vec<Isometry3<f64>> {
let scalar_velocity = (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0;
let mut next_conf = Vec::with_capacity(NUM_BODIES);
for (i, conf_i) in conf.iter().enumerate().take(NUM_BODIES) {
match &self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => {
next_conf.push(*conf_i * self.joint_delta(i, &scalar_velocity, dt));
}
JointType::SixDOF => {
let idx = i + self.joint_size_offsets[i];
let delta = exp_se3(&(Self::six_dof_twist(k1, idx) * (dt / 6.0)))
* exp_se3(&(Self::six_dof_twist(k2, idx) * (dt / 3.0)))
* exp_se3(&(Self::six_dof_twist(k3, idx) * (dt / 3.0)))
* exp_se3(&(Self::six_dof_twist(k4, idx) * (dt / 6.0)));
next_conf.push(*conf_i * delta);
}
}
}
next_conf
}
fn joint_delta(&self, body_id: usize, mu: &SVector<f64, NUM_DOFS>, dt: f64) -> Isometry3<f64> {
let idx = body_id + self.joint_size_offsets[body_id];
match &self.joint_types[body_id] {
JointType::Revolute(axis) => {
let angle = mu[idx] * dt;
let rotation = match axis {
Axis::X => UnitQuaternion::from_axis_angle(&Vector3::x_axis(), angle),
Axis::Y => UnitQuaternion::from_axis_angle(&Vector3::y_axis(), angle),
Axis::Z => UnitQuaternion::from_axis_angle(&Vector3::z_axis(), angle),
};
Isometry3::from_parts(Translation3::identity(), rotation)
}
JointType::Prismatic(axis) => {
let distance = mu[idx] * dt;
let translation = match axis {
Axis::X => Translation3::new(distance, 0.0, 0.0),
Axis::Y => Translation3::new(0.0, distance, 0.0),
Axis::Z => Translation3::new(0.0, 0.0, distance),
};
Isometry3::from_parts(translation, UnitQuaternion::identity())
}
JointType::SixDOF => exp_se3(&(Self::six_dof_twist(mu, idx) * dt)),
}
}
fn six_dof_twist(mu: &SVector<f64, NUM_DOFS>, idx: usize) -> Vector6<f64> {
Vector6::from_column_slice(mu.rows(idx, 6).as_slice())
}
pub fn generalized_newton_euler(
&self,
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
mu_prime: &SVector<f64, NUM_DOFS>,
sigma_prime: &SVector<f64, NUM_DOFS>,
rigid_body_forces: impl Fn(&[Vector6<f64>], &[Vector6<f64>]) -> SMatrix<f64, 6, NUM_BODIES>,
eta: &SVector<f64, NUM_DOFS>,
) -> SVector<f64, NUM_DOFS> {
let mut w: Vec<Vector6<f64>> = vec![Vector6::zeros(); NUM_BODIES];
let mut zeta = SVector::<f64, NUM_DOFS>::zeros();
let mut h = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let mut alpha = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut nu = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut nu_prime = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut Ad_h_inv_cache = vec![Matrix6::zeros(); NUM_BODIES];
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
for i in 0..NUM_BODIES {
let idx = i + self.joint_size_offsets[i];
h[i] = self.offset_matrices[i] * conf[i];
Ad_h_inv_cache[i] = Ad_inv(&h[i]);
let Phi_i = self.Phi.columns(idx, self.joint_dims[i]);
let mu_i = mu.rows(idx, self.joint_dims[i]);
let mu_prime_i = mu_prime.rows(idx, self.joint_dims[i]);
let sigma_prime_i = sigma_prime.rows(idx, self.joint_dims[i]);
let v_i = Phi_i * mu_i;
let vdot_i = Phi_i * mu_prime_i;
let ad_v_i = ad_se3(&v_i);
let ad_vdot_i = ad_se3(&vdot_i);
if lambda(i) < 0 {
nu[i] = v_i; nu_prime[i] = vdot_i;
match self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => {
alpha[i] = ad_vdot_i * v_i + Phi_i * sigma_prime_i;
}
JointType::SixDOF => {
alpha[i] = ad_vdot_i * v_i
+ Phi_i
* (sigma_prime_i
+ ad_se3(&mu_i.fixed_rows::<6>(0).into()) * mu_prime_i);
}
}
} else {
let Ad_h_inv = Ad_h_inv_cache[i];
nu[i] = Ad_h_inv * nu[lambda(i) as usize] + v_i;
nu_prime[i] = Ad_h_inv * nu_prime[lambda(i) as usize] + vdot_i;
alpha[i] = Ad_h_inv * alpha[lambda(i) as usize]
+ Phi_i * sigma_prime_i
+ 0.5 * ad_v_i * vdot_i
- 0.5 * ad_v_i * nu_prime[i]
+ 0.5 * ad_se3(&nu[i]) * vdot_i;
alpha[i] += match self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => Vector6::zeros(),
JointType::SixDOF => {
let mu_i = mu_i.fixed_rows::<6>(0).into();
Phi_i * ad_se3(&mu_i) * mu_prime_i
}
}
}
let quat = UnitQuaternion::from_quaternion(*h[i].rotation.quaternion());
w[i] = self.mass_matrices[i] * alpha[i]
- 1.0 / 2.0 * ad_se3(&nu[i]).transpose() * self.mass_matrices[i] * nu_prime[i]
- 1.0 / 2.0 * ad_se3(&nu_prime[i]).transpose() * self.mass_matrices[i] * nu[i]
- self.compute_hydrostatic_force(&quat, &Vector3::zeros(), i);
}
let rigid_body_forces = rigid_body_forces(&nu, &nu_prime);
for i in (0..NUM_BODIES).rev() {
let idx = i + self.joint_size_offsets[i];
let Phi_i = self.Phi.columns(idx, self.joint_dims[i]);
let eta_i = eta.rows(idx, self.joint_dims[i]);
w[i] += rigid_body_forces.column(i);
let zeta_i = Phi_i.transpose() * w[i] - eta_i;
zeta.rows_mut(idx, self.joint_dims[i]).copy_from(&zeta_i);
if lambda(i) >= 0 {
w[lambda(i) as usize] =
w[lambda(i) as usize] + Ad_h_inv_cache[i].transpose() * w[i];
}
}
zeta
}
pub fn compute_mass_matrix(&self, conf: &[Isometry3<f64>]) -> SMatrix<f64, NUM_DOFS, NUM_DOFS> {
let mut M_c = self.mass_matrices.clone();
let mut M_o = SMatrix::<f64, NUM_DOFS, NUM_DOFS>::zeros();
let mut h = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let mut Ad_h_inv_cache = vec![Matrix6::zeros(); NUM_BODIES];
for i in 0..NUM_BODIES {
h[i] = self.offset_matrices[i] * conf[i];
Ad_h_inv_cache[i] = Ad_inv(&h[i]);
}
for i in (0..NUM_BODIES).rev() {
let lambda_i = self.parent[i] as i32 - 1;
let Ad_h_i_inv = Ad_h_inv_cache[i];
if lambda_i >= 0 {
M_c[lambda_i as usize] =
M_c[lambda_i as usize] + Ad_h_i_inv.transpose() * M_c[i] * Ad_h_i_inv;
}
let idx = i + self.joint_size_offsets[i];
if self.joint_dims[i] == 1 {
let phi_col = self.Phi.fixed_view::<6, 1>(0, idx);
let X = M_c[i] * phi_col;
let mass_scalar = phi_col.transpose() * X;
M_o[(idx, idx)] = mass_scalar[(0, 0)];
let mut j = i;
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
let mut X_prop = X;
while lambda(j) >= 0 {
X_prop = Ad_h_inv_cache[j].transpose() * X_prop;
j = lambda(j) as usize;
let idx_j = j + self.joint_size_offsets[j];
if self.joint_dims[j] == 1 {
let phi_j = self.Phi.fixed_view::<6, 1>(0, idx_j);
let temp = X_prop.transpose() * phi_j;
M_o[(idx, idx_j)] = temp[(0, 0)];
M_o[(idx_j, idx)] = temp[(0, 0)];
} else {
let phi_j = self.Phi.fixed_view::<6, 6>(0, idx_j);
let temp = X_prop.transpose() * phi_j;
M_o.view_mut((idx, idx_j), (1, 6)).copy_from(&temp);
M_o.view_mut((idx_j, idx), (6, 1))
.copy_from(&temp.transpose());
}
}
} else {
let phi_block = self.Phi.fixed_view::<6, 6>(0, idx);
let mut X = M_c[i] * phi_block; let self_block = phi_block.transpose() * X;
M_o.view_mut((idx, idx), (6, 6)).copy_from(&self_block);
let mut j = i;
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
while lambda(j) >= 0 {
X = Ad_h_inv_cache[j].transpose() * X;
j = lambda(j) as usize;
let idx_j = j + self.joint_size_offsets[j];
if self.joint_dims[j] == 1 {
let phi_j = self.Phi.fixed_view::<6, 1>(0, idx_j);
let temp = X.transpose() * phi_j; M_o.view_mut((idx, idx_j), (6, 1)).copy_from(&temp);
M_o.view_mut((idx_j, idx), (1, 6))
.copy_from(&temp.transpose());
} else {
let phi_j = self.Phi.fixed_view::<6, 6>(0, idx_j);
let temp = X.transpose() * phi_j; M_o.view_mut((idx, idx_j), (6, 6)).copy_from(&temp);
M_o.view_mut((idx_j, idx), (6, 6))
.copy_from(&temp.transpose());
}
}
}
}
M_o
}
#[allow(clippy::too_many_arguments)]
pub fn forward_dynamics_ab(
&self,
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
rigid_body_forces_func: impl Fn(
&[Isometry3<f64>],
&[Vector6<f64>],
) -> SMatrix<f64, 6, NUM_BODIES>,
thruster_forces: &[Vector6<f64>],
eta: &SVector<f64, NUM_DOFS>,
lin_vel_current: &Vector3<f64>,
lin_accel_current: &Vector3<f64>,
) -> SVector<f64, NUM_DOFS> {
let mut workspace = ForwardDynamicsWorkspace::<NUM_BODIES>::new();
self.forward_dynamics_ab_with_workspace(
conf,
mu,
rigid_body_forces_func,
thruster_forces,
eta,
lin_vel_current,
lin_accel_current,
&mut workspace,
)
}
#[allow(clippy::too_many_arguments)]
pub fn forward_dynamics_ab_with_workspace(
&self,
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
rigid_body_forces_func: impl Fn(
&[Isometry3<f64>],
&[Vector6<f64>],
) -> SMatrix<f64, 6, NUM_BODIES>,
thruster_forces: &[Vector6<f64>],
eta: &SVector<f64, NUM_DOFS>,
lin_vel_current: &Vector3<f64>,
lin_accel_current: &Vector3<f64>,
workspace: &mut ForwardDynamicsWorkspace<NUM_BODIES>,
) -> SVector<f64, NUM_DOFS> {
let h = &mut workspace.h;
let Ad_h_inv_cache = &mut workspace.Ad_h_inv_cache;
let nu = &mut workspace.nu;
let alpha = &mut workspace.alpha;
let a_e = &mut workspace.a_e;
let b = &mut workspace.b;
let M_a = &mut workspace.M_a;
let v_inv_scalar = &mut workspace.v_inv_scalar;
let v_inv_matrix = &mut workspace.v_inv_matrix;
let U_scalar = &mut workspace.U_scalar;
let U_matrix = &mut workspace.U_matrix;
let u_scalar = &mut workspace.u_scalar;
let u_matrix = &mut workspace.u_matrix;
let joint_is_sixdof = &mut workspace.joint_is_sixdof;
let mut sigma = SVector::<f64, NUM_DOFS>::zeros();
let mut nu_0 = Vector6::<f64>::zeros();
nu_0.fixed_view_mut::<3, 1>(0, 0)
.copy_from(&(-lin_vel_current));
let a_e0 = self.gravity - lin_accel_current;
a_e[0] = a_e0;
M_a.copy_from_slice(&self.mass_matrices);
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
for i in 0..NUM_BODIES {
let idx = i + self.joint_size_offsets[i];
h[i] = self.offset_matrices[i] * conf[i];
Ad_h_inv_cache[i] = Ad_inv(&h[i]);
let joint_velocity = if self.joint_dims[i] == 1 {
self.Phi.fixed_view::<6, 1>(0, idx) * mu[idx]
} else {
self.Phi.fixed_view::<6, 6>(0, idx) * mu.fixed_rows::<6>(idx)
};
if lambda(i) == -1 {
nu[i] = Ad_h_inv_cache[i] * nu_0 + joint_velocity;
a_e[i] = h[i].rotation.inverse() * a_e0;
} else {
nu[i] = Ad_h_inv_cache[i] * nu[lambda(i) as usize] + joint_velocity;
a_e[i] = h[i].rotation.inverse() * a_e[lambda(i) as usize];
}
b[i] = -ad_se3(&nu[i]).transpose() * M_a[i] * nu[i]
- self.compute_hydrostatic_force(&h[i].rotation, lin_accel_current, i)
- thruster_forces[i];
}
let rigid_body_forces = rigid_body_forces_func(&h[..], &nu[..]);
for i in (0..NUM_BODIES).rev() {
let idx = i + self.joint_size_offsets[i];
b[i] += -rigid_body_forces.column(i);
if self.joint_dims[i] == 1 {
let phi_col = self.Phi.fixed_view::<6, 1>(0, idx);
let U_i_col = M_a[i] * phi_col; let V_i_scalar = phi_col.transpose() * U_i_col; let u_i_scalar = eta[idx] - (phi_col.transpose() * b[i])[0];
let v_scalar = V_i_scalar[(0, 0)];
assert!(
v_scalar.is_finite() && v_scalar.abs() > f64::EPSILON,
"scalar joint matrix inversion failed"
);
let inv_scalar = 1.0 / v_scalar;
let v_i = phi_col * mu[idx];
if lambda(i) >= 0 {
let outer = U_i_col * U_i_col.transpose();
let M_bar = M_a[i] - outer * inv_scalar;
let b_bar =
b[i] + M_bar * ad_se3(&nu[i]) * v_i + U_i_col * (inv_scalar * u_i_scalar);
let Ad_h_i_inv = Ad_h_inv_cache[i];
M_a[lambda(i) as usize] =
M_a[lambda(i) as usize] + Ad_h_i_inv.transpose() * M_bar * Ad_h_i_inv;
b[lambda(i) as usize] = b[lambda(i) as usize] + Ad_h_i_inv.transpose() * b_bar;
}
v_inv_scalar[i] = inv_scalar;
U_scalar[i] = U_i_col;
u_scalar[i] = u_i_scalar;
joint_is_sixdof[i] = false;
} else {
let Phi_block = self.Phi.fixed_view::<6, 6>(0, idx);
let v_i = Phi_block * mu.fixed_rows::<6>(idx);
let U_i_block = M_a[i] * Phi_block; let V_i_block = Phi_block.transpose() * U_i_block;
let eta_block = eta.fixed_rows::<6>(idx).into_owned();
let u_i_block = eta_block - Phi_block.transpose() * b[i]; let V_i_inv_block = V_i_block
.try_inverse()
.expect("6x6 joint matrix inversion failed");
if lambda(i) >= 0 {
let M_bar = M_a[i] - U_i_block * V_i_inv_block * U_i_block.transpose();
let b_bar =
b[i] + M_bar * ad_se3(&nu[i]) * v_i + U_i_block * V_i_inv_block * u_i_block;
let Ad_h_i_inv = Ad_h_inv_cache[i];
M_a[lambda(i) as usize] =
M_a[lambda(i) as usize] + Ad_h_i_inv.transpose() * M_bar * Ad_h_i_inv;
b[lambda(i) as usize] = b[lambda(i) as usize] + Ad_h_i_inv.transpose() * b_bar;
}
v_inv_matrix[i] = V_i_inv_block;
U_matrix[i] = U_i_block;
u_matrix[i] = u_i_block;
joint_is_sixdof[i] = true;
}
}
let mut alpha_0 = Vector6::<f64>::zeros();
alpha_0
.fixed_view_mut::<3, 1>(0, 0)
.copy_from(&(-lin_accel_current));
for i in 0..NUM_BODIES {
let idx = i + self.joint_size_offsets[i];
let v_i = if self.joint_dims[i] == 6 {
self.Phi.fixed_view::<6, 6>(0, idx) * mu.fixed_rows::<6>(idx)
} else {
self.Phi.fixed_view::<6, 1>(0, idx) * mu[idx]
};
let Ad_h_i_inv = Ad_h_inv_cache[i];
let alpha_bar: SVector<f64, 6> = if lambda(i) == -1 {
Ad_h_i_inv * alpha_0 + ad_se3(&nu[i]) * v_i
} else {
Ad_h_i_inv * alpha[lambda(i) as usize] + ad_se3(&nu[i]) * v_i
};
if joint_is_sixdof[i] {
let temp = v_inv_matrix[i] * (u_matrix[i] - U_matrix[i].transpose() * alpha_bar);
sigma.rows_mut(idx, 6).copy_from(&temp);
alpha[i] = alpha_bar + self.Phi.fixed_view::<6, 6>(0, idx) * temp;
} else {
let correction = (U_scalar[i].transpose() * alpha_bar)[0];
let temp_scalar = v_inv_scalar[i] * (u_scalar[i] - correction);
sigma[(idx, 0)] = temp_scalar;
alpha[i] = alpha_bar + self.Phi.fixed_view::<6, 1>(0, idx) * temp_scalar;
}
}
sigma
}
pub fn compute_hydrostatic_force(
&self,
quat: &UnitQuaternion<f64>,
current_accel: &Vector3<f64>,
body_id: usize,
) -> Vector6<f64> {
let mut hydrostatic_force = Vector6::<f64>::zeros();
let Rot = quat.to_rotation_matrix();
let rho = self.rho.unwrap_or(0.0);
let volume = match &self.volume {
Some(volume) => volume[body_id],
None => 0.0,
};
let r_cob = match &self.r_cob {
Some(r_cob) => r_cob[body_id],
None => Vector3::<f64>::zeros(),
};
let mass = self.mass.as_ref().unwrap()[body_id];
let r_com = self.r_com.as_ref().unwrap()[body_id];
let linear =
(mass - rho * volume) * Rot.matrix().transpose() * (self.gravity - current_accel);
let rotational = (mass * skew(&r_com) - rho * volume * skew(&r_cob))
* Rot.matrix().transpose()
* (self.gravity - current_accel);
hydrostatic_force
.fixed_view_mut::<3, 1>(0, 0)
.copy_from(&linear);
hydrostatic_force
.fixed_view_mut::<3, 1>(3, 0)
.copy_from(&rotational);
hydrostatic_force
}
pub fn compute_body_configurations(&self, config: &[Isometry3<f64>]) -> Vec<Isometry3<f64>> {
let mut g = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
for i in 0..NUM_BODIES {
g[i] = self.offset_matrices[i] * config[i];
if lambda(i) >= 0 {
g[i] = g[lambda(i) as usize] * g[i];
}
}
g
}
pub fn compute_jacobians(&self, config: &[Isometry3<f64>]) -> Vec<SMatrix<f64, 6, NUM_DOFS>> {
let mut jacs = vec![SMatrix::<f64, 6, NUM_DOFS>::zeros(); NUM_BODIES];
let mut h = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let mut Ad_inv_cache = vec![Matrix6::zeros(); NUM_BODIES];
for i in 0..NUM_BODIES {
let idx_i = i + self.joint_size_offsets[i];
h[i] = self.offset_matrices[i] * config[i];
Ad_inv_cache[i] = Ad_inv(&h[i]);
let parent_i = self.parent[i] as i32 - 1;
if parent_i >= 0 {
jacs[i] = Ad_inv_cache[i] * jacs[parent_i as usize];
} else {
jacs[i].fill(0.0);
}
let Phi_i = self.Phi.view((0, idx_i), (6, self.joint_dims[i]));
jacs[i]
.view_mut((0, idx_i), (6, self.joint_dims[i]))
.copy_from(&Phi_i);
}
jacs
}
pub fn compute_jacobian_derivatives(
&self,
jacs: &[SMatrix<f64, 6, NUM_DOFS>],
config: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
) -> Vec<SMatrix<f64, 6, NUM_DOFS>> {
let mut jacobian_derivs = vec![SMatrix::<f64, 6, NUM_DOFS>::zeros(); NUM_BODIES];
let mut h = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let mut Ad_inv_cache = vec![Matrix6::zeros(); NUM_BODIES];
let mut phi_mu_cache: Vec<Vector6<f64>> = vec![Vector6::zeros(); NUM_BODIES];
for j in 0..NUM_BODIES {
h[j] = self.offset_matrices[j] * config[j];
Ad_inv_cache[j] = Ad_inv(&h[j]);
let idx_j = j + self.joint_size_offsets[j];
match self.joint_dims[j] {
1 => {
let col = self.Phi.column(idx_j);
phi_mu_cache[j] = col * mu[idx_j];
}
6 => {
let mu_block = mu.rows(idx_j, 6);
for r in 0..6 {
phi_mu_cache[j][r] = mu_block[r];
}
}
_ => unreachable!("Unsupported joint dimension"),
}
}
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
for j in 1..NUM_BODIES {
let parent = lambda(j);
if parent < 0 {
continue;
}
let Phi_q_mu_j = &phi_mu_cache[j];
let ad_phi_mu_j = ad_se3(Phi_q_mu_j);
let ad_phi_mu_j_jac_j = ad_phi_mu_j * jacs[j]; let parent = parent as usize;
for &i in &self.ancestors[j] {
let idx_i = i + self.joint_size_offsets[i];
let parent_block =
jacobian_derivs[parent].view((0, idx_i), (6, self.joint_dims[i]));
let djac_ji = Ad_inv_cache[j] * parent_block
- ad_phi_mu_j_jac_j.view((0, idx_i), (6, self.joint_dims[i]));
jacobian_derivs[j]
.view_mut((0, idx_i), (6, self.joint_dims[i]))
.copy_from(&djac_ji);
}
}
jacobian_derivs
}
pub fn compute_jacobian(
&self,
config: &[Isometry3<f64>],
body_id: usize,
) -> SMatrix<f64, 6, NUM_DOFS> {
let mut jacobian = SMatrix::<f64, 6, NUM_DOFS>::zeros();
let idx = body_id + self.joint_size_offsets[body_id];
let Phi_i = self.Phi.view((0, idx), (6, self.joint_dims[body_id]));
jacobian
.view_mut((0, idx), (6, self.joint_dims[body_id]))
.copy_from(&Phi_i);
let mut j = body_id;
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
let mut k = Isometry3::<f64>::identity();
let mut first = true;
while lambda(j) >= 0 {
let h_j = self.offset_matrices[j] * config[j];
if first {
k = h_j;
first = false;
} else {
k = h_j * k;
}
j = lambda(j) as usize;
let idx_j = j + self.joint_size_offsets[j];
let Phi_j = self.Phi.view((0, idx_j), (6, self.joint_dims[j]));
let Ad_k_inv = Ad_inv(&k); jacobian
.view_mut((0, idx_j), (6, self.joint_dims[j]))
.copy_from(&(Ad_k_inv * Phi_j));
}
jacobian
}
pub fn compute_jacobian_derivative(
&self,
config: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
body_id: usize,
) -> SMatrix<f64, 6, NUM_DOFS> {
let mut jacobian_deriv = SMatrix::<f64, 6, NUM_DOFS>::zeros();
let mut j = body_id;
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
let mut Ad_h_inv: SMatrix<f64, 6, 6>;
let mut nu = SVector::<f64, 6>::zeros();
let mut h = Isometry3::<f64>::identity();
while lambda(j) >= 0 {
let idx_j = j + self.joint_size_offsets[j];
let Phi_j = self.Phi.view((0, idx_j), (6, self.joint_dims[j]));
let mu_j = mu.rows(idx_j, self.joint_dims[j]);
let h_j = self.offset_matrices[j] * config[j];
if j == body_id {
nu = SMatrix::<f64, 6, 6>::identity() * Phi_j * mu_j;
h = h_j;
} else {
Ad_h_inv = Ad_inv(&h);
nu += Ad_h_inv * Phi_j * mu_j;
h = h_j * h;
}
j = lambda(j) as usize;
let idx_j = j + self.joint_size_offsets[j];
let Phi_j = self.Phi.view((0, idx_j), (6, self.joint_dims[j]));
let jac_j = -ad_se3(&nu) * Ad_inv(&h) * Phi_j;
jacobian_deriv
.view_mut((0, idx_j), (6, self.joint_dims[j]))
.copy_from(&jac_j);
}
jacobian_deriv
}
pub fn compute_regressor_matrix<'a, const NUM_PARAMS: usize>(
&self,
body_regressors: [&'a BodyRegressorFn<'a, NUM_PARAMS>; NUM_BODIES],
joint_regressors: [&'a JointRegressorFn<'a, NUM_PARAMS>; NUM_BODIES],
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
mu_bar: &SVector<f64, NUM_DOFS>,
sigma_bar: &SVector<f64, NUM_DOFS>,
) -> SMatrix<f64, NUM_DOFS, NUM_PARAMS> {
self.try_compute_regressor_matrix(
body_regressors,
joint_regressors,
conf,
mu,
mu_bar,
sigma_bar,
)
.expect("regressor input validation failed")
}
pub fn try_compute_regressor_matrix<'a, const NUM_PARAMS: usize>(
&self,
body_regressors: [&'a BodyRegressorFn<'a, NUM_PARAMS>; NUM_BODIES],
joint_regressors: [&'a JointRegressorFn<'a, NUM_PARAMS>; NUM_BODIES],
conf: &[Isometry3<f64>],
mu: &SVector<f64, NUM_DOFS>,
mu_bar: &SVector<f64, NUM_DOFS>,
sigma_bar: &SVector<f64, NUM_DOFS>,
) -> Result<SMatrix<f64, NUM_DOFS, NUM_PARAMS>, &'static str> {
if conf.len() != NUM_BODIES {
return Err("conf length mismatch");
}
let mut regressor = SMatrix::<f64, NUM_DOFS, NUM_PARAMS>::zeros();
let mut W: Vec<SMatrix<f64, 6, NUM_PARAMS>> =
vec![SMatrix::<f64, 6, NUM_PARAMS>::zeros(); NUM_BODIES];
let mut h = vec![Isometry3::<f64>::identity(); NUM_BODIES];
let mut alpha_bar = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut nu = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut nu_bar = vec![Vector6::<f64>::zeros(); NUM_BODIES];
let mut Ad_h_inv_cache = vec![Matrix6::zeros(); NUM_BODIES];
let g = self.compute_body_configurations(conf);
let lambda = |x: usize| -> i32 { self.parent[x] as i32 - 1 };
for i in 0..NUM_BODIES {
let idx = i + self.joint_size_offsets[i];
h[i] = self.offset_matrices[i] * conf[i];
Ad_h_inv_cache[i] = Ad_inv(&h[i]);
let Phi_i = self.Phi.columns(idx, self.joint_dims[i]);
let mu_i = mu.rows(idx, self.joint_dims[i]);
let mu_bar_i = mu_bar.rows(idx, self.joint_dims[i]);
let sigma_bar_i = sigma_bar.rows(idx, self.joint_dims[i]);
let v_i = Phi_i * mu_i;
let vdot_i = Phi_i * mu_bar_i;
let ad_v_i = ad_se3(&v_i);
let ad_vdot_i = ad_se3(&vdot_i);
if lambda(i) < 0 {
nu[i] = v_i; nu_bar[i] = vdot_i;
alpha_bar[i] = ad_vdot_i * v_i + Phi_i * sigma_bar_i;
alpha_bar[i] += match self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => Vector6::zeros(),
JointType::SixDOF => Phi_i * ad_se3(&mu_i.fixed_rows::<6>(0).into()) * mu_bar_i,
}
} else {
let Ad_h_inv = Ad_h_inv_cache[i];
nu[i] = Ad_h_inv * nu[lambda(i) as usize] + v_i;
nu_bar[i] = Ad_h_inv * nu_bar[lambda(i) as usize] + vdot_i;
alpha_bar[i] = Ad_h_inv * alpha_bar[lambda(i) as usize]
+ Phi_i * sigma_bar_i
+ 0.5 * ad_v_i * vdot_i
- 0.5 * ad_v_i * nu_bar[i]
+ 0.5 * ad_se3(&nu[i]) * vdot_i;
alpha_bar[i] += match self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => Vector6::zeros(),
JointType::SixDOF => {
let mu_i = mu_i.fixed_rows::<6>(0).into();
Phi_i * ad_se3(&mu_i) * mu_bar_i
}
};
}
W[i] = body_regressors[i](&g[i], &nu[i], &nu_bar[i], &alpha_bar[i]);
}
for i in (0..NUM_BODIES).rev() {
let idx = i + self.joint_size_offsets[i];
let Phi_i = self.Phi.columns(idx, self.joint_dims[i]);
let mut regressor_i = Phi_i.transpose() * W[i];
match self.joint_types[i] {
JointType::Revolute(_) | JointType::Prismatic(_) => {
let jr = joint_regressors[i](
&conf[i],
JointKinArg::Scalar(mu[idx]),
JointKinArg::Scalar(mu_bar[idx]),
JointKinArg::Scalar(sigma_bar[idx]),
);
if let JointRegressorOut::Row(row) = jr {
regressor_i += row;
} else {
return Err("scalar joint regressor returned Matrix");
}
}
JointType::SixDOF => {
let mu6 = Vector6::from_column_slice(mu.rows(idx, 6).as_slice());
let mu_bar6 = Vector6::from_column_slice(mu_bar.rows(idx, 6).as_slice());
let sigma_bar6 = Vector6::from_column_slice(sigma_bar.rows(idx, 6).as_slice());
let jr = joint_regressors[i](
&conf[i],
JointKinArg::SixDOF(mu6),
JointKinArg::SixDOF(mu_bar6),
JointKinArg::SixDOF(sigma_bar6),
);
if let JointRegressorOut::Matrix(matrix) = jr {
regressor_i += matrix; } else {
return Err("six_dof joint regressor returned Row");
}
}
}
regressor
.rows_mut(idx, self.joint_dims[i])
.copy_from(®ressor_i);
if lambda(i) >= 0 {
W[lambda(i) as usize] =
W[lambda(i) as usize] + Ad_h_inv_cache[i].transpose() * W[i];
}
}
Ok(regressor)
}
}