locusrobotics / fuse

The fuse stack provides a general architecture for performing sensor fusion live on a robot. Some possible applications include state estimation, localization, mapping, and calibration.
Other
729 stars 122 forks source link

Gcc12 ceres 2.1.0 manifold locus #363

Closed svwilliams closed 7 months ago

svwilliams commented 8 months ago

Additional fixes and cleanup for #341.

This PR has a slightly strange history. It was started as a pull request from the community. Ultimately I needed to do some additional refactoring before I was happy with its state. But I cannot push changes to a community member's fork. So this PR includes all of the commits from #341 and adds an additional dozen or so commits from me at the end. I was pondering having separate reviews for the two pieces, but ultimately it is probably easier to understand as a single change set. When it comes time to merge it, I may merge #341 and then rebase this branch on top. Mostly to maintain proper attribution.

Anyway, what is this PR for?

The fuse library is built on top of a nonlinear least-squares solver, Google Ceres Solver. One of the concepts in Ceres Solver is a "local parameterization". This models the concept of variable updates that are nonlinear. The classic example is SO3, where a 3D rotation is represented by a 4-vector quaternion even though there are only 3 degrees of freedom. Further, all rotations must lie on the unit sphere in the 4D space. When the optimizer computes an update to a 3D rotation variable, it is basically computing a direction and step size to apply. This update can be thought of as living on a plane tangent to the 4D sphere at the starting rotation. And in this case, that tangent plane is only 3 dimensions since there are only 3 degrees of freedom. The LocalParameterization class is responsible for projecting the 3D linear update back onto the 4D nonlinear surface/manifold. Here's a more formal description if you don't like my incoherent rambling: http://ceres-solver.org/nnls_modeling.html#manifold

In Ceres Solver version 2.0 and before, the LocalParameterization class only handled projecting the linear delta from the tangent plane back onto the nonlinear manifold. It provided a method: Plus(x, delta) -> x_plus_delta, where x is the starting point/linearization point in the 4D quaternion space, delta is a displacement in the 3D tangent space, and the output is new rotation in 4D quaternion space. Ceres Solver did not provide a method to project a difference onto the tangent plane; basically the inverse operation: Minus(x, x_plus_delta) -> delta. However, the fuse project needed the inverse operation, so I added methods for that in fuse_core::LocalParameterization that was derived from the Ceres Solver class of the same name.

In Release 2.1, Ceres Solver decided it needed the inverse operation as well. Ceres added a new class, ceres::Manifold that implements both a Plus() and Minus() method, and changes a few function names as well.

In Release 2.2, Ceres Solver removed the LocalParameterization class entirely. This means that fuse needs to be updated to use the Manifold base class instead of the LocalParameterization base class. Merely updating the code is actually trivial. The problems are:

And how does the PR work?

My first instinct was to change the fuse::LocalParameterization class to derive from ceres::Manifold instead of ceres::LocalParameterization. The fuse base class could implement the translation logic required and derived classes would not need to be updated. However, there is a technical issue:

When I added the Minus() functionality to fuse::LocalParameterization, I selected the exact same name as the ceres::Manifold class. Further, the function signatures are identical: Minus(const double*, const double*, double*). But the parameter orders are different:

Instead, this PR:

needphpsql commented 7 months ago

Let's consider some use case:

  1. User has existing code using fuse before this PR. In some day later, the user's ceres is upgraded to 2.1 or 2.2. The user's source code doesn't need to change, however, they need to update the fuse library to get this PR and rebuild their program.
  2. User creates new code using the new fuse with this PR merged. In this condition, does the user need to use any new method to create their functions to use manifold class? Or they only need to use the fuse like before this PR?
svwilliams commented 7 months ago

Let's consider some use case:

  1. Correct. Ubuntu 24.04 will ship with Ceres 2.2. For this case, fuse will already be compiled with Manifold support. But for any older OS, fuse will be compiled without manifold support by default. If the user updates their local version of Ceres Solver to 2.1 or later, then the user will also need to download and compile fuse from source code. I can't think of a way around this.
  2. If the user code does not include any custom variable types, then the fuse interface remains unchanged. If the user code does include a custom variable, then I would recommend implementing the monifold() interface instead of the localParameterization(). However, this is not required. Newly created variables with localParameterization() functions will continue to operate correctly and automatically through the ManifoldAdapter class.