@@ -375,10 +375,40 @@ TEST_F(QuaternionFloatingMobilizerTest, MapUsesNplus) {
375
375
EXPECT_FALSE (std::abs (is_zero_if_proper_qdot) < 0.1 );
376
376
}
377
377
378
+ TEST_F (QuaternionFloatingMobilizerTest, MapVelocityToQDotAndViceVersa) {
379
+ // Set an arbitrary non-zero state, but with a non-unit quaternion.
380
+ const Quaternion<double > q_FM (0.7 , 0.8 , 0.9 , -1.2 ); // Unnormalized.
381
+ const Vector3<double > wxyz (5.4 , -9.8 , 3.2 );
382
+ const Vector3<double > vxyz (0.2 , 0.5 , -0.87 );
383
+ const Vector6<double > v (wxyz[0 ], wxyz[1 ], wxyz[2 ], vxyz[0 ], vxyz[1 ], vxyz[2 ]);
384
+ mobilizer_->SetQuaternion (context_.get (), q_FM);
385
+ mobilizer_->SetAngularVelocity (context_.get (), wxyz);
386
+ mobilizer_->SetTranslationalVelocity (context_.get (), vxyz);
387
+
388
+ // Calculate the qdot associated with angular and translational velocity.
389
+ Eigen::Matrix<double , 7 , 1 > qdot;
390
+ mobilizer_->MapVelocityToQDot (*context_, v, &qdot);
391
+
392
+ // Check if q̇_FM (the rotational part of the calculated qdot) satisfies the
393
+ // time-derivative of the quaternion constraint (q_FM)ᵀ * q_FM = constant, so
394
+ // (q̇_FM)ᵀ * q_FM + (q_FM)ᵀ * q̇_FM = 2 * (q_FM)ᵀ * q̇_FM = 0. However, since
395
+ // the qdot above is arbitrary, this is not true.
396
+ const Vector4<double > qdot_FM (qdot[0 ], qdot[1 ], qdot[2 ], qdot[3 ]);
397
+ const double is_zero_if_proper_qdot =
398
+ Vector4<double >(q_FM.w (), q_FM.x (), q_FM.y (), q_FM.z ()).dot (qdot_FM);
399
+ EXPECT_TRUE (std::abs (is_zero_if_proper_qdot) < 16 * kTolerance );
400
+
401
+ // Use qdot to calculate its associated angular and translational velocity.
402
+ Vector6<double > v_from_qdot;
403
+ mobilizer_->MapQDotToVelocity (*context_, qdot, &v_from_qdot);
404
+ EXPECT_TRUE (CompareMatrices (v, v_from_qdot, 16 * kTolerance ,
405
+ MatrixCompareType::relative));
406
+ }
407
+
378
408
TEST_F (QuaternionFloatingMobilizerTest, MapAccelerationToQDDotAndViceVersa) {
379
409
// Set an arbitrary non-zero state.
380
410
const math::RollPitchYaw<double > rpy (M_PI / 3 , -M_PI / 4 , M_PI / 5 );
381
- const Quaternion<double > q_FM = rpy.ToQuaternion ();
411
+ Quaternion<double > q_FM = rpy.ToQuaternion ();
382
412
const Vector3<double > wxyz (5.4 , -9.8 , 3.2 );
383
413
const Vector3<double > vxyz (0.2 , 0.5 , -0.87 );
384
414
const Vector6<double > v (wxyz[0 ], wxyz[1 ], wxyz[2 ], vxyz[0 ], vxyz[1 ], vxyz[2 ]);
@@ -435,6 +465,17 @@ TEST_F(QuaternionFloatingMobilizerTest, MapAccelerationToQDDotAndViceVersa) {
435
465
MatrixX<double > Nplus_rotational = Nplus.block <3 , 4 >(0 , 0 );
436
466
EXPECT_TRUE (CompareMatrices (N_rotational.transpose (), 0.25 * Nplus_rotational,
437
467
kTolerance , MatrixCompareType::relative));
468
+
469
+ // Repeat these experiments with an unnormalized (non-unit) quaternion.
470
+ // Use MapAccelerationToQDDot() to calculate q̈ and then use this calculated q̈
471
+ // with MapQDDotToAcceleration() to calculate v̇.
472
+ // Verify MapQDDotToAcceleration() is the inverse of MapAccelerationToQDDot().
473
+ q_FM = Quaternion<double >(0.7 , 0.8 , 0.9 , -1.2 ); // Unnormalized.
474
+ mobilizer_->SetQuaternion (context_.get (), q_FM);
475
+ mobilizer_->MapAccelerationToQDDot (*context_, vdot, &qddot);
476
+ mobilizer_->MapQDDotToAcceleration (*context_, qddot, &vdot_redo);
477
+ EXPECT_TRUE (CompareMatrices (vdot_redo, vdot, 16 * kTolerance ,
478
+ MatrixCompareType::relative));
438
479
}
439
480
440
481
} // namespace
0 commit comments