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

pydrake: math overloads provide inconsistent user experience #15038

Closed
RussTedrake opened this issue May 12, 2021 · 7 comments · Fixed by #15052
Closed

pydrake: math overloads provide inconsistent user experience #15038

RussTedrake opened this issue May 12, 2021 · 7 comments · Fixed by #15052

Comments

@RussTedrake
Copy link
Contributor

Consider the following example:

from pydrake.all import AutoDiffXd, Variable

x = 1
y = AutoDiffXd(1, [1, 0])
z = Variable('z')

Here's a simple basic use case, which does work fine.

import pydrake.math

print(pydrake.math.sin(x))
print(pydrake.math.sin(y))
print(pydrake.math.sin(z))

returns

0.8414709848078965
AD{0.8414709848078965, nderiv=2}
sin(z)

But there are some nearby patterns that lay a trap for users:

import numpy as np

print(np.sin(x))
print(np.sin(y))
print(np.sin(z))

gives

0.8414709848078965
AD{0.8414709848078965, nderiv=2}
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
AttributeError: 'pydrake.symbolic.Variable' object has no attribute 'sin'

The above exception was the direct cause of the following exception:

TypeError                                 Traceback (most recent call last)
<ipython-input-206-4a0ca175cc23> in <module>
      1 print(np.sin(x))
      2 print(np.sin(y))
----> 3 print(np.sin(z))

TypeError: loop of ufunc does not support argument 0 of type pydrake.symbolic.Variable which has no callable sin method

Q: Can we teach Expression to understand the numpy version?

Here is another one:

from pydrake.all import sin

print(sin(x))
print(sin(y))
print(sin(z))

gives

0.8414709848078965
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-207-74707ae6920c> in <module>
      1 print(sin(x))
----> 2 print(sin(y))
      3 print(sin(z))

TypeError: sin(): incompatible function arguments. The following argument types are supported:
    1. (arg0: pydrake.symbolic.Expression) -> pydrake.symbolic.Expression

Invoked with: <AutoDiffXd 1.0 nderiv=2>

Q: Can we move the math import in all.py below the symbolic import, so the symbolic version doesn't clobber the more general one?
(I know that we're in the land of perhaps ill-advised pydrake.allness, but I believe that should be better for non-expert users).

cc @EricCousineau-TRI . @soonho-tri -- might you be willing to take a look?

@soonho-tri
Copy link
Member

Q: Can we teach Expression to understand the numpy version?

it's a matter of adding sin method (and other corresponding methods such as cos) to the Expression class (in Python). When it's there, numpy will call Expression.sin(). I'll open a PR.

@soonho-tri
Copy link
Member

soonho-tri commented May 12, 2021

@EricCousineau-TRI , what is the best way to add a new method foo to a class created A via PyBind? Is it OK to use setattr?

def foo(self):
    print('hello world!')
setattr(A, 'foo', foo)

Update: never mind. I think it's better to add it from C++.

@EricCousineau-TRI
Copy link
Contributor

FWIW No need to use setattr; you can simply do A.foo = foo ;)

EricCousineau-TRI added a commit to soonho-tri/drake that referenced this issue May 15, 2021
soonho-tri added a commit that referenced this issue May 15, 2021
* Enable numpy math functions to work with symbolic variables.

#15038

Co-authored-by: Soonho Kong <soonho.kong@tri.global>
rpoyner-tri pushed a commit to rpoyner-tri/drake that referenced this issue May 16, 2021
…comotion#15039)

* Enable numpy math functions to work with symbolic variables.

RobotLocomotion#15038

Co-authored-by: Soonho Kong <soonho.kong@tri.global>
@soonho-tri
Copy link
Member

@EricCousineau-TRI , we still have a remaining issue here. The following code gives TypeError:

from pydrake.all import AutoDiffXd, Variable, sin

x = 1
y = AutoDiffXd(1, [1, 0])
z = Variable('z')

print(sin(x))
print(sin(y))
print(sin(z))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-207-74707ae6920c> in <module>
       print(sin(x))
---->  print(sin(y))
       print(sin(z))

TypeError: sin(): incompatible function arguments. The following argument types are supported:
    1. (arg0: pydrake.symbolic.Expression) -> pydrake.symbolic.Expression

Invoked with: <AutoDiffXd 1.0 nderiv=2>

As @RussTedrake wrote above, one solution is to update all.py:

Q: Can we move the math import in all.py below the symbolic import, so the symbolic version doesn't clobber the more general one?
(I know that we're in the land of perhaps ill-advised pydrake.allness, but I believe that should be better for non-expert users).

To be specific, here is a patch:

modified   bindings/pydrake/all.py
@@ -30,10 +30,10 @@ from . import getDrakePath
 from .autodiffutils import *
 from .forwarddiff import *
 from .lcm import *
-from .math import *
 from .perception import *
 from .polynomial import *
 from .symbolic import *
+from .math import *
 from .trajectories import *

What do you think about this approach?

@jwnimmer-tri
Copy link
Collaborator

I am not a huge fan of playing games with import order. If we have multiple competing definitions of a name and we need a specific one of those definitions to win, then we should explicitly disavow the unwanted implementations, i.e., we should not even attempt to import the unwanted implementations.

In fact, it should be a test failure if any of the "all.py" files ever overwrites a previously-imported name. When there are multiple definitions in play, we need to ask the drake developer to choose which one is supposed to win, instead of leaving it to alphabetical chance.

@RussTedrake
Copy link
Contributor Author

I'm not sure how hard that is to achieve. But the pydrake.math variants are the ones that should be imported in all.py. I'm all for doing things the right way, but perhaps we can land the small fix immediately and open an issue to track the double import defect?

@EricCousineau-TRI
Copy link
Contributor

I'm onboard with polishing later, but #15052 looks like a great intermediate solution!

rpoyner-tri added a commit that referenced this issue May 17, 2021
* DNM Disable CI

* notes: clean up the first few sections

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip: first pass done

* Factor out to h2 and sort the Build Dependencies

* Remove non-notable changes (refactorings, docs)

* Move APT site to announcements

* Punt hydroelastic viz change into dynamics section

Probably all of the hydroelatic changes should be culled, but having
them all in once place is a start.

* Prioritize and reword the breaking changes section

* Note AVX breaking change

* Fix some pydrake typos

Remove non-notable (unit test) change

* quadrotor example: add getter for geometry frame (#15046)

* quadrotor example: add getter for geometry frame

* Enable numpy math functions to work with symbolic variables. (#15039)

* Enable numpy math functions to work with symbolic variables.

#15038

Co-authored-by: Soonho Kong <soonho.kong@tri.global>

* Remove deprecated code 2021-04-21 (#15050)

Missed previously, perhaps owing to idiosyncratic date.

* drake::multibody::MultiBodyPlant::CalcCenterOfMassPosition
  * all overloads

* wip

* wip

* integrate new commits from merge of master

* Merge remote-tracking branch 'upstream/master' into release_notes-v0.30.0

* Fix CaMel case typo

* wip

* set date

* Revert "DNM Disable CI"

This reverts commit 608e8ec.

* wip
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants