Source code for symforce.codegen.geo_factors_codegen

# ----------------------------------------------------------------------------
# SymForce - Copyright 2022, Skydio, Inc.
# This source code is under the Apache 2.0 license found in the LICENSE file.
# ----------------------------------------------------------------------------

from pathlib import Path

import symforce.symbolic as sf
from symforce import ops
from symforce import python_util
from symforce import typing as T
from symforce.codegen import Codegen
from symforce.codegen import CppConfig

TYPES = list(sf.GEO_TYPES) + list(sf.CAM_TYPES) + [sf.V3]


[docs]def get_between_factor_docstring(between_argument_name: str) -> str: return """ Residual that penalizes the difference between between(a, b) and {a_T_b}. In vector space terms that would be: (b - a) - {a_T_b} In lie group terms: local_coordinates({a_T_b}, between(a, b)) to_tangent(compose(inverse({a_T_b}), compose(inverse(a), b))) Args: sqrt_info: Square root information matrix to whiten residual. This can be computed from a covariance matrix as the cholesky decomposition of the inverse. In the case of a diagonal it will contain 1/sigma values. Must match the tangent dim. """.format(a_T_b=between_argument_name)
[docs]def get_prior_docstring() -> str: return """ Residual that penalizes the difference between a value and prior (desired / measured value). In vector space terms that would be: prior - value In lie group terms: to_tangent(compose(inverse(value), prior)) Args: sqrt_info: Square root information matrix to whiten residual. This can be computed from a covariance matrix as the cholesky decomposition of the inverse. In the case of a diagonal it will contain 1/sigma values. Must match the tangent dim. """
[docs]def between_factor( a: T.Element, b: T.Element, a_T_b: T.Element, sqrt_info: sf.Matrix, epsilon: sf.Scalar = 0 ) -> sf.Matrix: assert type(a) is type(b) is type(a_T_b) assert sqrt_info.rows == sqrt_info.cols == ops.LieGroupOps.tangent_dim(a) # Compute error tangent_error = ops.LieGroupOps.local_coordinates( a_T_b, ops.LieGroupOps.between(a, b), epsilon=epsilon ) # Apply noise model residual = sqrt_info * sf.M(tangent_error) return residual
[docs]def prior_factor( value: T.Element, prior: T.Element, sqrt_info: sf.Matrix, epsilon: sf.Scalar = 0 ) -> sf.Matrix: assert type(value) is type(prior) assert sqrt_info.rows == sqrt_info.cols == ops.LieGroupOps.tangent_dim(value) # Compute error tangent_error = ops.LieGroupOps.local_coordinates(prior, value, epsilon=epsilon) # Apply noise model residual = sqrt_info * sf.M(tangent_error) return residual
[docs]def generate_between_factors(types: T.Sequence[T.Type], output_dir: T.Openable) -> None: """ Generates between factors for each type in types into output_dir. """ for cls in types: tangent_dim = ops.LieGroupOps.tangent_dim(cls) between_codegen = Codegen.function( func=between_factor, input_types=[cls, cls, cls, sf.M(tangent_dim, tangent_dim), sf.Symbol], output_names=["res"], config=CppConfig(), docstring=get_between_factor_docstring("a_T_b"), ).with_linearization( name=f"between_factor_{python_util.camelcase_to_snakecase(cls.__name__)}", which_args=["a", "b"], ) between_codegen.generate_function(output_dir, skip_directory_nesting=True) prior_codegen = Codegen.function( func=prior_factor, input_types=[cls, cls, sf.M(tangent_dim, tangent_dim), sf.Symbol], output_names=["res"], config=CppConfig(), docstring=get_prior_docstring(), ).with_linearization( name=f"prior_factor_{python_util.camelcase_to_snakecase(cls.__name__)}", which_args=["value"], ) prior_codegen.generate_function(output_dir, skip_directory_nesting=True)
[docs]def generate_pose3_extra_factors(output_dir: T.Openable) -> None: """ Generates factors specific to Poses which penalize individual components into output_dir. This includes factors for only the position or rotation components of a Pose. This can't be done by wrapping the other generated functions because we need jacobians with respect to the full pose. """ def between_factor_pose3_rotation( a: sf.Pose3, b: sf.Pose3, a_R_b: sf.Rot3, sqrt_info: sf.Matrix33, epsilon: sf.Scalar = 0 ) -> sf.Matrix: # NOTE(aaron): This should be equivalent to between_factor(a.R, b.R, a_R_b), but we write it # this way for explicitness and symmetry with between_factor_pose3_position, where the two # are not equivalent tangent_error = ops.LieGroupOps.local_coordinates( a_R_b, ops.LieGroupOps.between(a, b).R, epsilon=epsilon ) return sqrt_info * sf.M(tangent_error) def between_factor_pose3_position( a: sf.Pose3, b: sf.Pose3, a_t_b: sf.Vector3, sqrt_info: sf.Matrix33, epsilon: sf.Scalar = 0, ) -> sf.Matrix: # NOTE(aaron): This is NOT the same as between_factor(a.t, b.t, a_t_b, sqrt_info, epsilon) # between_factor(a.t, b.t, a_t_b) would be penalizing the difference in the global frame # (and expecting a_t_b to be in the global frame), we want to penalize the position # component of between_factor(a, b, a_T_b), which is in the `a` frame tangent_error = ops.LieGroupOps.local_coordinates( a_t_b, ops.LieGroupOps.between(a, b).t, epsilon=epsilon ) return sqrt_info * sf.M(tangent_error) def between_factor_pose3_translation_norm( a: sf.Pose3, b: sf.Pose3, translation_norm: sf.Scalar, sqrt_info: sf.Matrix11, epsilon: sf.Scalar = 0, ) -> sf.Matrix: """ Residual that penalizes the difference between translation_norm and (a.t - b.t).norm(). Args: sqrt_info: Square root information matrix to whiten residual. In this one dimensional case this is just 1/sigma. """ error = translation_norm - (a.t - b.t).norm(epsilon) return sqrt_info * sf.M([error]) def prior_factor_pose3_rotation( value: sf.Pose3, prior: sf.Rot3, sqrt_info: sf.Matrix33, epsilon: sf.Scalar = 0 ) -> sf.Matrix: return prior_factor(value.R, prior, sqrt_info, epsilon) def prior_factor_pose3_position( value: sf.Pose3, prior: sf.Vector3, sqrt_info: sf.Matrix33, epsilon: sf.Scalar = 0 ) -> sf.Matrix: return prior_factor(value.t, prior, sqrt_info, epsilon) between_rotation_codegen = Codegen.function( func=between_factor_pose3_rotation, output_names=["res"], config=CppConfig(), docstring=get_between_factor_docstring("a_R_b"), ).with_linearization(name="between_factor_pose3_rotation", which_args=["a", "b"]) between_rotation_codegen.generate_function(output_dir, skip_directory_nesting=True) between_position_codegen = Codegen.function( func=between_factor_pose3_position, output_names=["res"], config=CppConfig(), docstring=get_between_factor_docstring("a_t_b"), ).with_linearization(name="between_factor_pose3_position", which_args=["a", "b"]) between_position_codegen.generate_function(output_dir, skip_directory_nesting=True) between_translation_norm_codegen = Codegen.function( func=between_factor_pose3_translation_norm, output_names=["res"], config=CppConfig() ).with_linearization(name="between_factor_pose3_translation_norm", which_args=["a", "b"]) between_translation_norm_codegen.generate_function(output_dir, skip_directory_nesting=True) prior_rotation_codegen = Codegen.function( func=prior_factor_pose3_rotation, output_names=["res"], config=CppConfig(), docstring=get_prior_docstring(), ).with_linearization(name="prior_factor_pose3_rotation", which_args=["value"]) prior_rotation_codegen.generate_function(output_dir, skip_directory_nesting=True) prior_position_codegen = Codegen.function( func=prior_factor_pose3_position, output_names=["res"], config=CppConfig(), docstring=get_prior_docstring(), ).with_linearization(name="prior_factor_pose3_position", which_args=["value"]) prior_position_codegen.generate_function(output_dir, skip_directory_nesting=True)
[docs]def generate(output_dir: Path) -> None: """ Prior factors and between factors for C++. """ generate_between_factors(types=TYPES, output_dir=output_dir / "factors") generate_pose3_extra_factors(output_dir / "factors")