Drake: `Unable to cast Python instance to C++ type`

Created on 30 Apr 2020  路  23Comments  路  Source: RobotLocomotion/drake

This error message has been popping up in folks using mathematicalprogram from pydrake. I now have at least one minimal reproduction:

The following code

from pydrake.all import MathematicalProgram, Solve

prog = MathematicalProgram()
x = prog.NewContinuousVariables(1, 'x')

def constraint(x):
    return x[0]  # <== was return x

constraint_binding = prog.AddConstraint(
    constraint, lb=[0.], ub=[2.], vars=x)
result = Solve(prog)

prints out RuntimeError: Unable to cast Python instance to C++ type (compile in debug mode for details)

The only difference between this an the working version from our unit test is that I've returned x[0] from the constraint instead of x

pydrake python medium manipulation cleanup user assistance

All 23 comments

Ah, this is due to the fact that it's a scalar, not an array, and pybind11 chokes on it.
I would prefer that we always force people to return arrays, and not try to promote.

That being said, I will see if there is a way for a more user-friendly error.

More minimal reproduction (using .Eval())

constraint_binding.evaluator().Eval([AutoDiffXd(0.)])

Note that it is not triggered when passing an array of dtype=float.

From PR:

Difference in error modes in Release, before and after C++ binding changes:
https://github.com/EricCousineau-TRI/drake/commit/6e2bd17#diff-6aab4e726ece0a9e09f9e29c4cdee3cc

Students are still seeing this. I will try to come up with more test cases.

My guess is that's because the current nightly-release does not contain the fix?
https://github.com/RobotLocomotion/drake/commits/nightly-release (26c38c92d8 as of time of writing)
https://github.com/RobotLocomotion/drake/commits/master (fc777015)

Are the students using the Docker images (via Binder) and/or binary releases?

Both used the binaries. It's true that one might have used the nightlies, but the other was using colab, which fetches the continuous build output. I'll try to debug today.

For colab, perhaps they still had a kernel provisioned with an older build?

FYI @RussTedrake It looks like you may not always get the latest nightly-release when running Binder:
https://github.com/RobotLocomotion/drake/issues/13209

With pydrake compiled in debug mode, this results in:

russt@Puget-179850-01:~/Downloads$ python3 kneed_compass_gait.py 
[2020-05-04 13:33:50.428] [console] [warning] WARNING: Skipping transmission since it's attached to a fixed joint "stance_knee_joint".
[2020-05-04 13:33:50.428] [console] [warning] WARNING: Skipping transmission since it's attached to a fixed joint "stance_knee_joint".
Traceback (most recent call last):
  File "kneed_compass_gait.py", line 316, in <module>
    infeasible = GetInfeasibleConstraints(prog, result) #ERROR OCCURS HERE
RuntimeError: Unable to cast Python instance of type <class 'numpy.ndarray'> to C++ type 'Eigen::Matrix<double, -1, 1, 0, -1, 1>'

That seems very much like the same problem. Can you post which version of Drake you're using? (e.g. cat /opt/drake/share/doc/drake/VERSION.TXT for install?)

This was on master on my (other) machine installed via the bazel install rule, and there is no VERSION.TXT. Last commit is Mon May 4... models: Move jaco_description...

Sweet! I'm able to reproduce it in this Drake-branched commit:
https://github.com/RobotLocomotion/drake/compare/master...EricCousineau-TRI:issue-13181-repro-2

Digging in...

Looks like it's due to something in reset_velocity_heelstrike: 10a2a6691

Issue is because the constraint always returns AutoDiffXd, but GetInfeasibleConstraints whats to evaluate it with dtype=float: 8663ed138

Min repro: acae9e567

from pydrake.all import MathematicalProgram, Solve, AutoDiffXd

prog = MathematicalProgram()
x = prog.NewContinuousVariables(1, 'x')

def constraint(x):
    return [AutoDiffXd(0.)]

constraint_binding = prog.AddConstraint(
    constraint, lb=[0.], ub=[0.], vars=x)
# Good
constraint_binding.evaluator().Eval([AutoDiffXd(0.)])
# BAD
constraint_binding.evaluator().Eval([0.])

Thinking on this part, it may be hard to solve this generally in pybind11. However, there's prolly a way to do something like https://github.com/RobotLocomotion/drake/pull/13183. Will try that out.

Btw -- it now finally makes sense why people would see the error in the GetInfeasibleConstraints call, but not during Solve. Solve is calling with AutoDiffXd, but GetInfeasibleConstraints is calling with double/float.

Just found another tricky example where this was happening because the constraint was using a MutlibodyPlant_[AutoDiffXd] to compute some values, so was returning AutoDiffXd always... even when float was passed in.

If i ever see this again, the best recipe for debugging was to add these lines before the call to Solve:

for c in prog.generic_constraints():
    xs = [0]*len(c.variables())
    print(c)
    c.evaluator().Eval(xs)

which cycles through all of the constraints and evaluates them with float. It immediately pointed to the culprit, even in a complicated MathematicalProgram setup.

Hm... Is it worth adding a simple Python-centric CheckEvaluators(mp, x=None), and perhaps including that in a tutorial?

I like it. That could help a lot. Would you cycle through only generic costs and constraints, or just go ahead and do all of them? And presumably we'd call them with both float and AutoDiffXd?

If we can catch the failure and only print the culprit, that would be a very satisfying result.

Could Solve do more check(s) internally as its first action, instead of making the user opt-in to it? The more immediate and automatic we can make it, the better. If we can extend #13234 to print the constraint name upon an error (see also #13091) is that even better?

Hm... Another thought is to do it the moment custom-evaluators are added in Python, e.g. on PyFunctionCost / PyFunctionConstraint, they are immediately evaluated and will immediately throw if there is an error.
We can add a simple flag to enable/disable this functionality for a given MathematicalProgram in Python only (e.g. set_check_user_evaluators_on_add, or just add an argument for those specific evaluators).

If we can extend #13234 to print the constraint name upon an error (see also #13091) is that even better?

Ah, true. If we default-name the evaluator to use the Python function's name, then that should make it fairly obvious too.

I've been disinclined to do any evaluation on Add, no matter the language. It seems like a big step function vs the current behavior, and I would not be surprised if a lot of user code failed if we tried. And since the important thing is more-info-by-default without the user having to opt-in, a bool again does not meet the need.

I still think the best time to vet the costs and constraints is immediately as Solve begins. That's when we _know_ that the user thinks they all must be valid, but we've not yet started feed garbage into the backend solver.

Split check-eval discussion into #13312

Was this page helpful?
0 / 5 - 0 ratings