Skip to content

Fix return policies for some member functions#422

Merged
azeey merged 2 commits intogazebosim:ign-math6from
azeey:pybind11_return_policy
May 6, 2022
Merged

Fix return policies for some member functions#422
azeey merged 2 commits intogazebosim:ign-math6from
azeey:pybind11_return_policy

Conversation

@azeey
Copy link
Copy Markdown
Contributor

@azeey azeey commented May 5, 2022

🦟 Bug fix

Summary

We encountered some issues with the python bindings while working on gazebosim/gz-mujoco#5 where calling link.inertia().mass_matrix() results in a newly constructed MassMatrix3d object instead of returning the object inside the link's Inertiald object.

To fix this, generally, member functions that return a const ref should either use the default return policy (which makes a copy) or py::return_value_policy::reference_internal. Since we're dealing with small objects, making a copy seems the simplest and safest approach.

For member functions that return a mutable reference, we should use almost always use py::return_value_policy::reference_internal. In such instances, if there are const and non-const overloads, the non-const overload should be picked for binding to python.

Checklist

  • Signed all commits for DCO
  • Added tests
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • Consider updating Python bindings (if the library has them)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • While waiting for a review on your PR, please help review another open pull request to support the maintainers

Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining Signed-off-by messages.

Member functions that return a const ref should either use the default
return policy (which makes a copy) or
`py::return_value_policy::reference_internal`. Since we're dealing with
small objects, making a copy seems the simplest and safest approach.

For member functions that return a mutable reference, should use almost
always use `py::return_value_policy::reference_internal`.

Signed-off-by: Addisu Z. Taddese <addisu@openrobotics.org>
@azeey azeey requested a review from ahcorde May 5, 2022 20:33
@azeey azeey requested a review from scpeters as a code owner May 5, 2022 20:33
@github-actions github-actions bot added Gazebo 1️1️ Dependency of Gazebo classic version 11 🏯 fortress Ignition Fortress 🏰 citadel Ignition Citadel labels May 5, 2022
@codecov
Copy link
Copy Markdown

codecov bot commented May 5, 2022

Codecov Report

Merging #422 (0340abf) into ign-math6 (c52a3fa) will not change coverage.
The diff coverage is n/a.

❗ Current head 0340abf differs from pull request most recent head d26ed19. Consider uploading reports for the commit d26ed19 to get more accurate results

@@            Coverage Diff             @@
##           ign-math6     #422   +/-   ##
==========================================
  Coverage      99.83%   99.83%           
==========================================
  Files             41       41           
  Lines           4138     4138           
==========================================
  Hits            4131     4131           
  Misses             7        7           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c52a3fa...d26ed19. Read the comment docs.

@scpeters
Copy link
Copy Markdown
Member

scpeters commented May 6, 2022

are there tests we are missing that would confirm the intended behavior? we don't need to add it now, but I'm wondering what those tests would look like or if some of the classes already have them

@azeey
Copy link
Copy Markdown
Contributor Author

azeey commented May 6, 2022

are there tests we are missing that would confirm the intended behavior? we don't need to add it now, but I'm wondering what those tests would look like or if some of the classes already have them

I don't know how to test this behavior. It has to do with object lifetime and the python garbage collector, so I'm not sure if there's an easy way to test it.

@scpeters
Copy link
Copy Markdown
Member

scpeters commented May 6, 2022

are there tests we are missing that would confirm the intended behavior? we don't need to add it now, but I'm wondering what those tests would look like or if some of the classes already have them

I don't know how to test this behavior. It has to do with object lifetime and the python garbage collector, so I'm not sure if there's an easy way to test it.

ok, I don't fully understand the issue so I won't approve this, but I think it's fine to merge since it has one approval

@azeey
Copy link
Copy Markdown
Contributor Author

azeey commented May 6, 2022

are there tests we are missing that would confirm the intended behavior? we don't need to add it now, but I'm wondering what those tests would look like or if some of the classes already have them

I don't know how to test this behavior. It has to do with object lifetime and the python garbage collector, so I'm not sure if there's an easy way to test it.

ok, I don't fully understand the issue so I won't approve this, but I think it's fine to merge since it has one approval

I can demonstrate the issue from sdformat

import sdformat as sdf
from ignition.math import Inertiald, MassMatrix3d, Vector3d, Pose3d

link = sdf.Link()
mass = 10.0
inertial = Inertiald(MassMatrix3d(mass, Vector3d(2, 3, 4), Vector3d.ZERO),
                     Pose3d())
link.set_inertial(inertial)
print("mass from inertial:", inertial.mass_matrix().mass())
print("mass from link:", link.inertial().mass_matrix().mass())

will print

mass from inertial: 10.0
mass from link: 0.0

but we expect both to be 10. This PR fixes that.

Edit: You have forward port this to main to run that script since the SDFormat python bindings are in main.

@azeey azeey merged commit b330afe into gazebosim:ign-math6 May 6, 2022
@azeey azeey deleted the pybind11_return_policy branch May 6, 2022 20:51
scpeters added a commit that referenced this pull request May 10, 2022
Signed-off-by: Steve Peters <scpeters@openrobotics.org>
@scpeters
Copy link
Copy Markdown
Member

ok, I think I see what's happening. I still think it would be best to have tests that confirm what we think the API should be doing. Here is a branch where I've added some test expectations to Inertial_TEST.py to study the behavior change introduced by this PR:

https://github.com/gazebosim/gz-math/compare/scpeters/python_inertial_tests?expand=1

  • 29cd3fe: behavior before this PR, calling inertial.mass_matrix().set_mass() would change the mass in the object stored by the inertial since mass_matrix() was returning a reference (tests pass)
  • 997b2c3: merge with ign-math6 including the changes from this PR (test fails since mass_matrix() no longer returns a reference
  • d793089: change test expectation so it doesn't expect parent object to be changed (tests pass)

this more closely matches the c++ API; hopefully it is pythonic enough

1 similar comment
@scpeters
Copy link
Copy Markdown
Member

ok, I think I see what's happening. I still think it would be best to have tests that confirm what we think the API should be doing. Here is a branch where I've added some test expectations to Inertial_TEST.py to study the behavior change introduced by this PR:

https://github.com/gazebosim/gz-math/compare/scpeters/python_inertial_tests?expand=1

  • 29cd3fe: behavior before this PR, calling inertial.mass_matrix().set_mass() would change the mass in the object stored by the inertial since mass_matrix() was returning a reference (tests pass)
  • 997b2c3: merge with ign-math6 including the changes from this PR (test fails since mass_matrix() no longer returns a reference
  • d793089: change test expectation so it doesn't expect parent object to be changed (tests pass)

this more closely matches the c++ API; hopefully it is pythonic enough

@azeey
Copy link
Copy Markdown
Contributor Author

azeey commented May 11, 2022

The C++ member function Inertial::MassMatrix returns a const ref so mutation through the ref is not the intended API. As stated in the pybind11 documentation, pybind11 casts away constness in return values, so you don't get an error when doing inertial.mass_matrix().set_mass(), but I'm not sure if that's a well defined behavior. And since the intended API in C++ is to use Inerital::SetMassMatrix for mutating the mass matrix, I would propose we match that API in python.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🏰 citadel Ignition Citadel 🏯 fortress Ignition Fortress Gazebo 1️1️ Dependency of Gazebo classic version 11

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants