Skip to content

Commit e07cdc2

Browse files
author
Paul Mitiguy
committed
Changes to function name, matrix name, documentation.
1 parent 89b442c commit e07cdc2

File tree

2 files changed

+70
-93
lines changed

2 files changed

+70
-93
lines changed

multibody/tree/quaternion_floating_mobilizer.cc

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -279,44 +279,11 @@ void QuaternionFloatingMobilizer<T>::ProjectSpatialForce(
279279
}
280280

281281
template <typename T>
282-
Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcLMatrix(
282+
Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcQMatrix(
283283
const Quaternion<T>& 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-
//
290-
// See Eqs. 5 and 6 in Section 9.2 of Paul's book
291-
// [Mitiguy (August 7) 2017, §9.2], for the time derivative of the vector
292-
// component of the quaternion (Euler parameters). Notice however here we use
293-
// qs and qv for the "scalar" and "vector" components of the quaternion q_FM,
294-
// respectively, while Mitiguy uses ε₀ and ε (in bold), respectively.
295-
// This mobilizer is parameterized by the angular velocity w_FM_F, i.e. time
296-
// derivatives of the vector component of the quaternion are taken in the F
297-
// frame. If you are confused by this, notice that the vector component of a
298-
// quaternion IS a vector, and therefore you must specify in what frame time
299-
// derivatives are taken.
300-
//
301-
// Notice this is equivalent to:
302-
// Dt_F(q_FM) = 1/2 * w_FM⋅q_FM, where ⋅ denotes the "quaternion product" and
303-
// both the vector component qv_FM of q_FM and w_FM are expressed in frame F.
304-
// Using Dt_F(q) as an abbreviation of [Dt_F(q_FM)]_F.
305-
// The expression above can be written as:
306-
// Dt_F(q) = 1/2 * (-w_FM.dot(qv_F); qs * w_FM + w_FM.cross(qv_F))
307-
// = 1/2 * (-w_FM.dot(qv_F); qs * w_FM - qv_F.cross(w_FM))
308-
// = 1/2 * (-w_FM.dot(qv_F); (qs * Id - [qv_F]x) * w_FM)
309-
// = 1/2 * L(q_FM) * w_FM
310-
// = L(q_FM/2) * w_FM
311-
// That is:
312-
// | -qv_Fᵀ | ⌈ -qx -qy -qz ⌉
313-
// L(q_FM) = | qs * Id - [qv_F]x | = | qw qz -qy |
314-
// | -qz qw qx |
315-
// ⌊ qy -qx qw ⌋
316-
317-
const T& qw = q_FM.w(); // Scalar part of the quaternion.
318-
const T& qx = q_FM.x(); // q_FM.vec() = [q_FM.x(), q_FM.y(), q_FM.z()] is
319-
const T& qy = q_FM.y(); // the vector part of the quaternion.
284+
const T& qw = q_FM.w();
285+
const T& qx = q_FM.x();
286+
const T& qy = q_FM.y();
320287
const T& qz = q_FM.z();
321288
// clang-format off
322289
return (Eigen::Matrix<T, 4, 3>() << -qx, -qy, -qz,
@@ -344,29 +311,31 @@ QuaternionFloatingMobilizer<T>::QuaternionRateToAngularVelocityMatrix(
344311
const Matrix4<T> dqnorm_dq =
345312
(Matrix4<T>::Identity() - q_FM_tilde * q_FM_tilde.transpose()) / q_norm;
346313

347-
// With L given by CalcLMatrix(), we have N⁺(q_tilde) = 2 * L(q_FM_tilde)ᵀ.
348-
return CalcTwoTimesLMatrixTranspose(
314+
// From documentation in CalcQMatrix(), N⁺(q_tilde) = 2 * (Q_FM_tilde)ᵀ.
315+
return CalcTwoTimesQMatrixTranspose(
349316
{q_FM_tilde[0], q_FM_tilde[1], q_FM_tilde[2], q_FM_tilde[3]}) *
350317
dqnorm_dq;
351318
}
352319

353320
template <typename T>
354321
void QuaternionFloatingMobilizer<T>::DoCalcNMatrix(
355322
const systems::Context<T>& context, EigenPtr<MatrixX<T>> N) const {
356-
// Upper-left block
357-
N->template block<4, 3>(0, 0) = CalcLMatrixOverTwo(get_quaternion(context));
323+
// Upper-left block (rotational part of the N matrix) is 0.5 * Matrix(q_FM).
324+
// See QuaternionFloatingMobilizer::CalcQMatrix() for details.
325+
N->template block<4, 3>(0, 0) = CalcQMatrixOverTwo(get_quaternion(context));
358326
// Upper-right block
359327
N->template block<4, 3>(0, 3).setZero();
360328
// Lower-left block
361329
N->template block<3, 3>(4, 0).setZero();
362-
// Lower-right block
330+
// Lower-right block (translational part of the N matrix).
363331
N->template block<3, 3>(4, 3).setIdentity();
364332
}
365333

366334
template <typename T>
367335
void QuaternionFloatingMobilizer<T>::DoCalcNplusMatrix(
368336
const systems::Context<T>& context, EigenPtr<MatrixX<T>> Nplus) const {
369-
// Upper-left block
337+
// Upper-left block (rotational part of the N⁺ matrix) is [2 * Matrix(q_FM)]ᵀ
338+
// See QuaternionFloatingMobilizer::CalcQMatrix() for details.
370339
Nplus->template block<3, 4>(0, 0) =
371340
QuaternionRateToAngularVelocityMatrix(get_quaternion(context));
372341
// Upper-right block
@@ -399,12 +368,12 @@ void QuaternionFloatingMobilizer<T>::DoCalcNDotMatrix(
399368
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
400369
const Quaternion<T> q_FM = get_quaternion(context);
401370
const Vector3<T> w_FM_F = get_angular_velocity(context);
402-
const Vector4<T> qdot = CalcLMatrixOverTwo(q_FM) * w_FM_F;
371+
const Vector4<T> qdot = CalcQMatrixOverTwo(q_FM) * w_FM_F;
403372
const Quaternion<T> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]);
404373

405-
// Since Nᵣ(qᵣ) = 0.5 * L(q_FM), where L(q_FM) is linear in the elements of
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);
374+
// Since Nᵣ(qᵣ) = 0.5 * Q(q_FM), where Q(q_FM) is linear in the elements of
375+
// q_FM = [qw, qx, qy, qz]ᵀ, so Ṅᵣ(qᵣ,q̇ᵣ) = 0.5 * Q(q̇_FM).
376+
const Eigen::Matrix<T, 4, 3> NrDotMatrix = CalcQMatrixOverTwo(qdot_FM);
408377

409378
// Form the Ṅ(q,q̇) matrix associated with q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
410379
Ndot->template block<4, 3>(0, 0) = NrDotMatrix; // Upper-left block.
@@ -435,13 +404,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNplusDotMatrix(
435404
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
436405
const Quaternion<T> q_FM = get_quaternion(context);
437406
const Vector3<T> w_FM_F = get_angular_velocity(context);
438-
const Vector4<T> qdot = CalcLMatrixOverTwo(q_FM) * w_FM_F;
407+
const Vector4<T> qdot = CalcQMatrixOverTwo(q_FM) * w_FM_F;
439408
const Quaternion<T> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]);
440409

441-
// Since N⁺ᵣ(qᵣ) = 2 * L(q_FM)ᵀ, where L(q_FM) is linear in the elements of
442-
// q_FM = [qw, qx, qy, qz]ᵀq_FM, hence Ṅ⁺ᵣ(qᵣ,q̇ᵣ) = 2 * L(q̇_FM)ᵀ.
410+
// Since N⁺ᵣ(qᵣ) = 2 * Q(q_FM)ᵀ, where Q(q_FM) is linear in the elements of
411+
// q_FM = [qw, qx, qy, qz]ᵀq_FM, hence Ṅ⁺ᵣ(qᵣ,q̇ᵣ) = 2 * Q(q̇_FM)ᵀ.
443412
const Eigen::Matrix<T, 3, 4> NrPlusDot =
444-
CalcTwoTimesLMatrixTranspose(qdot_FM);
413+
CalcTwoTimesQMatrixTranspose(qdot_FM);
445414

446415
// Form the Ṅ⁺(q,q̇) matrix associated with v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
447416
NplusDot->template block<3, 4>(0, 0) = NrPlusDot; // Upper-left block.
@@ -455,8 +424,8 @@ void QuaternionFloatingMobilizer<T>::DoMapVelocityToQDot(
455424
const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
456425
EigenPtr<VectorX<T>> qdot) const {
457426
const Quaternion<T> q_FM = get_quaternion(context);
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>();
427+
// Angular component, q̇_FM = 0.5 * Q(q_FM) ⋅ w_FM_F:
428+
qdot->template head<4>() = CalcQMatrixOverTwo(q_FM) * v.template head<3>();
460429
// Translational component, ṗ_FoMo_F = v_FMo_F:
461430
qdot->template tail<3>() = v.template tail<3>();
462431
}
@@ -479,16 +448,15 @@ void QuaternionFloatingMobilizer<T>::DoMapAccelerationToQDDot(
479448
const Eigen::Ref<const VectorX<T>>& vdot,
480449
EigenPtr<VectorX<T>> qddot) const {
481450
// This function maps vdot to qddot by calculating q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
482-
// It first calculates the rotational part, then the translational part.
483451
//
484452
// For the rotational part of this mobilizer, the 2nd-derivatives of the
485453
// generalized positions q̈_FM = [q̈w, q̈x, q̈y, q̈z]ᵀ are related to the
486454
// 1st-derivatives of generalized velocities ω̇ = ẇ_FM_F = [ω̇x, ω̇y, ω̇z]ᵀ as
487-
// shown below where L(q_FM) is the 4x3 matrix documented in CalcLMatrix(),
488-
// ω = [ωx, ωy, ωz]ᵀ, ω² = |ω|², and 0.5 L̇(q̇_FM)⋅ω = -0.25 ω² q_FM.
455+
// shown below where Q_FM is the 4x3 matrix documented in CalcQMatrix(),
456+
// ω = [ωx, ωy, ωz]ᵀ, ω² = |ω|², and 0.5 Q̇_FM⋅ω = -0.25 ω² q_FM.
489457
//
490-
// q̈_FM = 0.5 L(q_FM)⋅ω̇ + 0.5 L̇(q̇_FM)⋅ω
491-
// = 0.5 L(q_FM)⋅ω̇ - 0.25 ω² q_FM
458+
// q̈_FM = 0.5 Q_FM⋅ω̇ + 0.5 Q̇_FM⋅ω
459+
// = 0.5 Q_FM⋅ω̇ - 0.25 ω² q_FM
492460
//
493461
// Formulas and proofs in Sections 9.3 and 9.6 of [Mitiguy, August 2025].
494462
// [Mitiguy, August 2025] Mitiguy, P. Advanced Dynamics & Motion Simulation.
@@ -497,12 +465,12 @@ void QuaternionFloatingMobilizer<T>::DoMapAccelerationToQDDot(
497465
const Vector3<T> w_FM_F = get_angular_velocity(context);
498466
const T w_squared_over_four = 0.25 * w_FM_F.squaredNorm();
499467
qddot->template head<4>() =
500-
CalcLMatrixOverTwo(q_FM) * vdot.template head<3>() -
468+
CalcQMatrixOverTwo(q_FM) * vdot.template head<3>() -
501469
w_squared_over_four * Vector4<T>(q_FM.w(), q_FM.x(), q_FM.y(), q_FM.z());
502470

503-
// Calculate the 2nd-derivative of the position vector p_FoMo_F = [x, y, z]ᵀ
504-
// in terms of 1st-derivative of the velocity v_FoM_F = [vx, vy, vz]ᵀ as
505-
// [ẍ, ÿ, z̈]ᵀ = [v̇x, v̇y, v̇z]ᵀ.
471+
// For the translational part of this mobilizer, the 2nd-derivative of the
472+
// position vector p_FoMo_F = [x, y, z]ᵀ is related to the 1st-derivative of
473+
// the velocity v_FoM_F = [vx, vy, vz]ᵀ as [ẍ, ÿ, z̈]ᵀ = [v̇x, v̇y, v̇z]ᵀ.
506474
qddot->template tail<3>() = vdot.template tail<3>();
507475
}
508476

multibody/tree/quaternion_floating_mobilizer.h

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

333333
private:
334-
// Returns the 4x3 matrix L(q_FM) where q_FM = [qw, qx, qy, qz]ᵀ is the
335-
// quaternion relating frames F and M.
336-
//
337-
// ⌈ -qx -qy -qz ⌉
338-
// L(q_FM) = | qw qz -qy |
334+
// Forms a 4x3 matrix whose elements depend linearly on the 4 elements of
335+
// the quaternion q = [qw, qx, qy, qz] as shown below.
336+
// @param[in] q a generic quaternion which is not necessarily a unit
337+
// quaternion or a quaternion associated with a rotation matrix.
338+
// @returns ⌈ -qx -qy -qz ⌉
339+
// | qw qz -qy |
339340
// | -qz qw qx |
340341
// ⌊ qy -qx qw ⌋
341342
//
342-
// Note: 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 rotational part of the N
345-
// and N⁺ matrices are 0.5 * L(q_FM) and 2 * L(q_FM)ᵀ, respectively. Others:
343+
// @note Herein, we denote the function that forms this matrix as Q(q).
344+
// When q is the quaternion q_FM that relates the orientation of frames F
345+
// F and M, we define the matrix Q_FM ≜ Q(q_FM).
346+
// Many uses of Q_FM are associated with an angular velocity expressed in
347+
// a particular frame. The examples below show Q_FM used in conjunction
348+
// with w_FM_F (frame M's angular velocity in frame F, expressed in F).
349+
//
350+
// q̇_FM = 0.5 * Q_FM * w_FM_F
351+
// q̈_FM = 0.5 * Q_FM * ẇ_FM_F - 0.25 ω² q_FM Note: ω² = |w_FM_F|²
352+
// w_FM_F = 2 * (Q_FM)ᵀ * q̇_FM
353+
// ẇ_FM_F = 2 * (Q_FM)ᵀ * q̈_FM
346354
//
347-
// q̇_FM = 0.5 * L(q_FM) * w_FM_F
348-
// q̈_FM = 0.5 * L(q_FM) * ẇ_FM_F - 0.25 ω² q_FM Note: ω² = |w_FM_F|²
349-
// w_FM_F = 2 * L(q_FM)ᵀ * q̇_FM
350-
// ẇ_FM_F = 2 * L(q_FM)ᵀ * q̇_FM
355+
// @note Since the elements of the matrix returned by Q(q) depend linearly on
356+
// qw, qx, qy, qz, s * Q(q) = Q(s * q), where s is a scalar (e.g., 0.5 or 2).
351357
//
352-
// Note: Since L(q_FM) depends linearly on q_FM, s * L(q_FM) = L(s * q_FM),
353-
// where s is a scalar (e.g., 0.5 or 2).
354-
static Eigen::Matrix<T, 4, 3> CalcLMatrix(const Quaternion<T>& q);
355-
356-
// Returns the 4x3 matrix 0.5 * L(q_FM), where q_FM = [qw, qx, qy, qz] is the
357-
// quaternion relating frames F and M. See documentation in CalcLMatrix(),
358-
static Eigen::Matrix<T, 4, 3> CalcLMatrixOverTwo(const Quaternion<T>& q_FM) {
359-
return CalcLMatrix(
360-
{0.5 * q_FM.w(), 0.5 * q_FM.x(), 0.5 * q_FM.y(), 0.5 * q_FM.z()});
358+
// Formulas, uses, and proofs are in Sections 9.3 and 9.6 of [Mitiguy].
359+
// [Mitiguy, August 2025] Mitiguy, P. Advanced Dynamics & Motion Simulation.
360+
// Textbook available at www.MotionGenesis.com
361+
static Eigen::Matrix<T, 4, 3> CalcQMatrix(const Quaternion<T>& q);
362+
363+
// Efficiently calculates the 4x3 matrix 0.5 * CalcQMatrix(q).
364+
// @param[in] q a generic quaternion which is not necessarily a unit
365+
// quaternion or a quaternion associated with a rotation matrix.
366+
// @see QuaternionFloatingMobilizer::CalcQMatrix().
367+
static Eigen::Matrix<T, 4, 3> CalcQMatrixOverTwo(const Quaternion<T>& q) {
368+
return CalcQMatrix({0.5 * q.w(), 0.5 * q.x(), 0.5 * q.y(), 0.5 * q.z()});
361369
}
362370

363-
// Returns the 3x4 matrix 2 * L(q_FM)ᵀ, where q_FM = [qw, qx, qy, qz] is the
364-
// quaternion relating frames F and M. See documentation in CalcLMatrix().
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()})
371+
// Efficiently calculates the 3x4 matrix [2 * CalcQMatrix(q)]ᵀ.
372+
// @param[in] q a generic quaternion which is not necessarily a unit
373+
// quaternion or a quaternion associated with a rotation matrix.
374+
// @see QuaternionFloatingMobilizer::CalcQMatrix().
375+
static Eigen::Matrix<T, 3, 4> CalcTwoTimesQMatrixTranspose(
376+
const Quaternion<T>& q) {
377+
return CalcQMatrix({2 * q.w(), 2 * q.x(), 2 * q.y(), 2 * q.z()})
368378
.transpose();
369379
}
370380

371381
// Helper to compute the kinematic map N⁺(q) from quaternion time derivative
372-
// to angular velocity for which w_WB = N⁺(q)⋅q̇_WB.
382+
// to angular velocity for which w_FM_F = N⁺(q)⋅q̇_FM.
373383
// This method can take a non unity quaternion q_tilde such that
374-
// w_WB = N⁺(q_tilde)⋅q̇_tilde_WB also holds true.
375-
// With L given by CalcLMatrix we have:
376-
// N⁺(q) = L(2 q_FM)ᵀ
384+
// w_FM_F = N⁺(q_tilde)⋅q̇_tilde_FM also holds true.
385+
// @returns N⁺(q_tilde)
377386
static Eigen::Matrix<T, 3, 4> QuaternionRateToAngularVelocityMatrix(
378387
const Quaternion<T>& q);
379388

0 commit comments

Comments
 (0)