Skip to content

Commit f82318f

Browse files
committed
Fix composite body code so more tests can pass.
1 parent ac1fbbd commit f82318f

3 files changed

Lines changed: 96 additions & 93 deletions

File tree

multibody/plant/test/composite_test.cc

Lines changed: 51 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,14 @@ TestModel MakeModel(bool combine_welded_bodies) {
124124
// Sanity check: Some information in the SpanningForest should be the same,
125125
// whether or not the welded links are combined.
126126
const internal::SpanningForest& forest = tree.forest();
127+
const internal::SpanningForest::Mobod& mobod_0 =
128+
forest.mobods(internal::MobodIndex(0));
127129
const internal::SpanningForest::Mobod& mobod_1 =
128130
forest.mobods(internal::MobodIndex(1));
129-
EXPECT_EQ(mobod_1.active_link_ordinal(), LinkOrdinal(1));
131+
EXPECT_TRUE(mobod_0.is_world()); // World is mobod(0) and LinkOrdinal(0).
130132
EXPECT_TRUE(mobod_1.is_base_body()); // Connects to World via revolute joint.
133+
EXPECT_EQ(mobod_0.active_link_ordinal(), LinkOrdinal(0));
134+
EXPECT_EQ(mobod_1.active_link_ordinal(), LinkOrdinal(1));
131135

132136
// If the welded links are combined, verify that the composite mobilized body
133137
// has the proper follower link ordinals (the active link ordinal which
@@ -175,96 +179,68 @@ GTEST_TEST(CompositeTest, CompositeSpatialInertia) {
175179
const TestModel explicit_model = MakeModel(false /* no combining */);
176180
const TestModel composite_model = MakeModel(true /* combine welds */);
177181

182+
const double m = 1.0, a = 0.1; // mass m and side-length a of solid cubes.
183+
const Frame<double>& world_frame = explicit_model.plant->world_frame();
184+
const RigidBody<double>* explicit_links[] = {
185+
explicit_model.link1, explicit_model.link2, explicit_model.link3,
186+
explicit_model.link4};
187+
const RigidBody<double>* composite_links[] = {
188+
composite_model.link1, composite_model.link2, composite_model.link3,
189+
composite_model.link4};
190+
178191
// The mass matrix is configuration-independent for this model (see above),
179192
// but we check at several angles to guard against future changes.
180193
const std::vector<double> angles = {0.0, M_PI / 6, M_PI / 4, -M_PI / 3};
181194
for (double angle : angles) {
182195
SetState(explicit_model, angle, 0.0);
183196
SetState(composite_model, angle, 0.0);
184197

185-
// Verify Link123's spatial inertia not depend on combined welded links
186-
const Frame<double>& world_frame = explicit_model.plant->world_frame();
198+
// Verify Link123's spatial inertia not depend on combined welded links.
199+
// Note: Due to Bug 1 above, the following test used to fail (now fixed)
187200
SpatialInertia<double> M_EWo_W = explicit_model.plant->CalcSpatialInertia(
188201
*explicit_model.context, world_frame,
189202
{explicit_model.link1->index(), explicit_model.link2->index(),
190203
explicit_model.link3->index()});
191-
192-
// TODO(Mitiguy) This next tests should be EXPECT_TRUE, hence a bug in
193-
// MultibodyPlant::CalcSpatialInertia(), maybe something like poses
194-
// using MoBod index rather linkIndex or cache not updated or ...
195204
SpatialInertia<double> M_CWo_W = composite_model.plant->CalcSpatialInertia(
196205
*composite_model.context, world_frame,
197206
{composite_model.link1->index(), composite_model.link2->index(),
198-
composite_model.link3->index()});
199-
EXPECT_FALSE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
207+
composite_model.link3->index()});
208+
EXPECT_TRUE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
200209
M_CWo_W.CopyToFullMatrix6(), kTolerance,
201210
MatrixCompareType::relative))
202-
<< "Spatial inertia mismatch at angle = " << angle;
203-
204-
// TODO(Mitiguy) The next three tests show that there is a bug in
205-
// MultibodyPlant::CalcSpatialInertia() that incorrectly associates the
206-
// Link123 composite spatial inertia with a single link (Link 1 or 2 or 3).
207-
M_CWo_W = composite_model.plant->CalcSpatialInertia(
208-
*composite_model.context, world_frame,
209-
{composite_model.link1->index()});
210-
EXPECT_FALSE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
211-
M_CWo_W.CopyToFullMatrix6(), kTolerance,
212-
MatrixCompareType::relative))
213-
<< "Spatial inertia mismatch at angle = " << angle;
214-
215-
// TODO(Mitiguy) This test and the previous and next test should NOT pass.
216-
M_CWo_W = composite_model.plant->CalcSpatialInertia(
217-
*composite_model.context, world_frame,
218-
{composite_model.link2->index()});
219-
EXPECT_FALSE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
220-
M_CWo_W.CopyToFullMatrix6(), kTolerance,
221-
MatrixCompareType::relative))
222-
<< "Spatial inertia mismatch at angle = " << angle;
223-
224-
// TODO(Mitiguy) This test and the previous two test should NOT pass.
225-
M_CWo_W = composite_model.plant->CalcSpatialInertia(
226-
*composite_model.context, world_frame,
227-
{composite_model.link3->index()});
228-
EXPECT_FALSE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
229-
M_CWo_W.CopyToFullMatrix6(), kTolerance,
230-
MatrixCompareType::relative))
231-
<< "Spatial inertia mismatch at angle = " << angle;
232-
233-
// Calculate Link4's spatial inertia about Wo (world origin) expressed in W.
234-
const double m = 1.0, a = 0.1; // Cube's mass (kg) and side-length (meter).
235-
const Vector3<double> p_WoL4o_W(4.0, 0.0, 0.0);
236-
SpatialInertia<double> M_L4Wo_W_expected =
237-
SpatialInertia<double>::SolidCubeWithMass(m, a).Shift(-p_WoL4o_W);
238-
239-
// Ensure the spatial inertia for Link 4 is correct in the explicit model.
240-
SpatialInertia<double> M_L4Wo_W = explicit_model.plant->CalcSpatialInertia(
241-
*explicit_model.context, world_frame, {explicit_model.link4->index()});
242-
EXPECT_TRUE(CompareMatrices(M_L4Wo_W.CopyToFullMatrix6(),
243-
M_L4Wo_W_expected.CopyToFullMatrix6(),
244-
kTolerance, MatrixCompareType::relative))
245-
<< "Spatial inertia mismatch for Link4 at angle = " << angle;
246-
247-
// Ensure the spatial inertia for Link 4 is correct in the composite model.
248-
// TODO(Mitiguy) There is a bug in MultibodyPlant::CalcSpatialInertia() when
249-
// composite mobilized bodies are used. The spatial inertia for the World
250-
// body (which is nan) replaces that of Link 4 (which is welded to world).
251-
// TODO(Mitiguy) Remove dead code after we are sure the bug is fixed.
252-
if (!kDrakeAssertIsArmed) {
253-
M_L4Wo_W = composite_model.plant->CalcSpatialInertia(
254-
*composite_model.context, world_frame,
255-
{composite_model.link4->index()});
256-
// TODO(Mitiguy) This test should be EXPECT_TRUE not EXPECT_FALSE.
257-
EXPECT_TRUE(CompareMatrices(M_L4Wo_W.CopyToFullMatrix6(),
258-
M_L4Wo_W_expected.CopyToFullMatrix6(),
259-
kTolerance, MatrixCompareType::relative))
260-
<< "Spatial inertia mismatch for Link4 at angle = " << angle;
261-
} else {
262-
// TODO(Mitiguy) Fix as an exception should not be thrown here.
263-
DRAKE_EXPECT_THROWS_MESSAGE_IF_ARMED(
264-
composite_model.plant->CalcSpatialInertia(
265-
*composite_model.context, world_frame,
266-
{composite_model.link4->index()}),
267-
"[\\s\\S]*did not pass the test CouldBePhysicallyValid[\\s\\S]*");
211+
<< "Link123 spatial inertia mismatch at angle = " << angle;
212+
213+
// Tests that uncovered old bugs in MultibodyPlant::CalcSpatialInertia().
214+
// Bug 1: When CalcSpatialInertia() was called with composite_model on a
215+
// single link (Link 1 or 2 or 3), the returned spatial inertia was for the
216+
// entire Link123 composite mobod.
217+
// Bug 2: When CalcSpatialInertia() was called with composite_model on link4
218+
// (which is welded to world), an exception was thrown (debug mode) or NaN
219+
// were returned (release mode).
220+
for (int i = 0; i < 3; ++i) {
221+
const RigidBody<double>* explicit_linki = explicit_links[i];
222+
const RigidBody<double>* composite_linki = composite_links[i];
223+
M_EWo_W = explicit_model.plant->CalcSpatialInertia(
224+
*explicit_model.context, world_frame, {explicit_linki->index()});
225+
M_CWo_W = composite_model.plant->CalcSpatialInertia(
226+
*composite_model.context, world_frame, {composite_linki->index()});
227+
EXPECT_TRUE(CompareMatrices(M_EWo_W.CopyToFullMatrix6(),
228+
M_CWo_W.CopyToFullMatrix6(), kTolerance,
229+
MatrixCompareType::relative))
230+
<< "Spatial inertia mismatch: link" << i + 1
231+
<< " at angle = " << angle;
232+
233+
// Due to special case of link4 being welded to world, compare link4's
234+
// spatial inertia to its expected value.
235+
if (i == 3) {
236+
const Vector3<double> p_WoL4o_W(4.0, 0.0, 0.0);
237+
SpatialInertia<double> M_L4Wo_W_expected =
238+
SpatialInertia<double>::SolidCubeWithMass(m, a).Shift(-p_WoL4o_W);
239+
EXPECT_TRUE(CompareMatrices(M_L4Wo_W_expected.CopyToFullMatrix6(),
240+
M_CWo_W.CopyToFullMatrix6(), kTolerance,
241+
MatrixCompareType::relative))
242+
<< "Inaccurate link4 spatial inertia at angle = " << angle;
243+
}
268244
}
269245

270246
// Ensure the mass matrix does not depend on welded links being combined.

multibody/tree/body_node_impl.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,31 @@ void BodyNodeImpl<T, ConcreteMobilizer>::CalcPositionKinematicsCache_BaseToTip(
113113
break;
114114
}
115115

116+
#if 0
116117
// TODO(sherm1) Calculate X_WL for composites. Currently we don't make
117118
// composites so X_WL = X_WB if L is the link on B.
118119
pc->SetX_WL(mobilizer_->mobod().active_link_ordinal(), X_WB);
120+
#endif
121+
122+
// Set X_WL for the active (most-inboard) link of this mobod. Its link frame
123+
// L coincides with the mobod frame B (X_BL = Identity), so X_WL = X_WB.
124+
const SpanningForest::Mobod& mobod = mobilizer_->mobod();
125+
pc->SetX_WL(mobod.active_link_ordinal(), X_WB);
126+
127+
// For composite mobods, also set X_WL for each follower (non-active) link Lₒ.
128+
// Because Lₒ is rigidly offset from B by X_BLₒ (pre-computed in
129+
// frame_body_pose_cache during CalcFrameBodyPoses), its world pose is simply:
130+
// X_WLₒ = X_WB * X_BLₒ
131+
if (mobod.is_composite()) {
132+
const auto& followers = mobod.follower_link_ordinals();
133+
// followers[0] is the active link (already handled above); start at 1.
134+
for (int i = 1; i < ssize(followers); ++i) {
135+
const LinkOrdinal follower_ordinal = followers[i];
136+
const math::RigidTransform<T>& X_BL =
137+
frame_body_pose_cache.get_X_BL(follower_ordinal);
138+
pc->SetX_WL(follower_ordinal, X_WB * X_BL);
139+
}
140+
}
119141

120142
// Compute shift vector p_PoBo_W from the parent origin to the body origin.
121143
const Vector3<T>& p_PoBo_P = X_PB.translation();

multibody/tree/multibody_tree.cc

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,19 +1714,21 @@ void MultibodyTree<T>::CalcFrameBodyPoses(
17141714
frame_body_poses->SetX_BF(frame->index(), X_BL * X_LF);
17151715
}
17161716

1717-
// For every mobilized body, precalculate its body-frame spatial inertia
1718-
// M_BBo_B by shifting and accumulating the spatial inertia M_LLo_L of each
1719-
// of the links it mobilizes (we call those the mobod's "follower links").
1720-
// For a non-composite mobod there is only one follower link; for a composite
1721-
// there are multiple links welded together.
1717+
// Pass 4 (over all mobods): For every mobod, precalculate its body-frame
1718+
// spatial inertia M_BBo_B by shifting and accumulating the spatial inertia
1719+
// M_LLo_L of each of its "follower" links. For a non-composite mobod there is
1720+
// only one follower link. For a composite, multiple follower links are welded
1721+
// together as a rigid unit.
17221722
// We also calculate and save p_BoLcm_B, the offset from mobod origin Bo to
17231723
// each of its follower links' centers of mass, expressed in B.
17241724
for (const SpanningForest::Mobod& mobod : forest().mobods()) {
1725-
if (mobod.is_world()) continue;
1726-
1727-
// Zero out the Mobod's mass properties. We'll build these up by
1728-
// accumulating contributions from all follower links.
1729-
frame_body_poses->SetM_BBo_B(mobod.index(), SpatialInertia<T>::Zero());
1725+
// Initialize each non-world Mobod's mass properties to zero and then
1726+
// accumulate mass contributions from all of its follower links.
1727+
// Note: The world mobod's spatial inertia is intentionally left as NaN.
1728+
const bool is_world_mobod = mobod.is_world();
1729+
if (!is_world_mobod) {
1730+
frame_body_poses->SetM_BBo_B(mobod.index(), SpatialInertia<T>::Zero());
1731+
}
17301732

17311733
// Accumulate the spatial inertia from each link following this mobod.
17321734
for (const LinkOrdinal& link_ordinal : mobod.follower_link_ordinals()) {
@@ -1736,21 +1738,26 @@ void MultibodyTree<T>::CalcFrameBodyPoses(
17361738
link.CalcSpatialInertiaInBodyFrame(context);
17371739
frame_body_poses->SetM_LLo_L(link_ordinal, M_LLo_L);
17381740

1741+
// Set p_BoLcm_B and accumulate M_LLo_L into M_BBo_B (unless B is world).
17391742
const Vector3<T>& p_LoLcm_L = M_LLo_L.get_com();
17401743
if (!frame_body_poses->is_X_BL_identity(link_ordinal)) {
17411744
const RigidTransform<T>& X_BL =
17421745
frame_body_poses->get_X_BL(link_ordinal);
17431746
const math::RotationMatrix<T>& R_BL = X_BL.rotation();
17441747
const Vector3<T>& p_BoLo_B = X_BL.translation();
1745-
const SpatialInertia<T> M_LLo_B = M_LLo_L.ReExpress(R_BL);
1746-
const SpatialInertia<T> M_LBo_B = M_LLo_B.Shift(-p_BoLo_B);
1747-
frame_body_poses->AddToM_BBo_B(mobod.index(), M_LBo_B);
17481748
const Vector3<T> p_BoLcm_B = X_BL * p_LoLcm_L;
17491749
frame_body_poses->Set_p_BoLcm_B(link_ordinal, p_BoLcm_B);
1750+
if (!is_world_mobod) {
1751+
const SpatialInertia<T> M_LLo_B = M_LLo_L.ReExpress(R_BL);
1752+
const SpatialInertia<T> M_LBo_B = M_LLo_B.Shift(-p_BoLo_B);
1753+
frame_body_poses->AddToM_BBo_B(mobod.index(), M_LBo_B);
1754+
}
17501755
} else {
1751-
// X_BL is identity.
1752-
frame_body_poses->AddToM_BBo_B(mobod.index(), M_LLo_L);
1756+
// X_BL is identity, so p_BoLcm_B = p_LoLcm_L and M_LBo_B = M_LLo_L.
17531757
frame_body_poses->Set_p_BoLcm_B(link_ordinal, p_LoLcm_L);
1758+
if (!is_world_mobod) {
1759+
frame_body_poses->AddToM_BBo_B(mobod.index(), M_LLo_L);
1760+
}
17541761
}
17551762
}
17561763
}
@@ -2549,9 +2556,7 @@ SpatialInertia<T> MultibodyTree<T>::CalcSpatialInertia(
25492556

25502557
// Add each body's spatial inertia in the world frame W to this system
25512558
// S's spatial inertia in W about Wo (the origin of W), expressed in W.
2552-
// TODO(Mitiguy) Create SpatialInertia<T>::Zero() and use it below.
2553-
SpatialInertia<T> M_SWo_W(0., Vector3<T>::Zero(),
2554-
UnitInertia<T>::TriaxiallySymmetric(0));
2559+
SpatialInertia<T> M_SWo_W = SpatialInertia<T>::Zero();
25552560

25562561
for (BodyIndex body_index : body_indexes) {
25572562
if (body_index == world_index()) continue; // World inertia does not add.

0 commit comments

Comments
 (0)