Files
ceres-solver/include/ceres/dynamic_autodiff_cost_function.h
T
Sergiu Deitsch 91773746be Simplify instantiation of cost functions and their functors
If arguments are passed to a cost function that can be used to construct
the functor, the latter will be instantiated by the cost function using
std::make_unique to ensure exception safety. This not only avoids static
analysis warnings caused by calling new but also spelling the cost
functor type name multiple times.

Also expand deduction guides for instantiating
Dynamic(Auto|Numeric)DiffCostFunction from std::unique_ptr enabled
constructor overloads.

Finally, make CostFunction default move constructible and assignable but
only through derived classes. This in turn allows derived classes to be
movable without relying on custom implementations of corresponding
operators.

Change-Id: Idee8b9871d862bc9f9f8b5a8d0bedc52863e93c0
2024-01-23 01:34:27 +01:00

319 lines
13 KiB
C++

// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2024 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: sameeragarwal@google.com (Sameer Agarwal)
// mierle@gmail.com (Keir Mierle)
#ifndef CERES_PUBLIC_DYNAMIC_AUTODIFF_COST_FUNCTION_H_
#define CERES_PUBLIC_DYNAMIC_AUTODIFF_COST_FUNCTION_H_
#include <cmath>
#include <memory>
#include <numeric>
#include <type_traits>
#include <vector>
#include "ceres/dynamic_cost_function.h"
#include "ceres/internal/fixed_array.h"
#include "ceres/jet.h"
#include "ceres/types.h"
#include "glog/logging.h"
namespace ceres {
// This autodiff implementation differs from the one found in
// autodiff_cost_function.h by supporting autodiff on cost functions
// with variable numbers of parameters with variable sizes. With the
// other implementation, all the sizes (both the number of parameter
// blocks and the size of each block) must be fixed at compile time.
//
// The functor API differs slightly from the API for fixed size
// autodiff; the expected interface for the cost functors is:
//
// struct MyCostFunctor {
// template<typename T>
// bool operator()(T const* const* parameters, T* residuals) const {
// // Use parameters[i] to access the i'th parameter block.
// }
// };
//
// Since the sizing of the parameters is done at runtime, you must
// also specify the sizes after creating the dynamic autodiff cost
// function. For example:
//
// DynamicAutoDiffCostFunction<MyCostFunctor, 3> cost_function;
// cost_function.AddParameterBlock(5);
// cost_function.AddParameterBlock(10);
// cost_function.SetNumResiduals(21);
//
// Under the hood, the implementation evaluates the cost function
// multiple times, computing a small set of the derivatives (four by
// default, controlled by the Stride template parameter) with each
// pass. There is a tradeoff with the size of the passes; you may want
// to experiment with the stride.
template <typename CostFunctor, int Stride = 4>
class DynamicAutoDiffCostFunction final : public DynamicCostFunction {
public:
// Constructs the CostFunctor on the heap and takes the ownership.
template <class... Args,
std::enable_if_t<std::is_constructible_v<CostFunctor, Args&&...>>* =
nullptr>
explicit DynamicAutoDiffCostFunction(Args&&... args)
// NOTE We explicitly use direct initialization using parentheses instead
// of uniform initialization using braces to avoid narrowing conversion
// warnings.
: DynamicAutoDiffCostFunction{
std::make_unique<CostFunctor>(std::forward<Args>(args)...)} {}
// Takes ownership by default.
explicit DynamicAutoDiffCostFunction(CostFunctor* functor,
Ownership ownership = TAKE_OWNERSHIP)
: DynamicAutoDiffCostFunction{std::unique_ptr<CostFunctor>{functor},
ownership} {}
explicit DynamicAutoDiffCostFunction(std::unique_ptr<CostFunctor> functor)
: DynamicAutoDiffCostFunction{std::move(functor), TAKE_OWNERSHIP} {}
DynamicAutoDiffCostFunction(const DynamicAutoDiffCostFunction& other) =
delete;
DynamicAutoDiffCostFunction& operator=(
const DynamicAutoDiffCostFunction& other) = delete;
DynamicAutoDiffCostFunction(DynamicAutoDiffCostFunction&& other) noexcept =
default;
DynamicAutoDiffCostFunction& operator=(
DynamicAutoDiffCostFunction&& other) noexcept = default;
~DynamicAutoDiffCostFunction() override {
// Manually release pointer if configured to not take ownership
// rather than deleting only if ownership is taken. This is to
// stay maximally compatible to old user code which may have
// forgotten to implement a virtual destructor, from when the
// AutoDiffCostFunction always took ownership.
if (ownership_ == DO_NOT_TAKE_OWNERSHIP) {
functor_.release();
}
}
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const override {
CHECK_GT(num_residuals(), 0)
<< "You must call DynamicAutoDiffCostFunction::SetNumResiduals() "
<< "before DynamicAutoDiffCostFunction::Evaluate().";
if (jacobians == nullptr) {
return (*functor_)(parameters, residuals);
}
// The difficulty with Jets, as implemented in Ceres, is that they were
// originally designed for strictly compile-sized use. At this point, there
// is a large body of code that assumes inside a cost functor it is
// acceptable to do e.g. T(1.5) and get an appropriately sized jet back.
//
// Unfortunately, it is impossible to communicate the expected size of a
// dynamically sized jet to the static instantiations that existing code
// depends on.
//
// To work around this issue, the solution here is to evaluate the
// jacobians in a series of passes, each one computing Stride *
// num_residuals() derivatives. This is done with small, fixed-size jets.
const int num_parameter_blocks =
static_cast<int>(parameter_block_sizes().size());
const int num_parameters = std::accumulate(
parameter_block_sizes().begin(), parameter_block_sizes().end(), 0);
// Allocate scratch space for the strided evaluation.
using JetT = Jet<double, Stride>;
internal::FixedArray<JetT, (256 * 7) / sizeof(JetT)> input_jets(
num_parameters);
internal::FixedArray<JetT, (256 * 7) / sizeof(JetT)> output_jets(
num_residuals());
// Make the parameter pack that is sent to the functor (reused).
internal::FixedArray<Jet<double, Stride>*> jet_parameters(
num_parameter_blocks, nullptr);
int num_active_parameters = 0;
// To handle constant parameters between non-constant parameter blocks, the
// start position --- a raw parameter index --- of each contiguous block of
// non-constant parameters is recorded in start_derivative_section.
std::vector<int> start_derivative_section;
bool in_derivative_section = false;
int parameter_cursor = 0;
// Discover the derivative sections and set the parameter values.
for (int i = 0; i < num_parameter_blocks; ++i) {
jet_parameters[i] = &input_jets[parameter_cursor];
const int parameter_block_size = parameter_block_sizes()[i];
if (jacobians[i] != nullptr) {
if (!in_derivative_section) {
start_derivative_section.push_back(parameter_cursor);
in_derivative_section = true;
}
num_active_parameters += parameter_block_size;
} else {
in_derivative_section = false;
}
for (int j = 0; j < parameter_block_size; ++j, parameter_cursor++) {
input_jets[parameter_cursor].a = parameters[i][j];
}
}
if (num_active_parameters == 0) {
return (*functor_)(parameters, residuals);
}
// When `num_active_parameters % Stride != 0` then it can be the case
// that `active_parameter_count < Stride` while parameter_cursor is less
// than the total number of parameters and with no remaining non-constant
// parameter blocks. Pushing parameter_cursor (the total number of
// parameters) as a final entry to start_derivative_section is required
// because if a constant parameter block is encountered after the
// last non-constant block then current_derivative_section is incremented
// and would otherwise index an invalid position in
// start_derivative_section. Setting the final element to the total number
// of parameters means that this can only happen at most once in the loop
// below.
start_derivative_section.push_back(parameter_cursor);
// Evaluate all of the strides. Each stride is a chunk of the derivative to
// evaluate, typically some size proportional to the size of the SIMD
// registers of the CPU.
int num_strides = static_cast<int>(
ceil(num_active_parameters / static_cast<float>(Stride)));
int current_derivative_section = 0;
int current_derivative_section_cursor = 0;
for (int pass = 0; pass < num_strides; ++pass) {
// Set most of the jet components to zero, except for
// non-constant #Stride parameters.
const int initial_derivative_section = current_derivative_section;
const int initial_derivative_section_cursor =
current_derivative_section_cursor;
int active_parameter_count = 0;
parameter_cursor = 0;
for (int i = 0; i < num_parameter_blocks; ++i) {
for (int j = 0; j < parameter_block_sizes()[i];
++j, parameter_cursor++) {
input_jets[parameter_cursor].v.setZero();
if (active_parameter_count < Stride &&
parameter_cursor >=
(start_derivative_section[current_derivative_section] +
current_derivative_section_cursor)) {
if (jacobians[i] != nullptr) {
input_jets[parameter_cursor].v[active_parameter_count] = 1.0;
++active_parameter_count;
++current_derivative_section_cursor;
} else {
++current_derivative_section;
current_derivative_section_cursor = 0;
}
}
}
}
if (!(*functor_)(&jet_parameters[0], &output_jets[0])) {
return false;
}
// Copy the pieces of the jacobians into their final place.
active_parameter_count = 0;
current_derivative_section = initial_derivative_section;
current_derivative_section_cursor = initial_derivative_section_cursor;
for (int i = 0, parameter_cursor = 0; i < num_parameter_blocks; ++i) {
for (int j = 0; j < parameter_block_sizes()[i];
++j, parameter_cursor++) {
if (active_parameter_count < Stride &&
parameter_cursor >=
(start_derivative_section[current_derivative_section] +
current_derivative_section_cursor)) {
if (jacobians[i] != nullptr) {
for (int k = 0; k < num_residuals(); ++k) {
jacobians[i][k * parameter_block_sizes()[i] + j] =
output_jets[k].v[active_parameter_count];
}
++active_parameter_count;
++current_derivative_section_cursor;
} else {
++current_derivative_section;
current_derivative_section_cursor = 0;
}
}
}
}
// Only copy the residuals over once (even though we compute them on
// every loop).
if (pass == num_strides - 1) {
for (int k = 0; k < num_residuals(); ++k) {
residuals[k] = output_jets[k].a;
}
}
}
return true;
}
const CostFunctor& functor() const { return *functor_; }
private:
explicit DynamicAutoDiffCostFunction(std::unique_ptr<CostFunctor> functor,
Ownership ownership)
: functor_(std::move(functor)), ownership_(ownership) {}
std::unique_ptr<CostFunctor> functor_;
Ownership ownership_;
};
// Deduction guide that allows the user to avoid explicitly specifying the
// template parameter of DynamicAutoDiffCostFunction. The class can instead be
// instantiated as follows:
//
// new DynamicAutoDiffCostFunction{new MyCostFunctor{}};
// new DynamicAutoDiffCostFunction{std::make_unique<MyCostFunctor>()};
//
template <typename CostFunctor>
DynamicAutoDiffCostFunction(CostFunctor* functor)
-> DynamicAutoDiffCostFunction<CostFunctor>;
template <typename CostFunctor>
DynamicAutoDiffCostFunction(CostFunctor* functor, Ownership ownership)
-> DynamicAutoDiffCostFunction<CostFunctor>;
template <typename CostFunctor>
DynamicAutoDiffCostFunction(std::unique_ptr<CostFunctor> functor)
-> DynamicAutoDiffCostFunction<CostFunctor>;
} // namespace ceres
#endif // CERES_PUBLIC_DYNAMIC_AUTODIFF_COST_FUNCTION_H_