use crate::copp::constraints::{AsInputMatrix1D, Constraints, InputMatrix};
use crate::diag::ConstraintError;
use nalgebra::{Const, DMatrix, Dyn, Matrix, ViewStorage};
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)]
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<(), ConstraintError> {
self.constraints.with_s(s_new)
}
#[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<(), ConstraintError> {
self.constraints
.with_q(q_new, dq_new, ddq_new, dddq_new, idx_s)
}
pub fn with_axial_velocity<T1, T2>(
&mut self,
axial_velocity_max: T1,
axial_velocity_min: T2,
start_idx_s: usize,
) -> Result<(), 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(());
}
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(())
}
pub fn with_axial_acceleration<T1, T2>(
&mut self,
axial_acceleration_max: T1,
axial_acceleration_min: T2,
start_idx_s: usize,
) -> Result<(), 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(());
}
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,
)?;
self.constraints.with_constraint_2order(
&acc_a_new.as_view(),
&acc_b_new.as_view(),
&axial_acceleration_min.as_view(),
start_idx_s,
true,
)?;
Ok(())
}
pub fn with_axial_jerk<T1, T2>(
&mut self,
axial_jerk_max: T1,
axial_jerk_min: T2,
start_idx_s: usize,
) -> Result<(), 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(());
}
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,
)?;
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_min,
start_idx_s,
true,
)?;
Ok(())
}
}
pub trait RobotTorque: RobotBasic {
fn inverse_dynamics(&self, q: &[f64], dq: &[f64], ddq: &[f64], tau: &mut [f64]);
}
impl<M: RobotTorque> Robot<M> {
#[cfg(test)]
pub(crate) fn get_torque_with_ab(
&self,
a_profile: &[f64],
b_profile: &[f64],
start_idx_s: usize,
) -> Result<DMatrix<f64>, ConstraintError> {
if a_profile.len() != b_profile.len() {
return Err(ConstraintError::NoMatchDimensions);
}
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);
}
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,
) -> (DMatrix<f64>, DMatrix<f64>, DMatrix<f64>) {
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);
let vec_zero_dim = vec![0.0; self.dim()];
let func = |start_idx: usize, ncols: usize, offset: usize| {
for (((((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())
{
let q_slice = q.as_slice();
let dq_slice = dq.as_slice();
let ddq_slice = ddq.as_slice();
self.model.inverse_dynamics(
q_slice,
&vec_zero_dim,
&vec_zero_dim,
g.as_mut_slice(),
);
self.model
.inverse_dynamics(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);
self.model
.inverse_dynamics(q_slice, dq_slice, ddq_slice, a.as_mut_slice());
a.iter_mut()
.zip(g.iter())
.for_each(|(a_i, g_i)| *a_i -= *g_i);
}
};
let ncols_mat = self.constraints.capacity();
Constraints::circular_process(ncols_mat, start_idx_s, ncols, func);
(coeff_a, coeff_b, coeff_g)
}
#[allow(clippy::type_complexity)]
pub(crate) fn torque2_coeff_a(
&self,
start_idx_s: usize,
ncols: usize,
) -> (DMatrix<f64>, DMatrix<f64>, DMatrix<f64>) {
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;
(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<(), ConstraintError>
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);
}
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);
}
if axial_torque_max.ncols() == 0 {
return Ok(());
}
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,
)?;
self.constraints.with_constraint_2order(
&coeff_a.as_view(),
&coeff_b.as_view(),
&(axial_torque_min - coeff_g).as_view(),
start_idx_s,
true,
)?;
Ok(())
}
}
impl RobotTorque for usize {
#[inline(always)]
fn inverse_dynamics(&self, _q: &[f64], _dq: &[f64], ddq: &[f64], tau: &mut [f64]) {
tau.copy_from_slice(ddq);
}
}