gtsam/python/FACTORS.md

2.6 KiB

GTSAM Python-based factors

One now can build factors purely in Python using the CustomFactor factor.

Theory

CustomFactor is a NonlinearFactor that has a std::function as its callback. This callback can be translated to a Python function call, thanks to pybind11's functional support.

Usage

In order to use a Python-based factor, one needs to have a Python function with the following signature:

import gtsam
import numpy as np
from typing import List

def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]):
    ...

this is a reference to the CustomFactor object. This is required because one can reuse the same error_func for multiple factors. v is a reference to the current set of values, and H is a list of references to the list of required Jacobians (see the corresponding C++ documentation).

If H is None, it means the current factor evaluation does not need Jacobians. For example, the error method on a factor does not need Jacobians, so we don't evaluate them to save CPU. If H is not None, each entry of H can be assigned a numpy array, as the Jacobian for the corresponding variable.

After defining error_func, one can create a CustomFactor just like any other factor in GTSAM:

noise_model = gtsam.noiseModel.Unit.Create(3)
# constructor(<noise model>, <list of keys>, <error callback>)
cf = gtsam.CustomFactor(noise_model, [X(0), X(1)], error_func)

Example

The following is a simple BetweenFactor implemented in Python.

import gtsam
import numpy as np
from typing import List

def error_func(this: gtsam.CustomFactor, v: gtsam.Values, H: List[np.ndarray]):
    # Get the variable values from `v`
    key0 = this.keys()[0]
    key1 = this.keys()[1]
    
    # Calculate non-linear error
    gT1, gT2 = v.atPose2(key0), v.atPose2(key1)
    error = gtsam.Pose2(0, 0, 0).localCoordinates(gT1.between(gT2))

    # If we need Jacobian
    if H is not None:
        # Fill the Jacobian arrays
        # Note we have two vars, so two entries
        result = gT1.between(gT2)
        H[0] = -result.inverse().AdjointMap()
        H[1] = np.eye(3)
    
    # Return the error
    return error

noise_model = gtsam.noiseModel.Unit.Create(3)
cf = gtsam.CustomFactor(noise_model, gtsam.KeyVector([0, 1]), error_func)

In general, the Python-based factor works just like their C++ counterparts.

Known Issues

Because of the pybind11-based translation, the performance of CustomFactor is not guaranteed. Also, because pybind11 needs to lock the Python GIL lock for evaluation of each factor, parallel evaluation of CustomFactor is not possible.