Skip to content

Commit 43bad7a

Browse files
committed
Change MultibodyPlant::CalcSpatialMomentumInWorldAboutPoint() to work directly with mobods. Add tests to show defect in other spatial momentum signatures.
1 parent 7eed083 commit 43bad7a

2 files changed

Lines changed: 71 additions & 37 deletions

File tree

multibody/plant/test/fused_welds_test.cc

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Tests that mass properties are identical whether welded-together links are
22
modeled with unfused weld joints or whether links that are welded together
3-
are fused into a mobilized body (fused Mobod). The test builds two identical
3+
are fused onto a mobilized body (fused Mobod). The test builds two identical
44
models that differ only in whether SetFuseWeldedLinks() is enabled. */
55

66
#include <limits>
@@ -158,15 +158,16 @@ void SetState(const TestModel& m, double angle_rad, double angular_vel) {
158158
}
159159

160160
/* Verify that the spatial inertia for fused mobod Link123 (with links 1, 2, 3)
161-
matches the sum of spatial inertias for unfused links 1, 2, 3. Next, ensure
162-
spatial inertia for each of the follower links in a fused mobod are computed
163-
correctly. Lastly, the 1x1 mass matrix for this 1-DOF model directly depends on
164-
the spatial inertia of Link123, so we also verify:
165-
(a) The mass matrix is identical between the fused and unfused weld models at
166-
several configurations.
161+
matches the sum of spatial inertias for unfused links 1, 2, 3 at several poses.
162+
Next, ensure spatial inertia for each of the follower links in a fused mobod are
163+
computed correctly. Lastly, the 1x1 mass matrix for this 1-DOF model directly
164+
depends on the spatial inertia of Link123, so we also verify:
165+
(a) The mass matrix is identical between the fused and unfused weld models.
167166
(b) The mass matrix matches the analytically computed value.
168167
169-
Analytical derivation
168+
Similarly, verify spatial momentum calculations.
169+
170+
Analytical derivation for spatial inertia.
170171
---------------------
171172
The model has 4 links, Linki (i=1,2,3,4), each a 1 kg solid cube of side 0.1 m.
172173
Links 1,2,3 are welded together and are connected to World via a revolute joint
@@ -181,8 +182,7 @@ For a solid cube of mass m and side a, its moment of inertia about any axis
181182
through its COM is m*a²/6. The parallel axis theorem calculates each cube's
182183
moment of inertia about the revolute's z-axis via: Iᵢ = m*a²/6 + m*(dᵢ)²,
183184
where dᵢ (i=1,2,3) is the distance between each cube's COM and the revolute's
184-
z-axis. Iᵢ is independent of joint angle because the links are welded
185-
together.
185+
z-axis. Iᵢ is independent of joint angle because the links are welded together.
186186
187187
Link1: I₁ = 1*(0.1)²/6 + 1*0² = 1/600 + 0 (d² = 0)
188188
Link2: I₂ = 1*(0.1)²/6 + 1*1² = 1/600 + 1 (d² = 1)
@@ -201,15 +201,15 @@ GTEST_TEST(FusedTest, CompositeSpatialInertia) {
201201
fused_model.link1, fused_model.link2, fused_model.link3,
202202
fused_model.link4};
203203

204-
// The mass matrix is configuration-independent for this model (see above),
205-
// but we check at several angles to guard against future changes.
204+
// Note: The mass matrix is configuration-independent for this model (see
205+
// above), but we check at several angles to guard against future changes.
206+
// Note: Use a non-zero angular velocity so spatial momentum is non-trivial.
206207
const std::vector<double> angles = {0.0, M_PI / 6, M_PI / 4, -M_PI / 3};
207208
for (double angle : angles) {
208-
SetState(unfused_model, angle, 0.0);
209-
SetState(fused_model, angle, 0.0);
209+
SetState(unfused_model, angle, 2.0 /* rad/s */);
210+
SetState(fused_model, angle, 2.0 /* rad/s */);
210211

211-
// Verify Link123's summed spatial inertia does not depend on whether they
212-
// are on a fused mobod.
212+
// Verify Link123's summed spatial inertia does not depend on fused links.
213213
SpatialInertia<double> M_UWo_W = unfused_model.plant->CalcSpatialInertia(
214214
*unfused_model.context, world_frame,
215215
{unfused_model.link1->index(), unfused_model.link2->index(),
@@ -223,8 +223,29 @@ GTEST_TEST(FusedTest, CompositeSpatialInertia) {
223223
MatrixCompareType::relative))
224224
<< "Link123 spatial inertia mismatch at angle = " << angle;
225225

226-
// Ensure that individual link spatial inertias are reported correctly
227-
// regardless of whether they were fused.
226+
// Verify Link123's summed spatial momentum does not depend on fused links.
227+
const Vector3<double> p_WoP_W(1.1, -2.3, 4.2);
228+
SpatialMomentum<double> L_WUP_W =
229+
unfused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
230+
*unfused_model.context,
231+
{unfused_model.link1->model_instance(),
232+
unfused_model.link2->model_instance(),
233+
unfused_model.link3->model_instance()},
234+
p_WoP_W);
235+
SpatialMomentum<double> L_WCP_W =
236+
fused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
237+
*fused_model.context,
238+
{fused_model.link1->model_instance(),
239+
fused_model.link2->model_instance(),
240+
fused_model.link3->model_instance()},
241+
p_WoP_W);
242+
// TODO(Mitiguy) EXPECT_FALSE is wrong! Should be EXPECT_TRUE!
243+
EXPECT_FALSE(CompareMatrices(L_WUP_W.get_coeffs(), L_WCP_W.get_coeffs(),
244+
kTolerance, MatrixCompareType::relative))
245+
<< "Link123 spatial momentum mismatch at angle = " << angle;
246+
247+
// Ensure that individual link spatial inertias and spatial momentum are
248+
// accurately calculated, regardless of whether they were fused.
228249
for (int i = 0; i < 4; ++i) {
229250
const RigidBody<double>* unfused_linki = unfused_links[i];
230251
const RigidBody<double>* fused_linki = fused_links[i];
@@ -238,6 +259,17 @@ GTEST_TEST(FusedTest, CompositeSpatialInertia) {
238259
<< "Spatial inertia mismatch: link" << i + 1
239260
<< " at angle = " << angle;
240261

262+
// Verify individual link's spatial momentum do not depend on fused links.
263+
L_WUP_W = unfused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
264+
*unfused_model.context, {unfused_linki->model_instance()}, p_WoP_W);
265+
L_WCP_W = fused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
266+
*fused_model.context, {fused_linki->model_instance()}, p_WoP_W);
267+
// TODO(Mitiguy) EXPECT_FALSE is wrong! Should be EXPECT_TRUE!
268+
EXPECT_FALSE(CompareMatrices(L_WUP_W.get_coeffs(), L_WCP_W.get_coeffs(),
269+
kTolerance, MatrixCompareType::relative))
270+
<< "Spatial momentum mismatch: link" << i + 1
271+
<< " at angle = " << angle;
272+
241273
// Since link4 is welded to world, special-case calculations are used. For
242274
// this special case, also compare link4 results to an analytical value.
243275
if (i == 3) {
@@ -248,6 +280,13 @@ GTEST_TEST(FusedTest, CompositeSpatialInertia) {
248280
M_CWo_W.CopyToFullMatrix6(), kTolerance,
249281
MatrixCompareType::relative))
250282
<< "Inaccurate link4 spatial inertia at angle = " << angle;
283+
284+
// Link4's spatial momentum should always be zero (welded to ground).
285+
// TODO(Mitiguy) EXPECT_FALSE is wrong! Should be EXPECT_TRUE!
286+
EXPECT_FALSE(CompareMatrices(L_WCP_W.get_coeffs(),
287+
Vector6<double>::Zero(), kTolerance,
288+
MatrixCompareType::relative))
289+
<< "Inaccurate link 4 spatial momentum at angle = " << angle;
251290
}
252291
}
253292

@@ -268,20 +307,20 @@ GTEST_TEST(FusedTest, CompositeSpatialInertia) {
268307
<< "Mass matrix analytical mismatch at angle = " << angle;
269308

270309
// Ensure spatial momentum does not depend on fused welded links.
271-
// Use a non-zero angular velocity so that the momentum is non-trivial.
272-
SetState(unfused_model, angle, 2.0 /* rad/s */);
273-
SetState(fused_model, angle, 2.0 /* rad/s */);
274-
const Vector3<double> p_WoWo_W = Vector3<double>::Zero();
310+
// Also, use an "about-point" P which is not coincident with Wo.
275311
const SpatialMomentum<double> L_unfused =
276312
unfused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
277-
*unfused_model.context, p_WoWo_W);
313+
*unfused_model.context, p_WoP_W);
314+
315+
// Test the function for the entire system's spatial momentum in world.
278316
const SpatialMomentum<double> L_fused =
279317
fused_model.plant->CalcSpatialMomentumInWorldAboutPoint(
280-
*fused_model.context, p_WoWo_W);
281-
// TODO(Mitiguy) EXPECT_FALSE is wrong, should be EXPECT_TRUE !
318+
*fused_model.context, p_WoP_W);
282319
EXPECT_TRUE(CompareMatrices(L_unfused.get_coeffs(), L_fused.get_coeffs(),
283320
kTolerance, MatrixCompareType::relative))
284321
<< "Spatial momentum mismatch at angle = " << angle;
322+
323+
// Test the function for a single link's spatial momentum in world.
285324
}
286325
}
287326

multibody/tree/multibody_tree.cc

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2843,7 +2843,7 @@ Vector3<T> MultibodyTree<T>::CalcCenterOfMassTranslationalAccelerationInWorld(
28432843
template <typename T>
28442844
SpatialMomentum<T> MultibodyTree<T>::CalcSpatialMomentumInWorldAboutPoint(
28452845
const systems::Context<T>& context, const Vector3<T>& p_WoP_W) const {
2846-
// Efficienctly evaluate all mobods' spatial inertias, velocities, and poses.
2846+
// Efficiently evaluate all mobods' spatial inertias, velocities, and poses.
28472847
const std::vector<SpatialInertia<T>>& M_Bi_W =
28482848
EvalSpatialInertiaInWorldCache(context);
28492849
const PositionKinematicsCache<T>& pc = EvalPositionKinematics(context);
@@ -2855,19 +2855,15 @@ SpatialMomentum<T> MultibodyTree<T>::CalcSpatialMomentumInWorldAboutPoint(
28552855
for (const SpanningForest::Mobod& mobod : forest().mobods()) {
28562856
if (mobod.is_world()) continue; // No contribution from the world.
28572857

2858-
// Form each mobod B's spatial momentum in W about Bo, expressed in W.
2858+
// Form each mobod B's spatial momentum in W about Bo, expressed in W, then
2859+
// shift L_WBBo_W from "about Bo" to "about Wo" and accumulate the sum.
28592860
const MobodIndex mobod_index = mobod.index();
28602861
const SpatialInertia<T>& M_BBo_W = M_Bi_W[mobod_index];
28612862
const SpatialVelocity<T>& V_WBo_W = vc.get_V_WB(mobod_index);
2862-
SpatialMomentum<T> L_WBBo_W = M_BBo_W * V_WBo_W;
2863-
2864-
// Shift L_WBBo_W from "about Bo" to "about Wo" and accumulate the sum.
28652863
const RigidTransform<T>& X_WB = pc.get_X_WB(mobod_index);
28662864
const Vector3<T>& p_WoBo_W = X_WB.translation();
2867-
// After ShiftInPlace, L_WBBo_W is no longer "about Bo", instead L_WBBo_W
2868-
// is now L_WBWo_W (B's spatial momentum in W "about Wo", expressed in W).
2869-
L_WBBo_W.ShiftInPlace(-p_WoBo_W); // After this, L_WBBo_W is now L_WBWo_W.
2870-
L_WSWo_W += L_WBBo_W; // Actually is L_WSWo_W += L_WBWo_W.
2865+
const SpatialMomentum<T> L_WBWo_W = (M_BBo_W * V_WBo_W).Shift(-p_WoBo_W);
2866+
L_WSWo_W += L_WBWo_W;
28712867
}
28722868

28732869
// Shift system S's spatial momentum from "about Wo" to "about point P".
@@ -2914,9 +2910,8 @@ SpatialMomentum<T> MultibodyTree<T>::CalcBodiesSpatialMomentumInWorldAboutWo(
29142910
const PositionKinematicsCache<T>& pc = EvalPositionKinematics(context);
29152911
const VelocityKinematicsCache<T>& vc = EvalVelocityKinematics(context);
29162912

2917-
// Accumulate each body's spatial momentum in the world frame W to this
2918-
// system S's spatial momentum in W about Wo (the origin of W), expressed in
2919-
// W.
2913+
// Accumulate each body's spatial momentum in world W to this system S's
2914+
// spatial momentum in W about Wo (the origin of W), expressed in W.
29202915
SpatialMomentum<T> L_WS_W = SpatialMomentum<T>::Zero();
29212916

29222917
// Add contributions from each link Bi.

0 commit comments

Comments
 (0)