symforce-org / symforce

Fast symbolic computation, code generation, and nonlinear optimization for robotics
https://symforce.org
Apache License 2.0
1.41k stars 145 forks source link

Better errors from wrong factor results #195

Closed aaron-skydio closed 2 years ago

aaron-skydio commented 2 years ago

This is a common failure (per our search traffic :) ), hopefully these error messages are clearer. Examples:

Python 3.8.0 (default, Nov  6 2019, 21:49:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import symforce.symbolic as sf

In [2]: from symforce import codegen

In [3]: def foo(x: sf.Scalar) -> sf.Scalar:
   ...:     return x
   ...:

In [4]: codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()

File ~/symforce/symforce/codegen/codegen.py:618, in Codegen.with_linearization(self, which_args, include_result, name, linearization_mode, sparse_linearization, custom_jacobian)
    613 common_msg = (
    614     "The output of a factor must be a column vector representing the "
    615     f"residual (of shape Nx1).  For factor \"{self.name}\", "
    616 )
    617 if python_util.scalar_like(result):
--> 618     raise ValueError(
    619         common_msg + "got a scalar expression instead.  Did you mean to wrap it in "
    620         "`geo.V1(expr)`?"
    621     )
    622 if isinstance(result, sf.Matrix):
    623     raise ValueError(common_msg + f"got a matrix of shape {result.shape} instead")

ValueError: The output of a factor must be a column vector representing the residual (of shape Nx1).  For factor "foo", got a scalar expression instead.  Did you mean to wrap it in `geo.V1(expr)`?

In [5]: def foo(x: sf.Scalar) -> sf.M22:
   ...:     return sf.M22(x, x**2, x**3, x**4)
   ...:

In [6]: codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()

File ~/symforce/symforce/codegen/codegen.py:623, in Codegen.with_linearization(self, which_args, include_result, name, linearization_mode, sparse_linearization, custom_jacobian)
    618         raise ValueError(
    619             common_msg + "got a scalar expression instead.  Did you mean to wrap it in "
    620             "`geo.V1(expr)`?"
    621         )
    622     if isinstance(result, sf.Matrix):
--> 623         raise ValueError(common_msg + f"got a matrix of shape {result.shape} instead")
    625     raise ValueError(common_msg + f"got an object of type {type(result)} instead")
    627 hessian = jacobian.compute_AtA(lower_only=True)

ValueError: The output of a factor must be a column vector representing the residual (of shape Nx1).  For factor "foo", got a matrix of shape (2, 2) instead

In [7]: def foo(x: sf.Scalar) -> sf.Rot3:
   ...:     return sf.Rot3.from_angle_axis(angle=x, axis=sf.V3(0, 0, 1))
   ...:

In [8]: codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 codegen.Codegen.function(foo, config=codegen.CppConfig()).with_linearization()

File ~/symforce/symforce/codegen/codegen.py:625, in Codegen.with_linearization(self, which_args, include_result, name, linearization_mode, sparse_linearization, custom_jacobian)
    622     if isinstance(result, sf.Matrix):
    623         raise ValueError(common_msg + f"got a matrix of shape {result.shape} instead")
--> 625     raise ValueError(common_msg + f"got an object of type {type(result)} instead")
    627 hessian = jacobian.compute_AtA(lower_only=True)
    628 outputs["hessian"] = hessian

ValueError: The output of a factor must be a column vector representing the residual (of shape Nx1).  For factor "foo", got an object of type <class 'symforce.geo.rot3.Rot3'> instead

For general objects, we're printing the type as opposed to the object so we don't attempt to print huge expressions (which isn't something I've been careful about previously, but should start being careful about).