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

ImportError: cannot import name %FACTOR #299

Closed mikoff closed 1 year ago

mikoff commented 1 year ago

Describe the bug After defining the residuals and factors for simple pose-graph optimization the optimizers throws the strange error:

ImportError: cannot import name 'posePriorResidual_factor' from 'sym_2a492f37e7334b538f42439106d4982e.posePriorResidual_factor' (/tmp/sf_codegen_posePriorResidual_factor_s_1yh5ct/python/symforce/sym_2a492f37e7334b538f42439106d4982e/posePriorResidual_factor.py)

To Reproduce Minimal example to reproduce the problem:

import symforce

symforce.set_symbolic_api("sympy")
symforce.set_log_level("warning")
symforce.set_epsilon_to_symbol()

import symforce.symbolic as sf

######################
### define residuals
######################

def poseDeltaResidual(
        poseA: sf.Pose2, 
        poseB: sf.Pose2, 
        deltaMeasured: sf.Pose2,
        epsilon: sf.Scalar
    ) -> sf.V3:

    deltaEst = poseA.inverse() * poseB
    residual = sf.V3(deltaEst.local_coordinates(deltaMeasured, epsilon=epsilon))
    return residual

def posePriorResidual(
        pose: sf.Pose2,
        posePrior: sf.Pose2,
        epsilon: sf.Scalar
    ) -> sf.V3:

    residual = sf.V3(pose.local_coordinates(posePrior, epsilon=epsilon))
    return residual

######################
### initial values
######################

posePrior = sf.Pose2.identity()
poses = [sf.Pose2.identity(), sf.Pose2.identity(), sf.Pose2.identity()]
deltaPoses = [sf.Pose2.identity(), sf.Pose2.identity()]
deltaPoses[0].t = sf.V2(1.0, 0.0)
deltaPoses[1].t = sf.V2(1.0, 0.0)

from symforce.values import Values

initial_values = Values(
    poses=poses,
    deltaPoses=deltaPoses,
    posePrior=posePrior,
    epsilon=sf.numeric_epsilon,
)

######################
### add factors
######################

from symforce.opt.factor import Factor
factors = []

# Prior factor
factors.append(Factor(
    residual=posePriorResidual,
    keys=[f"poses[{0}]", "posePrior", "epsilon"]
))

# Odometry factors
for i in range(2):
    factors.append(Factor(
        residual=poseDeltaResidual,
        keys=[f"poses[{i}]", f"poses[{i + 1}]", f"deltaPoses[{i}]", "epsilon"],
    ))

######################
### try to optimize
######################

from symforce.opt.optimizer import Optimizer

optimizer = Optimizer(
    factors=factors,
    optimized_keys=[f"poses[{i}]" for i in range(len(poses))],
    debug_stats=True,
)

Expected behavior The optimization result or some meaningful output.

Environment (please complete the following information):

aaron-skydio commented 1 year ago

Yep this is definitely a bug - I've confirmed this reproduces on colab: https://colab.research.google.com/drive/11G80afyuW_Wxse4o4-Nikb8syi38Mjlk?usp=sharing

mikoff commented 1 year ago

Just an addition, found out that if we rename the residuals functions, let's say poseDeltaResidual->pose_delta_res and posePriorResidual->pose_prior_res, the problem is gone. It seems that it is somehow connected with symbols name resolution.

bradley-solliday-skydio commented 1 year ago

Hey Mikoff. Thanks for spotting this issue. As you suggested, the issue was with how we are handling the generated function and module names: specifically, assuming them to be the same even under circumstances when they aren't.

Shouldn't be hard to fix and I expect to get to it some time this week.

aaron-skydio commented 1 year ago

And yes, in the meantime you can work around this by sticking to lowercase function names