use crate::copp::constraints::{AsInputMatrix1D, Constraints, InputMatrix};
use crate::diag::{ConstraintError, CoppError, RobotDynamicsError};
use crate::path::Path;
use nalgebra::{Const, DMatrix, Dyn, Matrix, ViewStorage};
use std::f64::consts::SQRT_2;
pub trait UpperBound {
fn check_valid(&self, dim: usize) -> bool;
fn ncols(&self) -> usize;
fn as_matrix(&self) -> InputMatrix<'_>;
}
impl UpperBound for (&[f64], usize) {
#[inline(always)]
fn check_valid(&self, dim: usize) -> bool {
self.0.len() == dim
}
#[inline(always)]
fn ncols(&self) -> usize {
self.1
}
#[inline(always)]
fn as_matrix(&self) -> InputMatrix<'_> {
let dim = self.0.len();
let ncols = self.1;
unsafe {
let storage = ViewStorage::from_raw_parts(
self.0.as_ptr(),
(Dyn(dim), Dyn(ncols)),
(Const::<1>, Dyn(0)),
);
Matrix::from_data(storage)
}
}
}
impl UpperBound for &InputMatrix<'_> {
#[inline(always)]
fn check_valid(&self, dim: usize) -> bool {
self.nrows() == dim
}
#[inline(always)]
fn ncols(&self) -> usize {
(*self).ncols()
}
#[inline(always)]
fn as_matrix(&self) -> InputMatrix<'_> {
self.as_view()
}
}
pub trait RobotBasic {
fn dim(&self) -> usize;
}
impl RobotBasic for usize {
#[inline(always)]
fn dim(&self) -> usize {
*self
}
}
pub struct Robot<M: RobotBasic> {
model: M,
pub constraints: Constraints,
}
impl<M: RobotBasic> Robot<M> {
#[inline(always)]
pub fn model(&self) -> &M {
&self.model
}
#[inline(always)]
pub fn model_mut(&mut self) -> &mut M {
&mut self.model
}
#[inline(always)]
fn check_strict_signed_limits(
upper: &InputMatrix,
lower: &InputMatrix,
bound_name: &'static str,
) -> Result<(), ConstraintError> {
let upper_valid = upper.iter().all(|&u| u > 0.0);
let lower_valid = lower.iter().all(|&l| l < 0.0);
if upper_valid && lower_valid {
Ok(())
} else {
Err(ConstraintError::InvalidSignedBounds { bound_name })
}
}
pub fn new(model: M) -> Self {
let dim = model.dim();
Self {
model,
constraints: Constraints::new(dim),
}
}
pub fn with_capacity(model: M, capacity: usize) -> Self {
let dim = model.dim();
Self {
model,
constraints: Constraints::with_capacity(dim, capacity),
}
}
#[inline(always)]
pub fn dim(&self) -> usize {
self.constraints.dim()
}
#[inline(always)]
pub fn with_s<T: AsInputMatrix1D + ?Sized>(
&mut self,
s_new: &T,
) -> Result<&mut Self, ConstraintError> {
self.constraints.with_s(s_new)?;
Ok(self)
}
#[inline(always)]
pub fn with_q(
&mut self,
q_new: &InputMatrix,
dq_new: &InputMatrix,
ddq_new: &InputMatrix,
dddq_new: Option<&InputMatrix>,
idx_s: usize,
) -> Result<&mut Self, ConstraintError> {
self.constraints
.with_q(q_new, dq_new, ddq_new, dddq_new, idx_s)?;
Ok(self)
}
#[inline(always)]
pub fn with_q_from_path_2nd(
&mut self,
path: &Path,
idx_s_from: usize,
idx_s_to: usize,
) -> Result<&mut Self, CoppError> {
self.constraints
.with_q_from_path_2nd(path, idx_s_from, idx_s_to)?;
Ok(self)
}
#[inline(always)]
pub fn with_q_from_path_3rd(
&mut self,
path: &Path,
idx_s_from: usize,
idx_s_to: usize,
) -> Result<&mut Self, CoppError> {
self.constraints
.with_q_from_path_3rd(path, idx_s_from, idx_s_to)?;
Ok(self)
}
pub fn with_axial_velocity<T1, T2>(
&mut self,
axial_velocity_max: T1,
axial_velocity_min: T2,
start_idx_s: usize,
) -> Result<&mut Self, ConstraintError>
where
T1: UpperBound,
T2: UpperBound,
{
if !axial_velocity_max.check_valid(self.dim())
|| !axial_velocity_min.check_valid(self.dim())
|| axial_velocity_max.ncols() != axial_velocity_min.ncols()
{
return Err(ConstraintError::NoMatchDimensions);
}
self.constraints
.check_s_in_bounds(start_idx_s, axial_velocity_max.ncols())?;
if !self
.constraints
.check_given_q(start_idx_s, start_idx_s + axial_velocity_max.ncols())
{
return Err(ConstraintError::NoGivenQInfo);
}
if axial_velocity_max.ncols() == 0 {
return Ok(self);
}
let axial_velocity_max = axial_velocity_max.as_matrix();
let axial_velocity_min = axial_velocity_min.as_matrix();
Self::check_strict_signed_limits(
&axial_velocity_max,
&axial_velocity_min,
"axial_velocity",
)?;
let mut amax_new =
DMatrix::<f64>::from_element(self.dim(), axial_velocity_max.ncols(), f64::INFINITY);
let func = |start_idx: usize, ncols: usize, offset: usize| {
let amax_ = self.constraints.dq.columns(start_idx, ncols).zip_zip_map(
&axial_velocity_max.columns(offset, ncols),
&axial_velocity_min.columns(offset, ncols),
|dq, vmax, vmin| {
if dq > 0.0 {
(vmax / dq).powi(2)
} else if dq < 0.0 {
(vmin / dq).powi(2)
} else {
f64::INFINITY
}
},
);
amax_new.columns_mut(offset, ncols).copy_from(&amax_);
};
let ncols_mat = self.constraints.capacity();
let start_idx = self.constraints.col_at_idx_s_unchecked(start_idx_s);
Constraints::circular_process(ncols_mat, start_idx, axial_velocity_max.ncols(), func);
self.constraints
.with_constraint_1order(&amax_new.as_view(), start_idx_s)?;
Ok(self)
}
pub fn with_axial_acceleration<T1, T2>(
&mut self,
axial_acceleration_max: T1,
axial_acceleration_min: T2,
start_idx_s: usize,
) -> Result<&mut Self, ConstraintError>
where
T1: UpperBound,
T2: UpperBound,
{
if !axial_acceleration_max.check_valid(self.dim())
|| !axial_acceleration_min.check_valid(self.dim())
|| axial_acceleration_max.ncols() != axial_acceleration_min.ncols()
{
return Err(ConstraintError::NoMatchDimensions);
}
self.constraints
.check_s_in_bounds(start_idx_s, axial_acceleration_max.ncols())?;
if !self
.constraints
.check_given_q(start_idx_s, start_idx_s + axial_acceleration_max.ncols())
{
return Err(ConstraintError::NoGivenQInfo);
}
if axial_acceleration_max.ncols() == 0 {
return Ok(self);
}
let axial_acceleration_max = axial_acceleration_max.as_matrix();
let axial_acceleration_min = axial_acceleration_min.as_matrix();
Self::check_strict_signed_limits(
&axial_acceleration_max,
&axial_acceleration_min,
"axial_acceleration",
)?;
let mut acc_a_new = DMatrix::<f64>::zeros(self.dim(), axial_acceleration_max.ncols());
let mut acc_b_new = DMatrix::<f64>::zeros(self.dim(), axial_acceleration_max.ncols());
let func = |start_idx: usize, ncols: usize, offset: usize| {
acc_a_new
.columns_mut(offset, ncols)
.copy_from(&self.constraints.ddq.columns(start_idx, ncols));
acc_b_new
.columns_mut(offset, ncols)
.copy_from(&self.constraints.dq.columns(start_idx, ncols));
};
let ncols_mat = self.constraints.capacity();
let start_idx = self.constraints.col_at_idx_s_unchecked(start_idx_s);
Constraints::circular_process(ncols_mat, start_idx, axial_acceleration_max.ncols(), func);
self.constraints
.with_constraint_2order(
&acc_a_new.as_view(),
&acc_b_new.as_view(),
&axial_acceleration_max.as_view(),
start_idx_s,
false,
)?
.with_constraint_2order(
&acc_a_new.as_view(),
&acc_b_new.as_view(),
&axial_acceleration_min.as_view(),
start_idx_s,
true,
)?;
Ok(self)
}
pub fn with_axial_jerk<T1, T2>(
&mut self,
axial_jerk_max: T1,
axial_jerk_min: T2,
start_idx_s: usize,
) -> Result<&mut Self, ConstraintError>
where
T1: UpperBound,
T2: UpperBound,
{
if !axial_jerk_max.check_valid(self.dim())
|| !axial_jerk_min.check_valid(self.dim())
|| axial_jerk_max.ncols() != axial_jerk_min.ncols()
{
return Err(ConstraintError::NoMatchDimensions);
}
self.constraints
.check_s_in_bounds(start_idx_s, axial_jerk_max.ncols())?;
if !self
.constraints
.check_given_q(start_idx_s, start_idx_s + axial_jerk_max.ncols())
|| !self
.constraints
.check_given_dddq(start_idx_s, start_idx_s + axial_jerk_max.ncols())
{
return Err(ConstraintError::NoGivenQInfo);
}
if axial_jerk_max.ncols() == 0 {
return Ok(self);
}
let axial_jerk_max = axial_jerk_max.as_matrix();
let axial_jerk_min = axial_jerk_min.as_matrix();
Self::check_strict_signed_limits(&axial_jerk_max, &axial_jerk_min, "axial_jerk")?;
let mut jerk_a_new = DMatrix::<f64>::zeros(self.dim(), axial_jerk_max.ncols());
let mut jerk_b_new = DMatrix::<f64>::zeros(self.dim(), axial_jerk_max.ncols());
let mut jerk_c_new = DMatrix::<f64>::zeros(self.dim(), axial_jerk_max.ncols());
let jerk_d_new = DMatrix::<f64>::zeros(self.dim(), axial_jerk_max.ncols());
let func = |start_idx: usize, ncols: usize, offset: usize| {
jerk_a_new
.columns_mut(offset, ncols)
.copy_from(&self.constraints.dddq.columns(start_idx, ncols));
jerk_b_new
.columns_mut(offset, ncols)
.copy_from(&self.constraints.ddq.columns(start_idx, ncols));
jerk_c_new
.columns_mut(offset, ncols)
.copy_from(&self.constraints.dq.columns(start_idx, ncols));
};
let ncols_mat = self.constraints.capacity();
let start_idx = self.constraints.col_at_idx_s_unchecked(start_idx_s);
Constraints::circular_process(ncols_mat, start_idx, axial_jerk_max.ncols(), func);
jerk_b_new.scale_mut(3.0);
self.constraints
.with_constraint_3order(
&jerk_a_new.as_view(),
&jerk_b_new.as_view(),
&jerk_c_new.as_view(),
&jerk_d_new.as_view(),
&axial_jerk_max,
start_idx_s,
false,
)?
.with_constraint_3order(
&jerk_a_new.as_view(),
&jerk_b_new.as_view(),
&jerk_c_new.as_view(),
&jerk_d_new.as_view(),
&axial_jerk_min,
start_idx_s,
true,
)?;
Ok(self)
}
}
pub trait RobotTorque: RobotBasic {
fn inverse_dynamics(
&self,
q: &[f64],
dq: &[f64],
ddq: &[f64],
tau: &mut [f64],
) -> Result<(), RobotDynamicsError>;
}
impl<M: RobotTorque> Robot<M> {
#[inline]
fn inverse_dynamics_with_context(
&self,
idx_s: usize,
call: &str,
q: &[f64],
dq: &[f64],
ddq: &[f64],
tau: &mut [f64],
) -> Result<(), RobotDynamicsError> {
self.model
.inverse_dynamics(q, dq, ddq, tau)
.map_err(|error| {
RobotDynamicsError::new(format!(
"inverse_dynamics failed at idx_s={idx_s} during {call}: {error}"
))
})?;
if let Some((index, value)) = tau
.iter()
.copied()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(RobotDynamicsError::new(format!(
"inverse_dynamics returned non-finite tau[{index}] = {value} at idx_s={idx_s} during {call}"
)));
}
Ok(())
}
pub(crate) fn get_torque_with_ab(
&self,
a_profile: &[f64],
b_profile: &[f64],
start_idx_s: usize,
) -> Result<DMatrix<f64>, CoppError> {
if a_profile.len() != b_profile.len() {
return Err(ConstraintError::NoMatchDimensions.into());
}
if a_profile.is_empty() {
return Ok(DMatrix::zeros(self.dim(), 0));
}
self.constraints
.check_s_in_bounds(start_idx_s, a_profile.len())?;
if !self
.constraints
.check_given_q(start_idx_s, start_idx_s + a_profile.len())
{
return Err(ConstraintError::NoGivenQInfo.into());
}
let (mut coeff_a, mut coeff_b, mut coeff_g) =
self.torque_coeff(start_idx_s, a_profile.len())?;
for (mut coeff_a_col, &a_curr) in coeff_a.column_iter_mut().zip(a_profile.iter()) {
coeff_a_col.scale_mut(a_curr);
}
for (mut coeff_b_col, &b_curr) in coeff_b.column_iter_mut().zip(b_profile.iter()) {
coeff_b_col.scale_mut(b_curr);
}
coeff_g += coeff_a;
coeff_g += coeff_b;
Ok(coeff_g)
}
#[allow(clippy::type_complexity)]
pub(crate) fn torque_coeff(
&self,
start_idx_s: usize,
ncols: usize,
) -> Result<(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>), RobotDynamicsError> {
let mut coeff_a = DMatrix::<f64>::zeros(self.dim(), ncols);
let mut coeff_b = DMatrix::<f64>::zeros(self.dim(), ncols);
let mut coeff_g = DMatrix::<f64>::zeros(self.dim(), ncols);
if ncols == 0 {
return Ok((coeff_a, coeff_b, coeff_g));
}
let mut dq_sqrt2 = vec![0.0; self.dim()];
let mut ddq_2 = vec![0.0; self.dim()];
let vec_zero_dim = vec![0.0; self.dim()];
let mut process_segment =
|start_idx: usize, ncols: usize, offset: usize| -> Result<(), RobotDynamicsError> {
for (local_col, (((((mut a, mut b), mut g), q), dq), ddq)) in coeff_a
.columns_mut(offset, ncols)
.column_iter_mut()
.zip(coeff_b.columns_mut(offset, ncols).column_iter_mut())
.zip(coeff_g.columns_mut(offset, ncols).column_iter_mut())
.zip(self.constraints.q.columns(start_idx, ncols).column_iter())
.zip(self.constraints.dq.columns(start_idx, ncols).column_iter())
.zip(self.constraints.ddq.columns(start_idx, ncols).column_iter())
.enumerate()
{
let idx_s = start_idx_s + offset + local_col;
let q_slice = q.as_slice();
let dq_slice = dq.as_slice();
let ddq_slice = ddq.as_slice();
self.inverse_dynamics_with_context(
idx_s,
"tau(q, 0, 0)",
q_slice,
&vec_zero_dim,
&vec_zero_dim,
g.as_mut_slice(),
)?;
self.inverse_dynamics_with_context(
idx_s,
"tau(q, 0, dq)",
q_slice,
&vec_zero_dim,
dq_slice,
b.as_mut_slice(),
)?;
b.iter_mut()
.zip(g.iter())
.for_each(|(b_i, &g_i)| *b_i -= g_i);
dq_sqrt2
.iter_mut()
.zip(dq.iter())
.for_each(|(dq_sqrt2_i, &dq_i)| *dq_sqrt2_i = SQRT_2 * dq_i);
ddq_2
.iter_mut()
.zip(ddq.iter())
.for_each(|(ddq_2_i, &ddq_i)| *ddq_2_i = 2.0 * ddq_i);
self.inverse_dynamics_with_context(
idx_s,
"tau(q, sqrt(2) * dq, 2 * ddq)",
q_slice,
dq_sqrt2.as_slice(),
ddq_2.as_slice(),
a.as_mut_slice(),
)?;
self.inverse_dynamics_with_context(
idx_s,
"tau(q, dq, ddq)",
q_slice,
dq_slice,
ddq_slice,
g.as_mut_slice(),
)?;
a.iter_mut()
.zip(g.iter())
.for_each(|(a_i, &g_i)| *a_i -= g_i);
g.iter_mut()
.zip(a.iter())
.for_each(|(g_i, &a_i)| *g_i -= a_i);
}
Ok(())
};
let ncols_mat = self.constraints.capacity();
if ncols_mat - start_idx_s >= ncols {
process_segment(start_idx_s, ncols, 0)?;
} else {
let len_first = ncols_mat - start_idx_s;
process_segment(start_idx_s, len_first, 0)?;
let len_second = ncols - len_first;
process_segment(0, len_second, len_first)?;
}
Ok((coeff_a, coeff_b, coeff_g))
}
#[allow(clippy::type_complexity)]
pub(crate) fn torque2_coeff_a(
&self,
start_idx_s: usize,
ncols: usize,
) -> Result<(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>), RobotDynamicsError> {
let s = self
.constraints
.s_vec(start_idx_s, start_idx_s + ncols + 1)
.expect("torque2_coeff_a: s interval must be in bounds");
let ds_double_down = s
.windows(2)
.map(|s_pair| 0.5 / (s_pair[1] - s_pair[0]))
.collect::<Vec<f64>>();
let (mut coeff_a, mut coeff_b, coeff_g) = self.torque_coeff(start_idx_s, ncols)?;
for (mut v_b, &ds_double_down) in coeff_b.column_iter_mut().zip(ds_double_down.iter()) {
v_b.scale_mut(ds_double_down);
}
coeff_a -= &coeff_b;
Ok((coeff_a, coeff_b, coeff_g))
}
pub fn with_axial_torque<T1, T2>(
&mut self,
axial_torque_max: T1,
axial_torque_min: T2,
start_idx_s: usize,
) -> Result<&mut Self, CoppError>
where
T1: UpperBound,
T2: UpperBound,
{
if !axial_torque_max.check_valid(self.dim())
|| !axial_torque_min.check_valid(self.dim())
|| axial_torque_max.ncols() != axial_torque_min.ncols()
{
return Err(ConstraintError::NoMatchDimensions.into());
}
self.constraints
.check_s_in_bounds(start_idx_s, axial_torque_max.ncols())?;
if !self
.constraints
.check_given_q(start_idx_s, start_idx_s + axial_torque_max.ncols())
{
return Err(ConstraintError::NoGivenQInfo.into());
}
if axial_torque_max.ncols() == 0 {
return Ok(self);
}
let axial_torque_max = axial_torque_max.as_matrix();
let axial_torque_min = axial_torque_min.as_matrix();
Self::check_strict_signed_limits(&axial_torque_max, &axial_torque_min, "axial_torque")?;
let (coeff_a, coeff_b, coeff_g) =
self.torque_coeff(start_idx_s, axial_torque_max.ncols())?;
self.constraints
.with_constraint_2order(
&coeff_a.as_view(),
&coeff_b.as_view(),
&(axial_torque_max - &coeff_g).as_view(),
start_idx_s,
false,
)?
.with_constraint_2order(
&coeff_a.as_view(),
&coeff_b.as_view(),
&(axial_torque_min - coeff_g).as_view(),
start_idx_s,
true,
)?;
Ok(self)
}
}
impl RobotTorque for usize {
#[inline(always)]
fn inverse_dynamics(
&self,
_q: &[f64],
_dq: &[f64],
ddq: &[f64],
tau: &mut [f64],
) -> Result<(), RobotDynamicsError> {
tau.copy_from_slice(ddq);
Ok(())
}
}