Skip to content

Commit 8f8e43f

Browse files
committed
Fixed composites to always begin with World
1 parent 2e33236 commit 8f8e43f

File tree

5 files changed

+157
-31
lines changed

5 files changed

+157
-31
lines changed

multibody/topology/link_joint_graph.cc

+17
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ CompositeLinkIndex LinkJointGraph::AddToCompositeLink(
393393
return existing_composite;
394394
}
395395

396+
void LinkJointGraph::CreateWorldCompositeLink() {
397+
DRAKE_DEMAND(composite_links().empty() && !links().empty());
398+
Link& world_link = data_.links[LinkIndex(0)];
399+
DRAKE_DEMAND(!world_link.composite_link_index_.is_valid());
400+
data_.composite_links.emplace_back(std::vector{LinkIndex(0)});
401+
world_link.composite_link_index_ = CompositeLinkIndex(0);
402+
}
403+
396404
void LinkJointGraph::AddUnmodeledJointToComposite(
397405
JointIndex unmodeled_joint_index, CompositeLinkIndex composite_link_index) {
398406
Joint& joint = data_.joints[unmodeled_joint_index];
@@ -471,6 +479,15 @@ std::vector<std::set<LinkIndex>> LinkJointGraph::FindSubgraphsOfWeldedLinks()
471479
return subgraphs;
472480
}
473481

482+
#ifdef NOTDEF
483+
// Our composite_links collection doesn't include lone Links that aren't welded
484+
// to anything. The return from this function must include every Link, with
485+
// the World link in the first set (even if nothing is welded to it).
486+
std::vector<std::set<LinkIndex>> LinkJointGraph::FindSubgraphsOfWeldedLinks()
487+
const {
488+
}
489+
#endif
490+
474491
// As mentioned above, the use of "parent" here does not imply anything about
475492
// the Parent/Child Joint connection. It is really just the root of a subgraph.
476493
// TODO(sherm1) Reimplement using already-built Composites.

multibody/topology/link_joint_graph.h

+18-5
Original file line numberDiff line numberDiff line change
@@ -307,14 +307,22 @@ class LinkJointGraph {
307307
return data_.must_be_base_body_links;
308308
}
309309

310-
/** After the Forest has been built, returns groups of Links that are welded
311-
together. Each group may be modeled with a single mobilized body or multiple
312-
mobilized bodies depending on modeling options. The first entry in each group
313-
is the "representative link". Composites are present here only for groups of
314-
two or more welded Links. */
310+
/** After the SpanningForest has been built, returns groups of Links that are
311+
welded together. Each group may be modeled in the Forest with a single
312+
mobilized body or multiple mobilized bodies depending on modeling options,
313+
but that doesn't change anything here. The first entry in each group is the
314+
"representative link". The 0th Composite Link is always present and its first
315+
entry is World (Link 0), even if nothing else is welded to World. Otherwise,
316+
composites are present here only for groups of two or more welded Links.
317+
Composite Links are discovered as a side effect of Forest-building; there is
318+
no cost to accessing them here. */
315319
const std::vector<std::vector<LinkIndex>>& composite_links() const {
316320
return data_.composite_links;
317321
}
322+
const std::vector<LinkIndex>& composite_links(
323+
CompositeLinkIndex composite_link_index) const {
324+
return composite_links().at(composite_link_index);
325+
}
318326

319327
/** @returns `true` if a Link named `name` was added to `model_instance`.
320328
@see AddLink().
@@ -366,6 +374,7 @@ class LinkJointGraph {
366374
element zero in the returned vector.
367375
- The minimum number of subgraphs is one. This corresponds to a graph with
368376
all Links welded to the world. */
377+
std::vector<std::set<LinkIndex>> XFindSubgraphsOfWeldedLinks() const;
369378
std::vector<std::set<LinkIndex>> FindSubgraphsOfWeldedLinks() const;
370379

371380
/** Returns all Links that are transitively welded, or rigidly affixed, to
@@ -441,6 +450,10 @@ class LinkJointGraph {
441450
CompositeLinkIndex AddToCompositeLink(LinkIndex existing_link_index,
442451
LinkIndex new_link_index);
443452

453+
// The World Link must already be in the graph but there are no Composite
454+
// Links yet. This creates the 0th Composite Link and puts World in it.
455+
void CreateWorldCompositeLink();
456+
444457
// Notes that we didn't model this Joint in the Forest because it is just a
445458
// weld to an existing Composite.
446459
void AddUnmodeledJointToComposite(JointIndex unmodeled_joint_index,

multibody/topology/spanning_forest.cc

+6-2
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ void SpanningForest::BuildForest(
7171

7272
SetBaseBodyChoicePolicy();
7373

74-
// Model the World (Link 0) with mobilized body 0.
74+
// Model the World (Link 0) with mobilized body 0. This also starts the
75+
// 0th Composite Link in the graph and the 0th Composite Mobod in the Forest.
7576
data_.mobods.emplace_back(MobodIndex(0), LinkIndex(0));
77+
data_.composite_mobods.emplace_back(std::vector{MobodIndex(0)});
78+
data_.mobods[MobodIndex(0)].composite_mobod_index_ = CompositeMobodIndex(0);
7679
mutable_graph().set_primary_mobod_for_link(LinkIndex(0), MobodIndex(0),
7780
JointIndex{});
81+
mutable_graph().CreateWorldCompositeLink();
7882

7983
/* Decides on forward/reverse mobilizers; combines composite bodies; chooses
8084
base bodies and adds 6dof mobilizers for them; splits loops; adds shadow
@@ -341,7 +345,7 @@ void SpanningForest::ExtendTreesOneLevel(
341345
const LinkIndex inboard_link_index =
342346
parent_is_modeled ? joint.parent_link() : joint.child_link();
343347

344-
// Don't keep references to mobods since we're growing the vector below.
348+
// Don't keep references to Mobods since we're growing the vector below.
345349
const MobodIndex inboard_mobod_index =
346350
graph().link_to_mobod(inboard_link_index);
347351

multibody/topology/spanning_forest.h

+15-10
Original file line numberDiff line numberDiff line change
@@ -203,21 +203,26 @@ class SpanningForest {
203203
const Tree& trees(TreeIndex tree_index) const { return trees()[tree_index]; }
204204

205205
/** Groups of mobilized bodies that are connected by Weld mobilizers so have
206-
no relative degrees of freedom. Note that if we are modeling welded-together
207-
Links as single Composite Links then each composite gets only a single Mobod
208-
and hence there won't be any composite Mobods here. Use mobod_to_links() to
209-
find all the Links following a single Mobod.
210-
211-
If anything is welded to World, that Composite Mobod comes first and contains
212-
World, mobilized bodies representing Links marked "Static", and bodies (if
213-
any) welded to Static bodies or World (recursively). Those are the "anchored"
214-
mobilized bodies. The other groups represent sets of welded-together mobilized
215-
bodies. Mobods that are not welded to any other Mobods do not appear here.
206+
no relative degrees of freedom. Note that if we are combining welded-together
207+
Links into single bodies, then each Composite Link gets only a single Mobod
208+
and hence there won't be any composite Mobods here except for World which is
209+
always considered a composite Mobod even if nothing is welded to it. Use
210+
mobod_to_links() to find all the Links following a single Mobod.
211+
212+
The World Composite Mobod comes first and contains World, mobilized bodies
213+
representing Links marked "Static", and bodies (if any) welded to Static
214+
bodies or World (recursively). Those are the "anchored" mobilized bodies. The
215+
other groups represent sets of welded-together mobilized bodies. Except for
216+
World, Mobods that are not welded to any other Mobods do not appear here.
216217
217218
@see mobod_to_links() */
218219
const std::vector<std::vector<MobodIndex>>& composite_mobods() const {
219220
return data_.composite_mobods;
220221
}
222+
const std::vector<MobodIndex>& composite_mobods(
223+
CompositeMobodIndex index) const {
224+
return composite_mobods()[index];
225+
}
221226

222227
/** Returns the global ModelingOptions that were used when this model was last
223228
built with BuildForest(). You can also provide a ModelInstanceIndex to see

multibody/topology/test/link_joint_graph_test.cc

+101-14
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ const char kPrismaticType[] = "prismatic";
2020
// Arbitrary world name for testing.
2121
const char kWorldLinkName[] = "DefaultWorldLinkName";
2222

23-
// Test a straightforward serial chain of bodies connected by
24-
// revolute joints: world->body1->body2->body3->body4->body5.
25-
// We perform a number of sanity checks on the provided API.
23+
/* Test a straightforward serial chain of bodies connected by
24+
revolute joints: world->body1->body2->body3->body4->body5.
25+
We perform a number of sanity checks on the provided API. */
2626
GTEST_TEST(LinkJointGraph, SerialChain) {
2727
LinkJointGraph graph;
2828
EXPECT_EQ(graph.num_joint_types(), 3); // predefined types thus far
@@ -191,7 +191,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
191191
2 [7] =>free9 (added 6dof, free bodies are always last)
192192
193193
Composite Links: {0 7 6 8} {11 10}
194-
Composite Mobods: none
194+
Composite Mobods: [0] (just the World "composite")
195195
*/
196196

197197
// TODO(sherm1) Move to its own test suite.
@@ -250,7 +250,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
250250

251251
EXPECT_EQ(ssize(forest.mobods()), 8);
252252
EXPECT_EQ(ssize(forest.trees()), 3);
253-
EXPECT_TRUE(forest.composite_mobods().empty());
253+
EXPECT_EQ(ssize(forest.composite_mobods()), 1); // just World
254254

255255
// Now force one of the joints in the composite to be modeled (meaning it
256256
// should get its own Mobod). This should split the World composite into
@@ -266,9 +266,9 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
266266

267267
EXPECT_EQ(ssize(forest.mobods()), 9);
268268
EXPECT_EQ(ssize(forest.trees()), 3);
269-
EXPECT_EQ(ssize(forest.composite_mobods()), 1);
269+
EXPECT_EQ(ssize(forest.composite_mobods()), 2);
270270
const std::vector<MobodIndex> now_expected{MobodIndex(6), MobodIndex(7)};
271-
EXPECT_EQ(forest.composite_mobods()[0], now_expected);
271+
EXPECT_EQ(forest.composite_mobods(CompositeMobodIndex(1)), now_expected);
272272

273273
/* Build again but with the FixedBase option for model_instance, and allow
274274
joint_10_11 to be part of a composite (default behavior). Now we expect
@@ -278,7 +278,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
278278
0 [1-5] ->link1->link2->link3->link4->link5
279279
280280
Composite Links: {0 7 6 8 11 10 9}
281-
Composite Mobods: none
281+
Composite Mobods: [0] (just World)
282282
*/
283283
graph.change_joint_flags(joint_10_11_index, JointFlags::Default);
284284
graph.BuildForest(ModelingOptions::CombineCompositeLinks,
@@ -292,12 +292,13 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
292292

293293
EXPECT_EQ(ssize(forest.mobods()), 6);
294294
EXPECT_EQ(ssize(forest.trees()), 1);
295-
EXPECT_EQ(ssize(forest.composite_mobods()), 0);
295+
EXPECT_EQ(ssize(forest.composite_mobods()), 1); // just World
296296
const std::vector<LinkIndex> expected_composite_link{
297297
LinkIndex(0), LinkIndex(7), LinkIndex(6), LinkIndex(8),
298298
LinkIndex(11), LinkIndex(10), LinkIndex(9)};
299299
EXPECT_EQ(ssize(graph.composite_links()), 1);
300-
EXPECT_EQ(graph.composite_links()[0], expected_composite_link);
300+
EXPECT_EQ(graph.composite_links(CompositeLinkIndex(0)),
301+
expected_composite_link);
301302
}
302303

303304
/* This is a straightforward graph with two trees each with multiple branches.
@@ -354,11 +355,11 @@ GTEST_TEST(LinkJointGraph, MultipleBranches) {
354355
EXPECT_EQ(ssize(graph.links()), 15); // no links added
355356
EXPECT_EQ(graph.num_user_joints(), 12); // the ones added above
356357
EXPECT_EQ(ssize(graph.joints()), 14); // modeling adds two floating joints
357-
EXPECT_TRUE(graph.composite_links().empty());
358+
EXPECT_EQ(ssize(graph.composite_links()), 1); // just World
358359

359360
EXPECT_EQ(ssize(forest.trees()), 2);
360361
EXPECT_EQ(ssize(forest.mobods()), 15); // includes World
361-
EXPECT_TRUE(forest.composite_mobods().empty());
362+
EXPECT_EQ(ssize(forest.composite_mobods()), 1); // just World
362363
EXPECT_EQ(forest.num_positions(), 26); // 12 revolute, 2 x 17 quat floating
363364
EXPECT_EQ(forest.num_velocities(), 24);
364365

@@ -1037,12 +1038,12 @@ GTEST_TEST(LinkJointGraph, LoopWithComposites) {
10371038
EXPECT_EQ(ssize(graph.links()), 12); // split one, added shadow
10381039
EXPECT_EQ(ssize(graph.joints()), 11); // no change
10391040
EXPECT_EQ(ssize(graph.constraints()), 1); // welded shadow to primary
1040-
EXPECT_EQ(ssize(graph.composite_links()), 3);
1041+
EXPECT_EQ(ssize(graph.composite_links()), 4); // World + 3
10411042

10421043
EXPECT_EQ(ssize(forest.mobods()), 9);
10431044
EXPECT_EQ(ssize(forest.loop_constraints()), 1);
10441045
EXPECT_EQ(ssize(forest.trees()), 2);
1045-
EXPECT_EQ(ssize(forest.composite_mobods()), 0);
1046+
EXPECT_EQ(ssize(forest.composite_mobods()), 1); // just World
10461047

10471048
EXPECT_EQ(forest.mobods(MobodIndex(1)).follower_links(),
10481049
(std::vector<LinkIndex>{LinkIndex(1), LinkIndex(2)}));
@@ -1107,6 +1108,92 @@ GTEST_TEST(LinkJointGraph, LoopWithComposites) {
11071108
graph_copy.forest().SanityCheckForest(); // Should be empty but OK
11081109
}
11091110

1111+
/* For both composite_links and composite_mobods: the World composite must
1112+
come first (even if nothing is welded to World). This graph's first branch has
1113+
a composite that could be seen prior to the weld to World. We'll attempt
1114+
to trick it into following that path by using a massless body, requiring it
1115+
to extend the first branch to Link {2} before moving on to the next branch.
1116+
But we want to see the {0,3} composite before the {1,2} composite.
1117+
1118+
+---> {1*} ===> {2}
1119+
{0} | 0 1 {Links} & Joints
1120+
World | ===> is a weld
1121+
+===> {3} * Link 1 is massless
1122+
| 2
1123+
|
1124+
+---> {4}
1125+
3
1126+
*/
1127+
GTEST_TEST(LinkJointGraph, WorldCompositeComesFirst) {
1128+
LinkJointGraph graph;
1129+
graph.RegisterJointType(kRevoluteType, 1, 1);
1130+
const ModelInstanceIndex model_instance(5); // arbitrary
1131+
1132+
// The first Link added defines the world's name and forest instance.
1133+
graph.AddLink("world", world_model_instance());
1134+
graph.AddLink("massless_link_1", model_instance, LinkFlags::TreatAsMassless);
1135+
graph.AddLink("link2", model_instance);
1136+
graph.AddLink("link3", model_instance);
1137+
graph.AddLink("link4", model_instance);
1138+
1139+
const auto& world = graph.links(LinkIndex(0));
1140+
const auto& massless_link = graph.links(LinkIndex(1));
1141+
const auto& link2 = graph.links(LinkIndex(2));
1142+
const auto& link3 = graph.links(LinkIndex(3));
1143+
const auto& link4 = graph.links(LinkIndex(4));
1144+
1145+
graph.AddJoint("joint0", model_instance,
1146+
kRevoluteType, world.index(), massless_link.index());
1147+
graph.AddJoint("joint1", model_instance,
1148+
"weld", massless_link.index(), link2.index());
1149+
graph.AddJoint("joint2", model_instance,
1150+
"weld", world.index(), link3.index());
1151+
graph.AddJoint("joint4", model_instance,
1152+
kRevoluteType, world.index(), link4.index());
1153+
1154+
const SpanningForest& forest = graph.BuildForest(); // Default options
1155+
forest.SanityCheckForest();
1156+
graph.DumpGraph("WorldCompositeComesFirst");
1157+
forest.DumpForest("WorldCompositeComesFirst");
1158+
1159+
EXPECT_EQ(ssize(graph.links()), 5);
1160+
EXPECT_EQ(ssize(forest.mobods()), 5); // Because we're not combining.
1161+
1162+
// "Anchored" means "fixed to World" (by welds).
1163+
EXPECT_TRUE(world.is_anchored());
1164+
EXPECT_FALSE(massless_link.is_anchored());
1165+
EXPECT_FALSE(link2.is_anchored());
1166+
EXPECT_TRUE(link3.is_anchored());
1167+
EXPECT_FALSE(link4.is_anchored());
1168+
1169+
EXPECT_EQ(ssize(graph.composite_links()), 2);
1170+
EXPECT_EQ(graph.composite_links(CompositeLinkIndex(0)),
1171+
(std::vector<LinkIndex>{LinkIndex(0), LinkIndex(3)}));
1172+
EXPECT_EQ(graph.composite_links(CompositeLinkIndex(1)),
1173+
(std::vector<LinkIndex>{LinkIndex(1), LinkIndex(2)}));
1174+
1175+
EXPECT_EQ(ssize(forest.composite_mobods()), 2);
1176+
EXPECT_EQ(forest.composite_mobods(CompositeMobodIndex(0)),
1177+
(std::vector<MobodIndex>{MobodIndex(0), MobodIndex(3)}));
1178+
EXPECT_EQ(forest.composite_mobods(CompositeMobodIndex(1)),
1179+
(std::vector<MobodIndex>{MobodIndex(1), MobodIndex(2)}));
1180+
1181+
// Remodel making single Mobods for composite links.
1182+
graph.BuildForest(ModelingOptions::CombineCompositeLinks);
1183+
forest.SanityCheckForest();
1184+
forest.DumpForest("WorldCompositeComesFirst -- combining");
1185+
EXPECT_EQ(ssize(forest.mobods()), 3); // Because we're combining.
1186+
EXPECT_EQ(forest.mobods(MobodIndex(0)).follower_links(),
1187+
(std::vector<LinkIndex>{LinkIndex(0), LinkIndex(3)}));
1188+
EXPECT_EQ(forest.mobods(MobodIndex(1)).follower_links(),
1189+
(std::vector<LinkIndex>{LinkIndex(1), LinkIndex(2)}));
1190+
EXPECT_EQ(forest.mobods(MobodIndex(2)).follower_links(),
1191+
(std::vector<LinkIndex>{LinkIndex(4)}));
1192+
1193+
EXPECT_EQ(ssize(graph.composite_links()), 2); // no change expected
1194+
EXPECT_EQ(ssize(forest.composite_mobods()), 1); // just World now
1195+
}
1196+
11101197
} // namespace internal
11111198
} // namespace multibody
11121199
} // namespace drake

0 commit comments

Comments
 (0)