Skip to content

Commit b8c7707

Browse files
authored
Terminology changes LinkComposite -> WeldedLinksAssembly, optimized/unoptimized/split. (#23670)
Added joints to assemblies, unit tests, and sanity checks on assembly link ordering.
1 parent 5bcea4e commit b8c7707

15 files changed

+1160
-722
lines changed

multibody/topology/link_joint_graph.cc

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void LinkJointGraph::InvalidateForest() {
122122
DRAKE_DEMAND(ssize(data_.joint_name_to_index) == data_.num_user_joints);
123123
DRAKE_DEMAND(data_.ephemeral_link_name_to_index.empty());
124124
DRAKE_DEMAND(data_.ephemeral_joint_name_to_index.empty());
125-
DRAKE_DEMAND(data_.link_composites.empty());
125+
DRAKE_DEMAND(data_.welded_links_assemblies.empty());
126126
DRAKE_DEMAND(data_.num_user_links == ssize(data_.links));
127127
DRAKE_DEMAND(data_.num_user_joints == ssize(data_.joints));
128128
return;
@@ -143,7 +143,7 @@ void LinkJointGraph::InvalidateForest() {
143143
data_.ephemeral_link_name_to_index.clear();
144144
data_.links.erase(data_.links.begin() + data_.num_user_links,
145145
data_.links.end());
146-
DRAKE_DEMAND(ssize(links()) == data_.num_user_links);
146+
DRAKE_DEMAND(num_links() == data_.num_user_links);
147147
}
148148

149149
if (ssize(data_.joints) > num_user_joints()) {
@@ -161,7 +161,7 @@ void LinkJointGraph::InvalidateForest() {
161161
// Remove all as-modeled information from the user's graph.
162162
for (auto& link : data_.links) link.ClearModel(data_.max_user_joint_index);
163163
for (auto& joint : data_.joints) joint.ClearModel();
164-
data_.link_composites.clear();
164+
data_.welded_links_assemblies.clear();
165165
}
166166

167167
const LinkJointGraph::Link& LinkJointGraph::world_link() const {
@@ -175,7 +175,7 @@ LinkIndex LinkJointGraph::AddLink(const std::string& link_name,
175175
DRAKE_DEMAND(model_instance.is_valid());
176176

177177
// Reject use of world model instance for any Link other than World.
178-
if (ssize(links()) > 0 && model_instance == world_model_instance()) {
178+
if (num_links() > 0 && model_instance == world_model_instance()) {
179179
throw std::logic_error(fmt::format(
180180
"{}(): Model instance index {} is reserved for the World link. "
181181
" World is always predefined and is named '{}'.",
@@ -203,15 +203,15 @@ LinkIndex LinkJointGraph::AddLink(const std::string& link_name,
203203
InvalidateForest();
204204

205205
const LinkIndex link_index(num_link_indexes());
206-
const LinkOrdinal link_ordinal(ssize(links()));
206+
const LinkOrdinal link_ordinal(num_links());
207207
data_.link_index_to_ordinal.push_back(link_ordinal);
208208

209209
// provide fast name lookup
210210
data_.link_name_to_index.insert({link_name, link_index});
211211

212212
data_.links.emplace_back(
213213
Link(link_index, link_ordinal, link_name, model_instance, flags));
214-
data_.num_user_links = ssize(links());
214+
data_.num_user_links = num_links();
215215
data_.max_user_link_index = link_index;
216216

217217
Link& new_link = data_.links.back();
@@ -485,14 +485,15 @@ bool LinkJointGraph::IsJointTypeRegistered(
485485
return it != data_.joint_type_name_to_index.end();
486486
}
487487

488-
void LinkJointGraph::CreateWorldLinkComposite() {
488+
void LinkJointGraph::CreateWorldWeldedLinksAssembly() {
489489
DRAKE_DEMAND(!links().empty()); // Better be at least World!
490-
DRAKE_DEMAND(data_.link_composites.empty());
490+
DRAKE_DEMAND(data_.welded_links_assemblies.empty());
491491
Link& world_link = data_.links[LinkOrdinal(0)];
492-
DRAKE_DEMAND(!world_link.link_composite_index_.has_value());
493-
data_.link_composites.emplace_back(LinkComposite{
494-
.links = std::vector{world_link.index()}, .is_massless = false});
495-
world_link.link_composite_index_ = LinkCompositeIndex(0);
492+
DRAKE_DEMAND(!world_link.welded_links_assembly_index_.has_value());
493+
WeldedLinksAssembly& assembly = data_.welded_links_assemblies.emplace_back();
494+
assembly.links_.push_back(world_link.index());
495+
assembly.is_massless_ = false;
496+
world_link.welded_links_assembly_index_ = WeldedLinksAssemblyIndex(0);
496497
}
497498

498499
LoopConstraintIndex LinkJointGraph::AddLoopClosingWeldConstraint(
@@ -602,42 +603,57 @@ JointIndex LinkJointGraph::AddEphemeralJointToWorld(
602603
return new_joint_index;
603604
}
604605

605-
LinkCompositeIndex LinkJointGraph::AddToLinkComposite(
606-
LinkOrdinal maybe_composite_link_ordinal, LinkOrdinal new_link_ordinal) {
607-
DRAKE_ASSERT(maybe_composite_link_ordinal.is_valid() &&
606+
WeldedLinksAssemblyIndex LinkJointGraph::AddToWeldedLinksAssembly(
607+
LinkOrdinal maybe_assembly_link_ordinal, LinkOrdinal new_link_ordinal,
608+
JointOrdinal weld_joint_ordinal) {
609+
DRAKE_ASSERT(maybe_assembly_link_ordinal.is_valid() &&
608610
new_link_ordinal.is_valid());
609-
Link& maybe_composite_link = mutable_link(maybe_composite_link_ordinal);
611+
Link& maybe_assembly_link = mutable_link(maybe_assembly_link_ordinal);
610612
Link& new_link = mutable_link(new_link_ordinal);
611613
DRAKE_DEMAND(!new_link.is_world());
612614

613-
std::optional<LinkCompositeIndex> existing_composite_index =
614-
maybe_composite_link.link_composite_index_;
615-
if (!existing_composite_index.has_value()) {
616-
// We're starting a new LinkComposite. This must be the "active link"
617-
// for this Composite because we saw it first while building the Forest.
618-
existing_composite_index = maybe_composite_link.link_composite_index_ =
619-
LinkCompositeIndex(ssize(data_.link_composites));
620-
data_.link_composites.emplace_back(LinkComposite{
621-
.links = std::vector<LinkIndex>{maybe_composite_link.index()},
622-
.is_massless = maybe_composite_link.is_massless()});
615+
std::optional<WeldedLinksAssemblyIndex> existing_assembly_index =
616+
maybe_assembly_link.welded_links_assembly_index_;
617+
if (!existing_assembly_index.has_value()) {
618+
// We're starting a new WeldedLinksAssembly. This must be the "active link"
619+
// for this Assembly because we saw it first while building the Forest.
620+
existing_assembly_index = maybe_assembly_link.welded_links_assembly_index_ =
621+
WeldedLinksAssemblyIndex(ssize(data_.welded_links_assemblies));
622+
WeldedLinksAssembly& assembly =
623+
data_.welded_links_assemblies.emplace_back();
624+
assembly.links_.push_back(maybe_assembly_link.index());
625+
assembly.is_massless_ = maybe_assembly_link.is_massless();
623626
}
624627

625-
LinkComposite& existing_composite =
626-
data_.link_composites[*existing_composite_index];
627-
existing_composite.links.push_back(new_link.index());
628-
// For the composite to be massless, _all_ its links must be massless.
629-
if (!new_link.is_massless()) existing_composite.is_massless = false;
630-
new_link.link_composite_index_ = existing_composite_index;
628+
WeldedLinksAssembly& existing_assembly =
629+
data_.welded_links_assemblies[*existing_assembly_index];
630+
existing_assembly.links_.push_back(new_link.index());
631+
existing_assembly.joints_.push_back(joints(weld_joint_ordinal).index());
632+
// For the assembly to be massless, _all_ its links must be massless.
633+
if (!new_link.is_massless()) {
634+
existing_assembly.is_massless_ = false;
635+
}
636+
new_link.welded_links_assembly_index_ = existing_assembly_index;
631637

632-
return *existing_composite_index;
638+
return *existing_assembly_index;
633639
}
634640

635-
void LinkJointGraph::AddUnmodeledJointToComposite(
641+
void LinkJointGraph::NoteUnmodeledJointInWeldedLinksAssembly(
636642
JointOrdinal unmodeled_joint_ordinal,
637-
LinkCompositeIndex link_composite_index) {
643+
WeldedLinksAssemblyIndex welded_links_assembly_index) {
638644
Joint& joint = mutable_joint(unmodeled_joint_ordinal);
639-
DRAKE_DEMAND(joint.traits_index() == weld_joint_traits_index());
640-
joint.how_modeled_ = link_composite_index;
645+
DRAKE_DEMAND(joint.is_weld());
646+
joint.how_modeled_ = welded_links_assembly_index;
647+
}
648+
649+
void LinkJointGraph::AddUnmodeledJointToWeldedLinksAssembly(
650+
JointOrdinal unmodeled_joint_ordinal,
651+
WeldedLinksAssemblyIndex welded_links_assembly_index) {
652+
WeldedLinksAssembly& assembly =
653+
data_.welded_links_assemblies[welded_links_assembly_index];
654+
assembly.joints_.push_back(joints(unmodeled_joint_ordinal).index());
655+
NoteUnmodeledJointInWeldedLinksAssembly(unmodeled_joint_ordinal,
656+
welded_links_assembly_index);
641657
}
642658

643659
LinkOrdinal LinkJointGraph::AddShadowLink(LinkOrdinal primary_link_ordinal,
@@ -649,14 +665,14 @@ LinkOrdinal LinkJointGraph::AddShadowLink(LinkOrdinal primary_link_ordinal,
649665
const int shadow_num = primary_link.num_shadows() + 1;
650666
/* Name should be <primary_name>$<shadow_num> (unique within primary's model
651667
instance). In the unlikely event that a user has names like this, we'll keep
652-
prepending "_" to the body name until this one is unique. Nothing much depends
668+
prepending "_" to the link name until this one is unique. Nothing much depends
653669
on the details of this name. */
654670
std::string shadow_link_name =
655671
fmt::format("{}${}", primary_link.name(), shadow_num);
656672
while (HasLinkNamed(shadow_link_name, primary_link.model_instance()))
657673
shadow_link_name = "_" + shadow_link_name;
658674
const LinkIndex shadow_link_index(num_link_indexes());
659-
const LinkOrdinal shadow_link_ordinal(ssize(links()));
675+
const LinkOrdinal shadow_link_ordinal(num_links());
660676
DRAKE_DEMAND(shadow_link_ordinal >= num_user_links()); // A sanity check.
661677
data_.link_index_to_ordinal.push_back(shadow_link_ordinal);
662678
data_.ephemeral_link_name_to_index.insert(
@@ -667,13 +683,18 @@ LinkOrdinal LinkJointGraph::AddShadowLink(LinkOrdinal primary_link_ordinal,
667683
/* Caution: primary_link reference is invalid now -- don't use it! */
668684
Link& shadow_link = data_.links.back();
669685
shadow_link.primary_link_ = primary_link_index;
670-
const Joint& shadow_joint = joints(shadow_joint_ordinal);
686+
Joint& shadow_joint = mutable_joint(shadow_joint_ordinal);
671687
if (shadow_is_parent) {
672688
shadow_link.add_joint_as_parent(shadow_joint.index());
689+
shadow_joint.set_effective_parent_link(shadow_link_index);
673690
} else {
674691
shadow_link.add_joint_as_child(shadow_joint.index());
692+
shadow_joint.set_effective_child_link(shadow_link_index);
675693
}
676-
mutable_link(primary_link_ordinal).shadow_links_.push_back(shadow_link_index);
694+
695+
Link& mutable_primary_link = mutable_link(primary_link_ordinal);
696+
mutable_primary_link.shadow_links_.push_back(shadow_link_index);
697+
mutable_primary_link.NoteRetargetedJoint(shadow_joint.index());
677698

678699
return shadow_link_ordinal;
679700
}
@@ -720,7 +741,7 @@ std::vector<LinkIndex> LinkJointGraph::FindPathFromWorld(
720741
std::vector<LinkIndex> path(mobod->level() + 1);
721742
while (mobod->inboard().is_valid()) {
722743
const Link& link = links(mobod->link_ordinal());
723-
path[mobod->level()] = link.index(); // Active Link if composite.
744+
path[mobod->level()] = link.index(); // Active Link if optimized assembly.
724745
mobod = &forest().mobods(mobod->inboard());
725746
}
726747
DRAKE_DEMAND(mobod->is_world());
@@ -745,25 +766,25 @@ std::vector<LinkIndex> LinkJointGraph::FindSubtreeLinks(
745766
return forest().FindSubtreeLinks(root_mobod_index);
746767
}
747768

748-
// Our link_composites collection doesn't include lone Links that aren't welded
749-
// to anything. The return from this function must include every Link, with
750-
// the World link in the first set (even if nothing is welded to it).
769+
// Our welded_links_assemblies collection doesn't include lone Links that aren't
770+
// welded to anything. The return from this function must include every Link,
771+
// with the World link in the first set (even if nothing is welded to it).
751772
std::vector<std::set<LinkIndex>> LinkJointGraph::GetSubgraphsOfWeldedLinks()
752773
const {
753774
ThrowIfForestNotBuiltYet(__func__);
754775

755776
std::vector<std::set<LinkIndex>> subgraphs;
756777

757-
// First, collect all the precomputed Link Composites. World is always
778+
// First, collect all the precomputed WeldedLinksAssemblies. World is always
758779
// the first one, even if nothing is welded to it.
759-
for (const LinkComposite& composite : link_composites()) {
780+
for (const WeldedLinksAssembly& assembly : welded_links_assemblies()) {
760781
subgraphs.emplace_back(
761-
std::set<LinkIndex>(composite.links.cbegin(), composite.links.cend()));
782+
std::set<LinkIndex>(assembly.links_.cbegin(), assembly.links_.cend()));
762783
}
763784

764-
// Finally, make one-Link subgraphs for Links that aren't in any composite.
785+
// Finally, make one-Link subgraphs for Links that aren't in any assembly.
765786
for (const Link& link : links()) {
766-
if (link.composite().has_value()) continue;
787+
if (link.welded_links_assembly().has_value()) continue;
767788
subgraphs.emplace_back(std::set<LinkIndex>{link.index()});
768789
}
769790

@@ -806,22 +827,23 @@ std::vector<std::set<LinkIndex>> LinkJointGraph::CalcSubgraphsOfWeldedLinks()
806827
return subgraphs;
807828
}
808829

809-
// If the Link isn't part of a LinkComposite just return the Link. Otherwise,
810-
// return all the Links in its LinkComposite.
830+
// If the Link isn't part of a WeldedLinksAssembly just return the Link.
831+
// Otherwise, return all the Links in its WeldedLinksAssembly.
811832
std::set<LinkIndex> LinkJointGraph::GetLinksWeldedTo(
812833
LinkIndex link_index) const {
813834
ThrowIfForestNotBuiltYet(__func__);
814835
DRAKE_DEMAND(link_index.is_valid());
815836
DRAKE_THROW_UNLESS(has_link(link_index));
816837
const Link& link = link_by_index(link_index);
817-
const std::optional<LinkCompositeIndex> composite_index = link.composite();
818-
if (!composite_index.has_value()) return std::set<LinkIndex>{link_index};
838+
const std::optional<WeldedLinksAssemblyIndex> assembly_index =
839+
link.welded_links_assembly();
840+
if (!assembly_index.has_value()) return std::set<LinkIndex>{link_index};
819841
const std::vector<LinkIndex>& welded_links =
820-
link_composites(*composite_index).links;
842+
welded_links_assemblies(*assembly_index).links();
821843
return std::set<LinkIndex>(welded_links.cbegin(), welded_links.cend());
822844
}
823845

824-
// Without a Forest we don't have LinkComposites available so recursively
846+
// Without a Forest we don't have WeldedLinksAssemblies available so recursively
825847
// chase Weld joints instead.
826848
std::set<LinkIndex> LinkJointGraph::CalcLinksWeldedTo(
827849
LinkIndex link_index) const {
@@ -930,7 +952,8 @@ void LinkJointGraph::Link::ClearModel(JointIndex max_user_joint_index) {
930952
mobod_ = {};
931953
joint_ = {};
932954
shadow_links_.clear();
933-
link_composite_index_ = {};
955+
joints_moved_to_shadow_links_.clear();
956+
welded_links_assembly_index_ = {};
934957
}
935958

936959
LinkJointGraph::Joint::Joint(JointIndex index, JointOrdinal ordinal,
@@ -953,6 +976,8 @@ LinkJointGraph::Joint::Joint(JointIndex index, JointOrdinal ordinal,
953976
child_link_index_.is_valid());
954977
DRAKE_DEMAND(parent_link_index_ != child_link_index_);
955978
DRAKE_DEMAND(ordinal_ <= static_cast<int>(index_));
979+
effective_parent_link_index_ = parent_link_index_;
980+
effective_child_link_index_ = child_link_index_;
956981
}
957982

958983
} // namespace internal

0 commit comments

Comments
 (0)