Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[multibody] Unambiguously identify ephemeral elements #22712

Merged
merged 1 commit into from
Mar 6, 2025

Conversation

sherm1
Copy link
Member

@sherm1 sherm1 commented Mar 6, 2025

Background

Users populate a MultibodyPlant with "elements" (Bodies, Joints, Frames, etc.), with base class MultibodyElement that holds common information like name, index, and model instance. During Finalize(), additional "ephemeral" elements are added internally to facilitate later computation. Currently we add ephemeral Joints to World for anything that doesn't have one already. We are adding ephemeral Frames for better performance in #22649, and we will add ephemeral Constraints for closing topological loops.

Currently there is no way to check whether an element in an existing plant is user-defined or ephemeral. This causes trouble in applications that involve rummaging through existing plants to construct a new plant. If the ephemeral elements are copied over from source plants to the destination plant, finalizing the destination plant will add more ephemeral elements causing duplication, name collisions, and unnecessary computation. For example, in Anzu the multibody_plant_subgraph utility has this problem.

Proposal

This PR introduces the public API element.is_ephemeral(). This can be used when rummaging through an already-finalized plant to skip over the throw-away ephemeral elements that may be there. Internally we already had AddEphemeralJoint(), now we add AddEphemeralFrame(), and both methods set the is_ephemeral bit. (BodyNodes and Mobilizers are always marked ephemeral though those are internal.) Cloning of possibly-ephemeral elements copies the is_ephemeral bit. Unit tests ensure that the known ephemeral elements are still ephemeral after cloning.

Next steps

This PR is a yak shave for the multibody performance epic #18442, so that #22649 (better F and M frames) can land. Once the is_ephemeral() API is available, I will fix Anzu's multibody_plant_subgraph utility to use it to avoid copying ephemeral elements. At that point it will be safe to re-submit #22649 which will add ephemeral frames that multibody_plant_subgraph will then ignore.


This change is Reviewable

@sherm1 sherm1 added priority: high release notes: feature This pull request contains a new feature labels Mar 6, 2025
Copy link
Member Author

@sherm1 sherm1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+@amcastro-tri for feature review please (per f2f discussion)

Reviewable status: LGTM missing from assignee amcastro-tri, needs platform reviewer assigned, needs at least two assigned reviewers

@jwnimmer-tri jwnimmer-tri self-assigned this Mar 6, 2025
Copy link
Collaborator

@jwnimmer-tri jwnimmer-tri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform pass done early, to help move this along.

BTW it's sad that the clone API isn't defined down in MultibodyElement, rather instead only in Frame & Joint & etc. If it were in the base class, we could have encapsulated the "is ephemeral" propagation for much better safety.

Reviewed 13 of 13 files at r1, all commit messages.
Reviewable status: 1 unresolved discussion, LGTM missing from assignees jwnimmer-tri(platform),amcastro-tri


multibody/tree/multibody_tree.h line 210 at r1 (raw file):

  // Same as above but sets the `is_ephemeral` bit.
  template <template <typename Scalar> class FrameType>
  const FrameType<T>& AddEphemeralFrame(std::unique_ptr<FrameType<T>> frame);

nit Since adding ephemeral frames is necessarily an internal-use-only operation, do we actually need the template sugar to return the subclass FrameType? In other words, could the function be like const Frame<T>& AddEphemeralFrame(std::unique_ptr<Frame<T>> frame)?

I ask because these subclass-infused sugar functions clutter up the headers a lot, bloat compile times, etc.

This one isn't so bad and maybe we should keep it, but I can't see any reason why we want the "automatic make_unique" overload below with the perfect forwarding. Surely drake developers can type out make_unique and not bat an eye?

@sherm1 sherm1 force-pushed the ephemeral_elements_note branch from bfc82e9 to 753daa3 Compare March 6, 2025 20:07
Copy link
Member Author

@sherm1 sherm1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! I spent some time yesterday looking at reworking element cloning. There is an absurd amount of duplication and potential errors there -- for example, each concrete joint type has to do its own copying of the Joint base class data members (damping, limits, etc.). However I decided it was too much to bite off in this narrow PR, and isn't causing any problems currently (at least since I fixed the missing model_instance copying). But it should be cleaned up. I'll file an issue and self-assign.

Reviewable status: LGTM missing from assignees jwnimmer-tri(platform),amcastro-tri


multibody/tree/multibody_tree.h line 210 at r1 (raw file):

Previously, jwnimmer-tri (Jeremy Nimmer) wrote…

nit Since adding ephemeral frames is necessarily an internal-use-only operation, do we actually need the template sugar to return the subclass FrameType? In other words, could the function be like const Frame<T>& AddEphemeralFrame(std::unique_ptr<Frame<T>> frame)?

I ask because these subclass-infused sugar functions clutter up the headers a lot, bloat compile times, etc.

This one isn't so bad and maybe we should keep it, but I can't see any reason why we want the "automatic make_unique" overload below with the perfect forwarding. Surely drake developers can type out make_unique and not bat an eye?

Done - make_unique is sufficient

Copy link
Collaborator

@jwnimmer-tri jwnimmer-tri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I peeked down the rabbit hole a little bit before deciding it wasn't a defect / worth doing here.

Reviewed 3 of 3 files at r2, all commit messages.
Reviewable status: LGTM missing from assignees jwnimmer-tri(platform),amcastro-tri

Copy link
Contributor

@amcastro-tri amcastro-tri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree also.
But or the sope of this PR :lgtm: , with only one question below

Reviewed 10 of 13 files at r1, 3 of 3 files at r2, all commit messages.
Reviewable status: 1 unresolved discussion, LGTM missing from assignee jwnimmer-tri(platform)


multibody/tree/fixed_offset_frame.cc line 58 at r2 (raw file):

  auto new_frame = std::make_unique<FixedOffsetFrame<ToScalar>>(
      this->name(), parent_frame_clone, X_PF_, this->model_instance());
  new_frame->set_is_ephemeral(this->is_ephemeral());

do we need to do this also for free floating joints? and welds? (for when the default joint to the world is weld)

Copy link
Collaborator

@jwnimmer-tri jwnimmer-tri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm: platform

Reviewable status: 2 unresolved discussions


multibody/tree/multibody_element.h line 32 at r2 (raw file):

/// the user-specified model. These are called "ephemeral" elements and can
/// be identified using the `is_ephemeral()` function here. Examples include
///   - free joints added to connect lone bodies or free-floating trees

(Moving the thread here -- Ale's note was posted in a weird place.)

Does this PR actually do this? I thought I remembered seeing it, but I can't find it now.

In any case, I don't see a full-plant (or at least full-tree) acceptance test for it.

Copy link
Member Author

@sherm1 sherm1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 2 unresolved discussions


multibody/tree/fixed_offset_frame.cc line 58 at r2 (raw file):

Previously, amcastro-tri (Alejandro Castro) wrote…

do we need to do this also for free floating joints? and welds? (for when the default joint to the world is weld)

Yes, definitely! That's already taken care of in Finalize() here. There are AddEphemeralJoint() calls for both floating joints and weld (already in master). This PR just makes AddEphemeralJoint() set the new is_ephemeral bit in MultibodyElement.


multibody/tree/multibody_element.h line 32 at r2 (raw file):

Previously, jwnimmer-tri (Jeremy Nimmer) wrote…

(Moving the thread here -- Ale's note was posted in a weird place.)

Does this PR actually do this? I thought I remembered seeing it, but I can't find it now.

In any case, I don't see a full-plant (or at least full-tree) acceptance test for it.

Yes. That's done in Finalize() here (already in master) where we call AddEphemeralJoint(). (And that sets the bit as of this PR)

If you look at the changes in this PR to multibody_plant_test.cc you'll see checks that added-during-finalize QuaternionFloating, RpyFloating, and Weld are each marked ephemeral.

Copy link
Member Author

@sherm1 sherm1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @jwnimmer-tri and @amcastro-tri -- comments addressed, PTAL.

Reviewable status: 2 unresolved discussions

Copy link
Collaborator

@jwnimmer-tri jwnimmer-tri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 1 unresolved discussion


multibody/tree/multibody_element.h line 32 at r2 (raw file):

Previously, sherm1 (Michael Sherman) wrote…

Yes. That's done in Finalize() here (already in master) where we call AddEphemeralJoint(). (And that sets the bit as of this PR)

If you look at the changes in this PR to multibody_plant_test.cc you'll see checks that added-during-finalize QuaternionFloating, RpyFloating, and Weld are each marked ephemeral.

Aha! Thanks, I knew I saw it somewhere.

My problem just now was that for some reason I expected plant code to be after tree code in the review matrix, so when I saw it go straight from tree code to pydrake code, I assumed I'd found everything. PEBKAC.

Copy link
Member Author

@sherm1 sherm1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dismissed @amcastro-tri from a discussion.
Reviewable status: :shipit: complete! all discussions resolved, LGTM from assignees jwnimmer-tri(platform),amcastro-tri

@sherm1 sherm1 merged commit 77148fa into RobotLocomotion:master Mar 6, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: high release notes: feature This pull request contains a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants