blob: fa05835e990e1d1caea64b83e4fa92ef91b35af5 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001.. default-domain:: cpp
2
3.. cpp:namespace:: ceres
4
5.. _chapter-interfacing_with_automatic_differentiation:
6
7Interfacing with Automatic Differentiation
8==========================================
9
10Automatic differentiation is straightforward to use in cases where an
11explicit expression for the cost function is available. But this is
12not always possible. Often one has to interface with external routines
13or data. In this chapter we will consider a number of different ways
14of doing so.
15
16To do this, we will consider the problem of finding parameters
17:math:`\theta` and :math:`t` that solve an optimization problem of the
18form:
19
20.. math::
21 \min & \quad \sum_i \left \|y_i - f\left (\|q_{i}\|^2\right) q_i
22 \right \|^2\\
23 \text{such that} & \quad q_i = R(\theta) x_i + t
24
25Here, :math:`R` is a two dimensional rotation matrix parameterized
26using the angle :math:`\theta` and :math:`t` is a two dimensional
27vector. :math:`f` is an external distortion function.
28
29We begin by considering the case, where we have a templated function
30:code:`TemplatedComputeDistortion` that can compute the function
31:math:`f`. Then the implementation of the corresponding residual
32functor is straightforward and will look as follows:
33
34.. code-block:: c++
35 :emphasize-lines: 21
36
37 template <typename T> T TemplatedComputeDistortion(const T r2) {
38 const double k1 = 0.0082;
39 const double k2 = 0.000023;
Austin Schuh3de38b02024-06-25 18:25:10 -070040 return 1.0 + k1 * r2 + k2 * r2 * r2;
Austin Schuh70cc9552019-01-21 19:46:48 -080041 }
42
43 struct Affine2DWithDistortion {
44 Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
45 x[0] = x_in[0];
46 x[1] = x_in[1];
47 y[0] = y_in[0];
48 y[1] = y_in[1];
49 }
50
51 template <typename T>
52 bool operator()(const T* theta,
53 const T* t,
54 T* residuals) const {
55 const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
56 const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
57 const T f = TemplatedComputeDistortion(q_0 * q_0 + q_1 * q_1);
58 residuals[0] = y[0] - f * q_0;
59 residuals[1] = y[1] - f * q_1;
60 return true;
61 }
62
63 double x[2];
64 double y[2];
65 };
66
67So far so good, but let us now consider three ways of defining
68:math:`f` which are not directly amenable to being used with automatic
69differentiation:
70
71#. A non-templated function that evaluates its value.
72#. A function that evaluates its value and derivative.
73#. A function that is defined as a table of values to be interpolated.
74
75We will consider them in turn below.
76
77A function that returns its value
78----------------------------------
79
80Suppose we were given a function :code:`ComputeDistortionValue` with
81the following signature
82
83.. code-block:: c++
84
85 double ComputeDistortionValue(double r2);
86
87that computes the value of :math:`f`. The actual implementation of the
88function does not matter. Interfacing this function with
89:code:`Affine2DWithDistortion` is a three step process:
90
911. Wrap :code:`ComputeDistortionValue` into a functor
92 :code:`ComputeDistortionValueFunctor`.
932. Numerically differentiate :code:`ComputeDistortionValueFunctor`
94 using :class:`NumericDiffCostFunction` to create a
95 :class:`CostFunction`.
963. Wrap the resulting :class:`CostFunction` object using
97 :class:`CostFunctionToFunctor`. The resulting object is a functor
98 with a templated :code:`operator()` method, which pipes the
99 Jacobian computed by :class:`NumericDiffCostFunction` into the
100 appropriate :code:`Jet` objects.
101
102An implementation of the above three steps looks as follows:
103
104.. code-block:: c++
105 :emphasize-lines: 15,16,17,18,19,20, 29
106
107 struct ComputeDistortionValueFunctor {
108 bool operator()(const double* r2, double* value) const {
109 *value = ComputeDistortionValue(r2[0]);
110 return true;
111 }
112 };
113
114 struct Affine2DWithDistortion {
115 Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
116 x[0] = x_in[0];
117 x[1] = x_in[1];
118 y[0] = y_in[0];
119 y[1] = y_in[1];
120
Austin Schuh3de38b02024-06-25 18:25:10 -0700121 compute_distortion = std::make_unique<ceres::CostFunctionToFunctor<1, 1>>(
122 std::make_unique<ceres::NumericDiffCostFunction<
123 ComputeDistortionValueFunctor
124 , ceres::CENTRAL, 1, 1
125 >
126 >()
127 );
Austin Schuh70cc9552019-01-21 19:46:48 -0800128 }
129
130 template <typename T>
131 bool operator()(const T* theta, const T* t, T* residuals) const {
132 const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
133 const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
134 const T r2 = q_0 * q_0 + q_1 * q_1;
135 T f;
136 (*compute_distortion)(&r2, &f);
137 residuals[0] = y[0] - f * q_0;
138 residuals[1] = y[1] - f * q_1;
139 return true;
140 }
141
142 double x[2];
143 double y[2];
Austin Schuh3de38b02024-06-25 18:25:10 -0700144 std::unique_ptr<ceres::CostFunctionToFunctor<1, 1>> compute_distortion;
Austin Schuh70cc9552019-01-21 19:46:48 -0800145 };
146
147
148A function that returns its value and derivative
149------------------------------------------------
150
151Now suppose we are given a function :code:`ComputeDistortionValue`
Austin Schuh3de38b02024-06-25 18:25:10 -0700152that is able to compute its value and optionally its Jacobian on demand
Austin Schuh70cc9552019-01-21 19:46:48 -0800153and has the following signature:
154
155.. code-block:: c++
156
157 void ComputeDistortionValueAndJacobian(double r2,
158 double* value,
159 double* jacobian);
160
161Again, the actual implementation of the function does not
162matter. Interfacing this function with :code:`Affine2DWithDistortion`
163is a two step process:
164
1651. Wrap :code:`ComputeDistortionValueAndJacobian` into a
166 :class:`CostFunction` object which we call
167 :code:`ComputeDistortionFunction`.
1682. Wrap the resulting :class:`ComputeDistortionFunction` object using
169 :class:`CostFunctionToFunctor`. The resulting object is a functor
170 with a templated :code:`operator()` method, which pipes the
171 Jacobian computed by :class:`NumericDiffCostFunction` into the
172 appropriate :code:`Jet` objects.
173
174The resulting code will look as follows:
175
176.. code-block:: c++
177 :emphasize-lines: 21,22, 33
178
179 class ComputeDistortionFunction : public ceres::SizedCostFunction<1, 1> {
180 public:
181 virtual bool Evaluate(double const* const* parameters,
182 double* residuals,
183 double** jacobians) const {
184 if (!jacobians) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800185 ComputeDistortionValueAndJacobian(parameters[0][0], residuals, nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -0800186 } else {
187 ComputeDistortionValueAndJacobian(parameters[0][0], residuals, jacobians[0]);
188 }
189 return true;
190 }
191 };
192
193 struct Affine2DWithDistortion {
194 Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
195 x[0] = x_in[0];
196 x[1] = x_in[1];
197 y[0] = y_in[0];
198 y[1] = y_in[1];
199 compute_distortion.reset(
200 new ceres::CostFunctionToFunctor<1, 1>(new ComputeDistortionFunction));
201 }
202
203 template <typename T>
204 bool operator()(const T* theta,
205 const T* t,
206 T* residuals) const {
207 const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
208 const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
209 const T r2 = q_0 * q_0 + q_1 * q_1;
210 T f;
211 (*compute_distortion)(&r2, &f);
212 residuals[0] = y[0] - f * q_0;
213 residuals[1] = y[1] - f * q_1;
214 return true;
215 }
216
217 double x[2];
218 double y[2];
219 std::unique_ptr<ceres::CostFunctionToFunctor<1, 1> > compute_distortion;
220 };
221
222
223A function that is defined as a table of values
224-----------------------------------------------
225
226The third and final case we will consider is where the function
227:math:`f` is defined as a table of values on the interval :math:`[0,
228100)`, with a value for each integer.
229
230.. code-block:: c++
231
232 vector<double> distortion_values;
233
234There are many ways of interpolating a table of values. Perhaps the
235simplest and most common method is linear interpolation. But it is not
236a great idea to use linear interpolation because the interpolating
237function is not differentiable at the sample points.
238
239A simple (well behaved) differentiable interpolation is the `Cubic
240Hermite Spline
241<http://en.wikipedia.org/wiki/Cubic_Hermite_spline>`_. Ceres Solver
242ships with routines to perform Cubic & Bi-Cubic interpolation that is
243automatic differentiation friendly.
244
245Using Cubic interpolation requires first constructing a
246:class:`Grid1D` object to wrap the table of values and then
247constructing a :class:`CubicInterpolator` object using it.
248
249The resulting code will look as follows:
250
251.. code-block:: c++
252 :emphasize-lines: 10,11,12,13, 24, 32,33
253
254 struct Affine2DWithDistortion {
255 Affine2DWithDistortion(const double x_in[2],
256 const double y_in[2],
257 const std::vector<double>& distortion_values) {
258 x[0] = x_in[0];
259 x[1] = x_in[1];
260 y[0] = y_in[0];
261 y[1] = y_in[1];
262
263 grid.reset(new ceres::Grid1D<double, 1>(
264 &distortion_values[0], 0, distortion_values.size()));
265 compute_distortion.reset(
266 new ceres::CubicInterpolator<ceres::Grid1D<double, 1> >(*grid));
267 }
268
269 template <typename T>
270 bool operator()(const T* theta,
271 const T* t,
272 T* residuals) const {
273 const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
274 const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
275 const T r2 = q_0 * q_0 + q_1 * q_1;
276 T f;
277 compute_distortion->Evaluate(r2, &f);
278 residuals[0] = y[0] - f * q_0;
279 residuals[1] = y[1] - f * q_1;
280 return true;
281 }
282
283 double x[2];
284 double y[2];
285 std::unique_ptr<ceres::Grid1D<double, 1> > grid;
286 std::unique_ptr<ceres::CubicInterpolator<ceres::Grid1D<double, 1> > > compute_distortion;
287 };
288
289In the above example we used :class:`Grid1D` and
290:class:`CubicInterpolator` to interpolate a one dimensional table of
291values. :class:`Grid2D` combined with :class:`CubicInterpolator` lets
292the user to interpolate two dimensional tables of values. Note that
293neither :class:`Grid1D` or :class:`Grid2D` are limited to scalar
294valued functions, they also work with vector valued functions.