Skip to content

Commit d43fc30

Browse files
author
Paul Mitiguy
committed
Fix documentation and proofs, improve tests to double-check results of proofs.
1 parent 27c7edd commit d43fc30

File tree

3 files changed

+53
-29
lines changed

3 files changed

+53
-29
lines changed

multibody/tree/quaternion_floating_mobilizer.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,11 +325,10 @@ void QuaternionFloatingMobilizer<T>::DoCalcNMatrix(
325325
const systems::Context<T>& context, EigenPtr<MatrixX<T>> N) const {
326326
// Upper-left block (rotational part of the N matrix) is Nᵣ ≜ 0.5 Q_FM.
327327
// See QuaternionFloatingMobilizer::CalcQMatrix() for details.
328-
// The lower-right block (translational part of the N matrix) is [I₃₃].
329328
N->template block<4, 3>(0, 0) = CalcQMatrixOverTwo(get_quaternion(context));
330329
N->template block<4, 3>(0, 3).setZero(); // Upper-right block.
331330
N->template block<3, 3>(4, 0).setZero(); // Lower-left block.
332-
N->template block<3, 3>(4, 3).setIdentity(); // Lower-right block.
331+
N->template block<3, 3>(4, 3).setIdentity(); // Lower-right block = [I₃₃].
333332
}
334333

335334
template <typename T>

multibody/tree/quaternion_floating_mobilizer.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -301,18 +301,18 @@ class QuaternionFloatingMobilizer final : public MobilizerImpl<T, 7, 6> {
301301
// ⎣ 0₃₃ I₃₃ ⎦ I₃₃ is the 3x3 identity matrix.
302302
// Nᵣ(q) = 0.5 * QuaternionFloatingMobilizer::CalcQMatrix() is a 4x3 matrix.
303303
// Note: The time-derivative of the quaternion qᵣ in context can be calculated
304-
// q̇ᵣ = Nᵣ(q) w_FM_F, where w_FM_F is frame M's angular velocity in frame F,
305-
// expressed in F. For a unit quaternion q̂ᵣ, we prove d/dt(q̂ᵣ) satisfies the
306-
// "orthogonality constraint" i.e., d/dt(q̂ᵣ ⋅ q̂ᵣ = 1) => q̂ᵣ ⋅ d/dt(q̂ᵣ) = 0
307-
// via q̂ᵣ ⋅ d/dt(q̂ᵣ) = q̂ᵣ ⋅ Nᵣ(q̂ᵣ) w_FM_F = [0 0 0] w_FM_F = 0.
308-
// For a non-unit quaternion qᵣ, the orthogonality constraint is proved via
309-
// qᵣ ⋅ d/dt(qᵣ) = qᵣ ⋅ Nᵣ(qᵣ) w_FM_F = |qᵣ| q̂ᵣ ⋅ |qᵣ| Nᵣ(q̂ᵣ) w_FM_F =
310-
// |qᵣ|² q̂ᵣ ⋅ Nᵣ(q̂ᵣ) w_FM_F = |qᵣ|² [0 0 0] w_FM_F = 0, where this proof
311-
// uses the fact Nᵣ(qᵣ) is linear in qᵣ and qᵣ = |qᵣ| q̂ᵣ.
304+
// q̇ᵣ = Nᵣ(q) vᵣ, where vᵣ are the rotational generalized velocities. For a
305+
// quaternion qᵣ, we prove q̇ᵣ satisfies the "orthogonality constraint".
306+
// Mathematically, the derivative of a unit or constant-length quaternion
307+
// i.e., qᵣ ⋅ qᵣ = constant is d/dt(qᵣ ⋅ qᵣ = constant) => qᵣ ⋅ q̇ᵣ = 0.
308+
// With q̇ᵣ = Nᵣ(q) vᵣ, we prove q̇ᵣ satisfies the orthogonality constraint via
309+
// qᵣ ⋅ q̇ᵣ = qᵣ ⋅ Nᵣ(qᵣ) vᵣ = |qᵣ| q̂ᵣ ⋅ |qᵣ| Nᵣ(q̂ᵣ) vᵣ
310+
// = |qᵣ|² q̂ᵣ ⋅ Nᵣ(q̂ᵣ) vᵣ = |qᵣ|² [0 0 0] vᵣ = 0, since we can prove
311+
// q̂ᵣ ⋅ Nᵣ(q̂ᵣ) = [0 0 0], where q̂ᵣ is a unit quaternion.
312312
// Summary: If the quaternion in context is a unit quaternion q̂ᵣ, then its
313-
// time-derivative can be calculated as d/dt(q̂ᵣ) = Nᵣ(q̂ᵣ) w_FM_F.
313+
// time-derivative can be calculated as d/dt(q̂ᵣ) = Nᵣ(q̂ᵣ) vᵣ.
314314
// If the quaternion is context is a non-unit quaternion, then its time-
315-
// derivative can be calculated d/dt(qᵣ) = Nᵣ(qᵣ) w_FM_F = |qᵣ| Nᵣ(q̂ᵣ) w_FM_F.
315+
// derivative can be calculated d/dt(qᵣ) = Nᵣ(qᵣ) vᵣ = |qᵣ| Nᵣ(q̂ᵣ) vᵣ.
316316
void DoCalcNMatrix(const systems::Context<T>& context,
317317
EigenPtr<MatrixX<T>> N) const final;
318318

multibody/tree/test/quaternion_floating_mobilizer_test.cc

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "drake/common/eigen_types.h"
1010
#include "drake/common/test_utilities/eigen_matrix_compare.h"
1111
#include "drake/common/test_utilities/expect_throws_message.h"
12+
#include "drake/math/quaternion.h"
1213
#include "drake/math/random_rotation.h"
1314
#include "drake/math/rotation_matrix.h"
1415
#include "drake/multibody/tree/multibody_tree-inl.h"
@@ -381,31 +382,55 @@ TEST_F(QuaternionFloatingMobilizerTest, MapUsesNplus) {
381382

382383
TEST_F(QuaternionFloatingMobilizerTest, MapVelocityToQDotAndViceVersa) {
383384
// Set an arbitrary non-zero state, but with a non-unit quaternion.
384-
const Quaternion<double> q_FM(0.7, 0.8, 0.9, -1.2); // Unnormalized.
385+
// Test with both a unnormalized and normalized quaternion q_FM.
386+
const Quaternion<double> q_nonUnit(0.7, 0.8, 0.9, -1.2); // Unnormalized.
387+
const Quaternion<double> q_unit = q_nonUnit.normalized(); // Unit quaternion.
385388
const Vector3<double> wxyz(5.4, -9.8, 3.2);
386389
const Vector3<double> vxyz(0.2, 0.5, -0.87);
387390
const Vector6<double> v(wxyz[0], wxyz[1], wxyz[2], vxyz[0], vxyz[1], vxyz[2]);
388-
mobilizer_->SetQuaternion(context_.get(), q_FM);
391+
mobilizer_->SetQuaternion(context_.get(), q_unit);
389392
mobilizer_->SetAngularVelocity(context_.get(), wxyz);
390393
mobilizer_->SetTranslationalVelocity(context_.get(), vxyz);
391394

392395
// Calculate the qdot associated with angular and translational velocity.
393-
Eigen::Matrix<double, 7, 1> qdot;
394-
mobilizer_->MapVelocityToQDot(*context_, v, &qdot);
395-
396-
// Check if q̇_FM (the rotational part of the calculated qdot) satisfies the
397-
// time-derivative of the quaternion constraint (q_FM)ᵀ * q_FM = constant, so
398-
// (q̇_FM)ᵀ * q_FM + (q_FM)ᵀ * q̇_FM = 2 * (q_FM)ᵀ * q̇_FM = 0. However, since
399-
// the qdot above is arbitrary, this is not true.
400-
const Vector4<double> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]);
401-
const double is_zero_if_proper_qdot =
402-
Vector4<double>(q_FM.w(), q_FM.x(), q_FM.y(), q_FM.z()).dot(qdot_FM);
403-
EXPECT_TRUE(std::abs(is_zero_if_proper_qdot) < 16 * kTolerance);
396+
Eigen::Matrix<double, 7, 1> qdot_unit; // Calculate qdot using q_unit.
397+
mobilizer_->MapVelocityToQDot(*context_, v, &qdot_unit);
398+
399+
// Use the previously calculated qdot to calculate an associated angular and
400+
// translational velocity -- and check that they match the original v.
401+
Vector6<double> v_from_qdot_unit;
402+
mobilizer_->MapQDotToVelocity(*context_, qdot_unit, &v_from_qdot_unit);
403+
EXPECT_TRUE(CompareMatrices(v, v_from_qdot_unit, 16 * kTolerance,
404+
MatrixCompareType::relative));
404405

405-
// Use qdot to calculate its associated angular and translational velocity.
406-
Vector6<double> v_from_qdot;
407-
mobilizer_->MapQDotToVelocity(*context_, qdot, &v_from_qdot);
408-
EXPECT_TRUE(CompareMatrices(v, v_from_qdot, 16 * kTolerance,
406+
// Ensure that the calculated q̇ᵣ (rotational part of the calculated qdot)
407+
// satisfies the time-derivative of the quaternion constraint.
408+
// Mathematically: (qᵣ)ᵀ * qᵣ = constant, so it should be true that
409+
// (q̇ᵣ)ᵀ * qᵣ + (qᵣ)ᵀ * q̇ᵣ = 2 * (qᵣ)ᵀ * q̇ᵣ = 0. Due to the fact that q̇ᵣ is
410+
// calculated as q̇ᵣ = Nᵣ * vᵣ, we can prove this is true. Double check here.
411+
const Vector4<double> qrdot_unit(qdot_unit[0], qdot_unit[1], qdot_unit[2],
412+
qdot_unit[3]);
413+
double is_zero_if_OK_qdot =
414+
math::CalculateQuaternionDtConstraintViolation(q_unit, qrdot_unit);
415+
EXPECT_TRUE(std::abs(is_zero_if_OK_qdot) < 16 * kTolerance);
416+
417+
// Repeat the previous calculations with a non-unit quaternion.
418+
mobilizer_->SetQuaternion(context_.get(), q_nonUnit);
419+
Eigen::Matrix<double, 7, 1> qdot_nonUnit; // Calculate qdot using q_nonUnit.
420+
mobilizer_->MapVelocityToQDot(*context_, v, &qdot_nonUnit);
421+
Vector6<double> v_from_qdot_nonUnit;
422+
mobilizer_->MapQDotToVelocity(*context_, qdot_nonUnit, &v_from_qdot_nonUnit);
423+
EXPECT_TRUE(CompareMatrices(v, v_from_qdot_nonUnit, 16 * kTolerance,
424+
MatrixCompareType::relative));
425+
const Vector4<double> qrdot_nonUnit(qdot_nonUnit[0], qdot_nonUnit[1],
426+
qdot_nonUnit[2], qdot_nonUnit[3]);
427+
is_zero_if_OK_qdot =
428+
math::CalculateQuaternionDtConstraintViolation(q_unit, qrdot_nonUnit);
429+
EXPECT_TRUE(std::abs(is_zero_if_OK_qdot) < 16 * kTolerance);
430+
431+
// Verify that qdot_nonUnit = |qr_nonUnit| * qdot_unit.
432+
const double s = q_nonUnit.norm();
433+
EXPECT_TRUE(CompareMatrices(qrdot_nonUnit, s * qrdot_unit, 16 * kTolerance,
409434
MatrixCompareType::relative));
410435
}
411436

0 commit comments

Comments
 (0)