blob: abddd5513e69767be0f694f365a752a72c1e9869 [file] [edit]
.. _chapter-modeling_faqs:
.. default-domain:: cpp
.. cpp:namespace:: ceres
========
Modeling
========
Use analytical or automatic derivatives
=======================================
Avoid numeric differentiation whenever possible. Numeric differentiation is slow,
sensitive to step-size choices and floating-point roundoff, and prone to poor
convergence.
Ceres can automatically differentiate templated cost functors, which is the
recommended approach for most problems. When the derivatives have a simple
closed form or the performance overhead of automatic differentiation is too
high, analytic derivatives are worth implementing directly.
Reserve numerical derivatives for cases where a templated implementation of the
cost function is impossible, such as when calling an external library. Even
then, you can often isolate the non-templated evaluation in a helper and
combine numeric differentiation of that part with automatic or analytic
differentiation of the rest using :class:`CostFunctionToFunctor`.
Do not return the norm or squared norm of a residual vector from a :class:`CostFunction`
========================================================================================
A :class:`CostFunction` should output the signed components of the residual
vector :math:`r(x) \in \mathbb{R}^k` directly, never its Euclidean norm
:math:`\|r(x)\|` or squared norm :math:`\|r(x)\|^2`. Ceres already forms the
sum of squared components :math:`\frac{1}{2} \|r(x)\|^2 = \frac{1}{2} \sum_{i=1}^k r_i(x)^2`
internally.
Collapsing a :math:`k`-dimensional residual vector into a scalar :math:`\tilde{r}(x) = \|r(x)\|`
leaves the objective value unchanged, but replaces the :math:`k \times n`
Jacobian :math:`J(x)` with the single row :math:`\frac{r(x)^\top}{\|r(x)\|} J(x)`.
The Gauss-Newton Hessian approximation then drops from :math:`J(x)^\top J(x)` to
the rank-1 matrix :math:`\frac{1}{\|r(x)\|^2} J(x)^\top r(x) r(x)^\top J(x)`,
discarding curvature information in all directions orthogonal to :math:`r(x)`
and stalling convergence. Worse, :math:`\|r(x)\|` is not differentiable at
:math:`r(x) = 0`, producing ``NaN`` Jacobians at the exact point the solver is
trying to reach. Returning :math:`\|r(x)\|^2` avoids the ``NaN`` but changes the
objective to :math:`\frac{1}{2}\|r(x)\|^4`, whose Jacobian :math:`2 r(x)^\top J(x)`
vanishes as :math:`r(x) \to 0`, making the Gauss-Newton system singular near the
solution.
Do not pack multiple independent measurements into a single residual block when using a :class:`LossFunction`
=============================================================================================================
A :class:`LossFunction` operates on the squared norm
:math:`s = \|f_i(x)\|^2 = \sum_{j=1}^{k_i} f_{ij}(x)^2` of an entire residual
block, not on its individual scalar components :math:`f_{ij}(x)` separately.
For a multi-dimensional measurement such as a 2D image reprojection error, this
is desirable: :math:`\rho(\|f_i(x)\|^2)` is isotropic (rotationally invariant
in measurement space) and down-weights both coordinates together whenever the
feature match is an outlier.
Conversely, you should not pack multiple independent measurements into a single
:class:`CostFunction` if you intend to use a non-trivial :class:`LossFunction`.
A single outlier component will inflate :math:`\|f_i(x)\|^2` and cause the
:class:`LossFunction` to down-weight every other inlier measurement in the same
residual block. Independent measurements that can fail independently should be
added as separate residual blocks.
When using Quaternions, consider using :class:`QuaternionManifold`
==================================================================
`Quaternions <https://en.wikipedia.org/wiki/Quaternion>`_ are a
four-dimensional parameterization of the three-dimensional rotation group
:math:`SO(3)`. Because :math:`SO(3)` is three-dimensional, the tangent space of
a unit quaternion is also three-dimensional, so it is often beneficial to
attach a :class:`Manifold` to quaternion parameter blocks. For parameter blocks
stored in :math:`w, x, y, z` order, use :class:`QuaternionManifold`.
.. NOTE::
If you are using `Eigen's Quaternion
<https://libeigen.gitlab.io/eigen/docs-nightly/classEigen_1_1Quaternion.html>`_,
whose memory layout is :math:`x, y, z, w`, use
:class:`EigenQuaternionManifold` instead.
How do I solve problems with general linear & non-linear **inequality** constraints with Ceres Solver?
======================================================================================================
Ceres Solver directly supports only lower and upper bound constraints on
parameter blocks.
A crude workaround for general inequality constraints is to have one or more
cost functions check whether the inequalities are satisfied and return ``false``
when they are violated. Returning ``false`` rejects the candidate step and
prevents the solver from entering the infeasible region, provided the initial
parameter values are feasible. Because the solver cannot see the constraint
boundary until a step crosses it, however, this approach can cause premature
convergence.
How do I solve problems with general linear & non-linear **equality** constraints with Ceres Solver?
====================================================================================================
Ceres Solver does not support general equality constraints directly; it supports
only lower and upper bound constraints on parameter blocks. The step-rejection
workaround described above for inequality constraints does **not** work for
equality constraints.
How do I set one or more components of a parameter block constant?
==================================================================
Attach a :class:`SubsetManifold` to the parameter block to hold the desired
coordinates constant during optimization.