Skip to content

Commit b784c91

Browse files
author
Paul Mitiguy
committed
Partially respond to reviewer comments.
1 parent b1a0765 commit b784c91

File tree

2 files changed

+74
-59
lines changed

2 files changed

+74
-59
lines changed

multibody/tree/quaternion_floating_mobilizer.cc

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,25 @@ void QuaternionFloatingMobilizer<T>::ProjectSpatialForce(
281281
template <typename T>
282282
Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcLMatrix(
283283
const Quaternion<T>& q_FM) {
284-
// This L matrix helps us compute both N(q) and N⁺(q) since it turns out that:
285-
// N(q) = L(q_FM/2)
286-
// and:
287-
// N⁺(q) = L(2 q_FM)ᵀ
284+
// The L matrix helps compute Nᵣ(q_FM) and N⁺ᵣ(q_FM), which are the rotational
285+
// parts of the N matrix N(q) and Nplus matrix N⁺(q). It turns out that:
286+
//
287+
// Nᵣ(q_FM) = 0.5 * L(q_FM) = L(q_FM/2) and
288+
// N⁺ᵣ(q_FM) = 2 * L(q_FM)ᵀ = L(2 q_FM)ᵀ
289+
//
288290
// See Eqs. 5 and 6 in Section 9.2 of Paul's book
289291
// [Mitiguy (August 7) 2017, §9.2], for the time derivative of the vector
290292
// component of the quaternion (Euler parameters). Notice however here we use
291293
// qs and qv for the "scalar" and "vector" components of the quaternion q_FM,
292294
// respectively, while Mitiguy uses ε₀ and ε (in bold), respectively.
293-
// This mobilizer is parameterized by the angular velocity w_FM, i.e. time
295+
// This mobilizer is parameterized by the angular velocity w_FM-F, i.e. time
294296
// derivatives of the vector component of the quaternion are taken in the F
295297
// frame. If you are confused by this, notice that the vector component of a
296298
// quaternion IS a vector, and therefore you must specify in what frame time
297299
// derivatives are taken.
298300
//
299301
// Notice this is equivalent to:
300-
// Dt_F(q) = 1/2 * w_FM⋅q_FM, where ⋅ denotes the "quaternion product" and
302+
// Dt_F(q_FM) = 1/2 * w_FM⋅q_FM, where ⋅ denotes the "quaternion product" and
301303
// both the vector component qv_FM of q_FM and w_FM are expressed in frame F.
302304
// Dt_F(q) is short for [Dt_F(q)]_F.
303305
// The expression above can be written as:
@@ -306,19 +308,21 @@ Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcLMatrix(
306308
// = 1/2 * (-w_FM.dot(qv_F); (qs * Id - [qv_F]x) * w_FM)
307309
// = L(q_FM/2) * w_FM
308310
// That is:
309-
// | -qv_Fᵀ |
310-
// L(q) = | qs * Id - [qv_F]x |
311-
312-
const T qs = q_FM.w(); // The scalar component.
313-
const Vector3<T> qv = q_FM.vec(); // The vector component.
314-
const Vector3<T> mqv = -qv; // minus qv.
311+
// | -qv_Fᵀ | ⌈ -qx -qy -qz ⌉
312+
// L(q_FM) = | qs * Id - [qv_F]x | = | qw qz -qy |
313+
// | -qz qw qx |
314+
// ⌊ qy -qx qw ⌋
315315

316-
// NOTE: the rows of this matrix are in an order consistent with the order
317-
// in which we store the quaternion in the state, with the scalar component
318-
// first followed by the vector component.
319-
return (Eigen::Matrix<T, 4, 3>() << mqv.transpose(), qs, qv.z(), mqv.y(),
320-
mqv.z(), qs, qv.x(), qv.y(), mqv.x(), qs)
321-
.finished();
316+
const T& qw = q_FM.w(); // Scalar part of the quaternion.
317+
const T& qx = q_FM.x(); // q_FM.vec() = [q_FM.x(), q_FM.y(), q_FM.z()] is
318+
const T& qy = q_FM.y(); // the vector part of the quaternion.
319+
const T& qz = q_FM.z();
320+
// clang-format off
321+
return (Eigen::Matrix<T, 4, 3>() << -qx, -qy, -qz,
322+
qw, qz, -qy,
323+
-qz, qw, qx,
324+
qy, -qx, qw).finished();
325+
// clang-format on
322326
}
323327

324328
template <typename T>
@@ -339,20 +343,17 @@ QuaternionFloatingMobilizer<T>::QuaternionRateToAngularVelocityMatrix(
339343
const Matrix4<T> dqnorm_dq =
340344
(Matrix4<T>::Identity() - q_FM_tilde * q_FM_tilde.transpose()) / q_norm;
341345

342-
// With L given by CalcLMatrix we have:
343-
// N⁺(q_tilde) = L(2 q_FM_tilde)ᵀ
344-
return CalcLMatrix({2.0 * q_FM_tilde[0], 2.0 * q_FM_tilde[1],
345-
2.0 * q_FM_tilde[2], 2.0 * q_FM_tilde[3]})
346-
.transpose() *
346+
// With L given by CalcLMatrix(), we have N⁺(q_tilde) = 2 * L(q_FM_tilde)ᵀ.
347+
return CalcTwoTimesLMatrixTranspose(
348+
{q_FM_tilde[0], q_FM_tilde[1], q_FM_tilde[2], q_FM_tilde[3]}) *
347349
dqnorm_dq;
348350
}
349351

350352
template <typename T>
351353
void QuaternionFloatingMobilizer<T>::DoCalcNMatrix(
352354
const systems::Context<T>& context, EigenPtr<MatrixX<T>> N) const {
353355
// Upper-left block
354-
N->template block<4, 3>(0, 0) =
355-
AngularVelocityToQuaternionRateMatrix(get_quaternion(context));
356+
N->template block<4, 3>(0, 0) = CalcLMatrixOverTwo(get_quaternion(context));
356357
// Upper-right block
357358
N->template block<4, 3>(0, 3).setZero();
358359
// Lower-left block
@@ -397,14 +398,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNDotMatrix(
397398
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
398399
const Quaternion<T> q_FM = get_quaternion(context);
399400
const Vector3<T> w_FM_F = get_angular_velocity(context);
400-
const Vector4<T> qdot = AngularVelocityToQuaternionRateMatrix(q_FM) * w_FM_F;
401-
const Quaternion<T> half_qdot(0.5 * qdot[0], 0.5 * qdot[1], 0.5 * qdot[2],
402-
0.5 * qdot[3]);
401+
const Vector4<T> qdot = CalcLMatrixOverTwo(q_FM) * w_FM_F;
402+
const Quaternion<T> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]);
403403

404-
// Leveraging comments and code in AngularVelocityToQuaternionRateMatrix()
405-
// and noting that Nᵣ(qᵣ) = L(q_FM/2), where the elements of the matrix L are
406-
// linear in q_FM = [qw, qx, qy, qz]ᵀ, so Ṅᵣ(qᵣ,q̇ᵣ) = L(q̇_FM/2).
407-
const Eigen::Matrix<T, 4, 3> NrDotMatrix = CalcLMatrix(half_qdot);
404+
// Leveraging comments and code in CalcLMatrixOverTwo() and noting that
405+
// Nᵣ(qᵣ) = 0.5 * L(q_FM), where the elements of the matrix L are linear in
406+
// q_FM = [qw, qx, qy, qz]ᵀ, so Ṅᵣ(qᵣ,q̇ᵣ) = 0.5 * L(q̇_FM).
407+
const Eigen::Matrix<T, 4, 3> NrDotMatrix = CalcLMatrixOverTwo(qdot_FM);
408408

409409
// Form the Ṅ(q,q̇) matrix associated with q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
410410
Ndot->template block<4, 3>(0, 0) = NrDotMatrix; // Upper-left block.
@@ -435,14 +435,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNplusDotMatrix(
435435
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
436436
const Quaternion<T> q_FM = get_quaternion(context);
437437
const Vector3<T> w_FM_F = get_angular_velocity(context);
438-
const Vector4<T> qdot = AngularVelocityToQuaternionRateMatrix(q_FM) * w_FM_F;
439-
const Quaternion<T> twice_qdot(2.0 * qdot[0], 2.0 * qdot[1], 2.0 * qdot[2],
440-
2.0 * qdot[3]);
438+
const Vector4<T> qdot = CalcLMatrixOverTwo(q_FM) * w_FM_F;
439+
const Quaternion<T> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]);
441440

442-
// Leveraging comments and code in AngularVelocityToQuaternionRateMatrix(()
443-
// and noting that N⁺ᵣ(qᵣ) = L(2 * q_FM)ᵀ, where the elements of the matrix L
444-
// are linear in q_FM = [qw, qx, qy, qz]ᵀ, so Ṅ⁺ᵣ(qᵣ,q̇ᵣ) = L(2 * q̇_FM)ᵀ.
445-
const Eigen::Matrix<T, 3, 4> NrPlusDot = CalcLMatrix(twice_qdot).transpose();
441+
// Since N⁺ᵣ(qᵣ) = 2 * L(q_FM)ᵀ, where L(q_FM) is linear in q_FM, hence
442+
// ⁺ᵣ(qᵣ,q̇ᵣ) = 2 * L(q̇_FM)ᵀ.
443+
const Eigen::Matrix<T, 3, 4> NrPlusDot =
444+
CalcTwoTimesLMatrixTranspose(qdot_FM);
446445

447446
// Form the Ṅ⁺(q,q̇) matrix associated with v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
448447
NplusDot->template block<3, 4>(0, 0) = NrPlusDot; // Upper-left block.
@@ -456,10 +455,9 @@ void QuaternionFloatingMobilizer<T>::DoMapVelocityToQDot(
456455
const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
457456
EigenPtr<VectorX<T>> qdot) const {
458457
const Quaternion<T> q_FM = get_quaternion(context);
459-
// Angular component, q̇_WB = N(q)⋅w_WB:
460-
qdot->template head<4>() =
461-
AngularVelocityToQuaternionRateMatrix(q_FM) * v.template head<3>();
462-
// Translational component, ṗ_WB = v_WB:
458+
// Angular component, q̇_FM = 0.5 * L(q_FM) ⋅ w_FM_F:
459+
qdot->template head<4>() = CalcLMatrixOverTwo(q_FM) * v.template head<3>();
460+
// Translational component, ṗ_FoMo_F = v_FMo_F:
463461
qdot->template tail<3>() = v.template tail<3>();
464462
}
465463

@@ -501,7 +499,7 @@ void QuaternionFloatingMobilizer<T>::DoMapAccelerationToQDDot(
501499
const Vector3<T> w_FM_F = get_angular_velocity(context);
502500
const T w_squared_over_four = 0.25 * w_FM_F.squaredNorm();
503501
qddot->template head<4>() =
504-
AngularVelocityToQuaternionRateMatrix(q_FM) * vdot.template head<3>() -
502+
CalcLMatrixOverTwo(q_FM) * vdot.template head<3>() -
505503
w_squared_over_four * Vector4<T>(q_FM.w(), q_FM.x(), q_FM.y(), q_FM.z());
506504

507505
// Calculate the 2nd-derivative of the position vector p_FoMo_F = [x, y, z]ᵀ
@@ -520,7 +518,7 @@ void QuaternionFloatingMobilizer<T>::DoMapQDDotToAcceleration(
520518
//
521519
// For the rotational part of this mobilizer, the 1st-derivatives of the
522520
// generalized velocities ẇ_FM_F = v̇ᵣ = [ẇx, ẇy, ẇz]ᵀ are related to the
523-
// 2nd-derivatives of the generalized positions q̈̇_FM = q̇ᵣ = [q̈w, q̈x, q̈y, q̈z]ᵀ
521+
// 2nd-derivatives of the generalized positions q̈_FM = q̇ᵣ = [q̈w, q̈x, q̈y, q̈z]ᵀ
524522
// as v̇ᵣ = N⁺ᵣ(q)⋅q̈_FM, where N⁺ᵣ(q) is the 3x4 matrix below.
525523
// Note: Perhaps surprisingly, Ṅ⁺ᵣ(q,q̇)⋅q̇ = [0, 0, 0]ᵀ.
526524
//

multibody/tree/quaternion_floating_mobilizer.h

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -331,26 +331,43 @@ class QuaternionFloatingMobilizer final : public MobilizerImpl<T, 7, 6> {
331331
const MultibodyTree<symbolic::Expression>& tree_clone) const final;
332332

333333
private:
334-
// Helper to compute the kinematic map N(q). L ∈ ℝ⁴ˣ³.
335-
static Eigen::Matrix<T, 4, 3> CalcLMatrix(const Quaternion<T>& q);
336-
337-
// Returns the 4x3 Nᵣ(q) matrix that relates q̇_FM = Nᵣ(q) * w_FM_F, where
338-
// q̇_FM = [q̇w, q̇x, q̇y, q̇z]ᵀ (time-derivative of quaternion for frames F and M)
339-
// w_FM_F = [ωx, ωy, ωz]ᵀ (frame M's angular velocity in F, expressed in F).
334+
// Returns the 4x3 matrix L(q_FM) where q_FM = [qw, qx, qy, qz]ᵀ is the
335+
// quaternion relating frames F and M.
340336
//
341-
// ⌈ q̇w ⌉ ⌈ -qx -qy -qz ⌉ ⌈ ωx
342-
// | q̇x | = 0.5 | qw qz -qy | | ωy |
343-
// | q̇y | | -qz qw qx | ⌊ ωz ⌋
344-
// ⌊ q̇z ⌋ ⌊ qy -qx qw ⌋
337+
// ⌈ -qx -qy -qz ⌉
338+
// L(q_FM) = | qw qz -qy |
339+
// | -qz qw qx |
340+
// ⌊ qy -qx qw ⌋
345341
//
346-
// Herein L(q_FM) is the 4x3 matrix shown above, so Nᵣ(q) = 0.5 * L(q_FM).
347-
// Due to the linear nature of L(q_FM), 0.5 * L(q_FM) = L(0.5 * q_FM).
348-
static Eigen::Matrix<T, 4, 3> AngularVelocityToQuaternionRateMatrix(
349-
const Quaternion<T>& q_FM) {
342+
// The matrix L(q_FM) is an intermediary for various quaternion-related
343+
// calculations. For example, denoting w_FM_F = [ωx, ωy, ωz]ᵀ as frame M's
344+
// angular velocity in frame F, expressed in F, the following involve L(q_FM):
345+
//
346+
// q̇_FM = 0.5 * L(q_FM) * w_FM_F
347+
// q̈_FM = 0.5 * L(q_FM) * ẇ_FM_F - 0.25 ω² q_FM Note: ω² = |w_FM_F|²
348+
// w_FM_F = 2 * L(q_FM)ᵀ * q̇_FM
349+
// ẇ_FM_F = 2 * L(q_FM)ᵀ * q̇_FM
350+
// Nᵣ(q_FM) = 0.5 * L(q_FM) Nᵣ is the rotational part of the N matrix.
351+
// N⁺ᵣ(q_FM) = 2 * L(q_FM)ᵀ N⁺ᵣ is the rotational part of the Nplus matrix.
352+
static Eigen::Matrix<T, 4, 3> CalcLMatrix(const Quaternion<T>& q);
353+
354+
// Returns the 4x3 matrix 0.5 * L(q_FM), where q_FM = [qw, qx, qy, qz] is the
355+
// quaternion relating frames F and M. As documented in CalcLMatrix(),
356+
// L(q_FM) depends linearly on q_FM, so 0.5 * L(q_FM) = L(0.5 * q_FM).
357+
static Eigen::Matrix<T, 4, 3> CalcLMatrixOverTwo(const Quaternion<T>& q_FM) {
350358
return CalcLMatrix(
351359
{0.5 * q_FM.w(), 0.5 * q_FM.x(), 0.5 * q_FM.y(), 0.5 * q_FM.z()});
352360
}
353361

362+
// Returns the 3x4 matrix 2 * L(q_FM)ᵀ, where q_FM = [qw, qx, qy, qz] is the
363+
// quaternion relating frames F and M. As documented in CalcLMatrix(),
364+
// L(q_FM) depends linearly on q_FM, so 2 * L(q_FM)ᵀ = L(2 * q_FM)ᵀ.
365+
static Eigen::Matrix<T, 3, 4> CalcTwoTimesLMatrixTranspose(
366+
const Quaternion<T>& q_FM) {
367+
return CalcLMatrix({2 * q_FM.w(), 2 * q_FM.x(), 2 * q_FM.y(), 2 * q_FM.z()})
368+
.transpose();
369+
}
370+
354371
// Helper to compute the kinematic map N⁺(q) from quaternion time derivative
355372
// to angular velocity for which w_WB = N⁺(q)⋅q̇_WB.
356373
// This method can take a non unity quaternion q_tilde such that

0 commit comments

Comments
 (0)