Skip to content

Commit c4afb2c

Browse files
author
Paul Mitiguy
committed
Consolidated code, added tests.
1 parent 78e6eb4 commit c4afb2c

File tree

3 files changed

+98
-36
lines changed

3 files changed

+98
-36
lines changed

multibody/tree/rpy_ball_mobilizer.cc

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,36 @@ RpyBallMobilizer<T>::TemplatedDoCloneToScalar(
446446
outboard_frame_clone);
447447
}
448448

449+
template <typename T>
450+
Vector3<T> RpyBallMobilizer<T>::CalcNplusDotTimesQdot(
451+
const systems::Context<T>& context, const char* function_name) const {
452+
using std::cos;
453+
using std::sin;
454+
const Vector3<T> angles = get_angles(context);
455+
const T cp = cos(angles[1]);
456+
const T sp = sin(angles[1]);
457+
const T sy = sin(angles[2]);
458+
const T cy = cos(angles[2]);
459+
if (abs(cp) < 1.0e-3) {
460+
ThrowSinceCosPitchIsNearZero(angles[1], function_name);
461+
}
462+
// The algorithm below efficiently calculates Ṅ⁺(q,q̇)⋅q̇.
463+
const Vector3<T> v = get_angular_velocity(context);
464+
Vector3<T> qdot;
465+
DoMapVelocityToQDot(context, v, &qdot);
466+
const T& rdot = qdot[0];
467+
const T& pdot = qdot[1];
468+
const T& ydot = qdot[2];
469+
const T pdot_ydot = pdot * ydot;
470+
const T rdot_pdot = rdot * pdot;
471+
const T rdot_ydot = rdot * ydot;
472+
const T sp_rdot_pdot = sp * rdot_pdot;
473+
const T cp_rdot_ydot = cp * rdot_ydot;
474+
return Vector3<T>(-cy * pdot_ydot - cy * sp_rdot_pdot - sy * cp_rdot_ydot,
475+
-sy * pdot_ydot - sy * sp_rdot_pdot + cy * cp_rdot_ydot,
476+
-cp * rdot_pdot);
477+
}
478+
449479
template <typename T>
450480
void RpyBallMobilizer<T>::DoMapAccelerationToQDDot(
451481
const systems::Context<T>& context,
@@ -466,47 +496,49 @@ void RpyBallMobilizer<T>::DoMapAccelerationToQDDot(
466496
//
467497
// There are various ways to get q̈ = [r̈, p̈, ÿ]ᵀ in terms of v̇ = [ω̇x, ω̇y, ω̇z]ᵀ.
468498
// A simple calculation directly solves for q̈ as q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
469-
// Since N⁺(q) is simpler than N(q), then Ṅ⁺(q,q̇) is much simpler than Ṅ(q,q̇),
470-
// so a more efficient calculation of q̈ starts with v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈
471-
// and then solves for q̈ = N⁺(q) {v̇ - Ṅ⁺(q,q̇)⋅q̇}. Since N(q)⋅N⁺(q) = I₃₃
472-
// (the 3x3 identity matrix), Ṅ⋅N⁺ + N Ṅ⁺ = 0₃₃, which leads to Ṅ⁺ = N⁺⋅Ṅ⋅N⁺.
473-
// Substituting this gives q̈ = N⁺(q) {v̇ - N⁺⋅Ṅ⋅N⁺⋅q̇} = N⁺(q) {v̇ - N⁺⋅Ṅ⋅N⁺⋅q̇}
499+
// However, N⁺(q) is simpler than N(q) so Ṅ⁺(q,q̇) is much simpler than Ṅ(q,q̇).
500+
// Hence, a more efficient calculation of q̈ starts as v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈
501+
// and then solves as q̈ = N(q) {v̇ - Ṅ⁺(q,q̇)⋅q̇}.
474502
// --------------------------------------------------------------------------
475-
using std::cos;
476-
using std::sin;
477-
const Vector3<T> angles = get_angles(context);
478-
const T cp = cos(angles[1]);
479-
const T sp = sin(angles[1]);
480-
const T sy = sin(angles[2]);
481-
const T cy = cos(angles[2]);
482-
if (abs(cp) < 1.0e-3) {
483-
const char* function_name_less_Do = __func__ + 2;
484-
ThrowSinceCosPitchIsNearZero(angles[1], function_name_less_Do);
485-
}
503+
const Vector3<T> vdotEtc = vdot - CalcNplusDotTimesQdot(context, __func__);
486504

487-
const Vector3<T> v = get_angular_velocity(context);
488-
Vector3<T> qdot;
489-
DoMapVelocityToQDot(context, v, &qdot);
490-
const T& rdot = qdot[0];
491-
const T& pdot = qdot[1];
492-
const T& ydot = qdot[2];
493-
const T pdot_ydot = pdot * ydot;
494-
const T rdot_pdot = rdot * pdot;
495-
const T rdot_ydot = rdot * ydot;
496-
const T sp_rdot_pdot = sp * rdot_pdot;
497-
const T cp_rdot_ydot = cp * rdot_ydot;
505+
// Mathematical trick: Although the function below was designed to efficiently
506+
// calculate q̇ = N(q)⋅v, it can be used to calculate q̈ = N(q) {v̇ - Ṅ⁺(q,q̇)⋅q̇}.
507+
DoMapVelocityToQDot(context, vdotEtc, qddot);
508+
}
498509

499-
const T& wxdot = vdot[0];
500-
const T& wydot = vdot[1];
501-
const T& wzdot = vdot[2];
510+
template <typename T>
511+
void RpyBallMobilizer<T>::DoMapQDDotToAcceleration(
512+
const systems::Context<T>& context,
513+
const Eigen::Ref<const VectorX<T>>& qddot,
514+
EigenPtr<VectorX<T>> vdot) const {
515+
// --------------------------------------------------------------------------
516+
// Seen in MapQDotToVelocity(), the generalized velocities v = [ωx, ωy, ωz]ᵀ
517+
// are related to q̇ = [ṙ, ṗ, ẏ]ᵀ (time-derivatives of generalized positions)
518+
// as shown below, whose matrix form is v = N⁺(q)⋅q̇, where N⁺ is a 3x3 matrix.
519+
//
520+
// ⌈ ωx ⌉ ⌈cos(y) cos(p) -sin(y) 0⌉ ⌈ ṙ ⌉
521+
// | ωy | = |sin(y) cos(p) cos(y) 0| | ṗ |
522+
// ⌊ ωz ⌋ ⌊ -sin(p) 0 1⌋ ⌊ ẏ ⌋
523+
//
524+
// For this mobilizer v = w_FM_F = [ωx, ωy, ωz]ᵀ is the mobilizer M frame's
525+
// angular velocity in the mobilizer F frame, expressed in frame F and
526+
// q = [r, p, y]ᵀ denote roll, pitch, yaw angles (generalized positions).
527+
//
528+
// There are various ways to calculate v̇ = [ω̇x, ω̇y, ω̇z]ᵀ (the time-derivatives
529+
// of the generalized velocities). A simple one is v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
530+
// --------------------------------------------------------------------------
531+
// Calculate Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
532+
const Vector3<T> NplusDotTimesQdot = CalcNplusDotTimesQdot(context, __func__);
502533

503-
Vector3<T> rhs;
504-
rhs[0] = wxdot + cy * pdot_ydot + cy * sp_rdot_pdot + sy * cp_rdot_ydot;
505-
rhs[1] = wydot + sy * pdot_ydot + sy * sp_rdot_pdot - cy * cp_rdot_ydot;
506-
rhs[2] = wzdot + cp * rdot_pdot;
534+
// Mathematical trick: Although the function below was designed to efficiently
535+
// calculate v = N⁺(q)⋅q̇, it can be used to calculate N⁺(q)⋅q̈.
536+
DoMapQDotToVelocity(context, qddot, vdot);
507537

508-
// Mathematical trick to efficiently calculate the result.
509-
DoMapVelocityToQDot(context, rhs, qddot);
538+
// Calculate v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
539+
vdot->coeffRef(0) += NplusDotTimesQdot[0];
540+
vdot->coeffRef(1) += NplusDotTimesQdot[1];
541+
vdot->coeffRef(2) += NplusDotTimesQdot[2];
510542
}
511543

512544
template <typename T>

multibody/tree/rpy_ball_mobilizer.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ class RpyBallMobilizer final : public MobilizerImpl<T, 3, 3> {
261261
void DoCalcNplusDotMatrix(const systems::Context<T>& context,
262262
EigenPtr<MatrixX<T>> NplusDot) const final;
263263

264+
// Calculate the term Ṅ⁺(q,q̇)⋅q̇ which appears in v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
265+
Vector3<T> CalcNplusDotTimesQdot(const systems::Context<T>& context,
266+
const char* function_name) const;
267+
264268
// Maps the generalized velocity v, which corresponds to the angular velocity
265269
// w_FM, to time derivatives of roll-pitch-yaw angles θ₀, θ₁, θ₂ in qdot.
266270
//
@@ -305,6 +309,12 @@ class RpyBallMobilizer final : public MobilizerImpl<T, 3, 3> {
305309
const Eigen::Ref<const VectorX<T>>& vdot,
306310
EigenPtr<VectorX<T>> qddot) const final;
307311

312+
// Maps qddot to vdot, which for this mobilizer is v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
313+
// For simple mobilizers v̇ = q̈. This mobilizer's N and Ṅ⁺ are more elaborate.
314+
void DoMapQDDotToAcceleration(const systems::Context<T>& context,
315+
const Eigen::Ref<const VectorX<T>>& qddot,
316+
EigenPtr<VectorX<T>> vdot) const final;
317+
308318
std::unique_ptr<Mobilizer<double>> DoCloneToScalar(
309319
const MultibodyTree<double>& tree_clone) const override;
310320

multibody/tree/test/rpy_ball_mobilizer_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,26 @@ TEST_F(RpyBallMobilizerTest, MapAccelerationToQDDotAndViceVersa) {
244244
const Vector3<double> qddot_expected = Ndot * wxyz + N * vdot;
245245
EXPECT_TRUE(CompareMatrices(qddot, qddot_expected, kTolerance,
246246
MatrixCompareType::relative));
247+
248+
// Compute the 3x3 N⁺(q) matrix and its time-derivative Ṅ⁺(q,q̇).
249+
MatrixX<double> Nplus(3, 3), Nplusdot(3, 3);
250+
mobilizer_->CalcNplusMatrix(*context_, &Nplus);
251+
mobilizer_->CalcNplusDotMatrix(*context_, &Nplusdot);
252+
253+
// Starting with the previous q̈, use MapQDDotToAcceleration() to calculate v̇.
254+
Vector3<double> wdot;
255+
mobilizer_->MapQDDotToAcceleration(*context_, qddot, &wdot);
256+
257+
// Verify equivalence of v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈ and MapQDDotToAcceleration().
258+
Vector3<double> qdot;
259+
mobilizer_->MapVelocityToQDot(*context_, wxyz, &qdot);
260+
const Vector3<double> vdot_expected = Nplusdot * qdot + Nplus * qddot;
261+
EXPECT_TRUE(CompareMatrices(vdot_expected, vdot, 16 * kTolerance,
262+
MatrixCompareType::relative));
263+
264+
// Verify MapQDDotToAcceleration() is the inverse of MapAccelerationToQDDot().
265+
EXPECT_TRUE(CompareMatrices(vdot, wdot, 16 * kTolerance,
266+
MatrixCompareType::relative));
247267
}
248268

249269
TEST_F(RpyBallMobilizerTest, SingularityError) {

0 commit comments

Comments
 (0)