@@ -279,44 +279,11 @@ void QuaternionFloatingMobilizer<T>::ProjectSpatialForce(
279279}
280280
281281template <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
353320template <typename T>
354321void 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
366334template <typename T>
367335void 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
0 commit comments