Brush C++ API
A flexible interpretable machine learning framework
Loading...
Searching...
No Matches
tiny_cost_function.h
Go to the documentation of this file.
1/* Brush
2copyright 2020 William La Cava
3license: GNU/GPL v3
4
5Code below heavily inspired by heal-research/operon, Copyright 2019-2022 Heal Research
6*/
7
8#ifndef TINY_OPTIMIZER
9#define TINY_OPTIMIZER
10
11
12namespace Brush {
13using Scalar = float;
14// this cost function is adapted to work with both solvers from Ceres: the normal one and the tiny solver
15// for this, a number of template parameters are necessary:
16// - the CostFunctor is the actual functor for computing the residuals
17// - the Dual type represents a dual number, the user can specify the type for the Scalar part (float, double) and the Stride (Ceres-specific)
18// - the StorageOrder specifies the format of the jacobian (row-major for the big Ceres solver, column-major for the tiny solver)
19
20// TODO: eliminate this and use the Ceres tiny solver autodiff function instead:
21// https://github.com/ceres-solver/ceres-solver/blob/caf614a6c1ac1717be606c37fe434391edb2f417/include/ceres/tiny_solver_autodiff_function.h
22namespace detail {
23 template<typename CostFunctor, typename Dual, typename Scalar, int JacobianLayout = Eigen::ColMajor>
24 inline auto Autodiff(CostFunctor const& function, Scalar const* parameters, Scalar* residuals, Scalar* jacobian) -> bool
25 {
26 static_assert(std::is_convertible_v<typename Dual::Scalar, Scalar>, "The chosen Jet and Scalar types are not compatible.");
27 static_assert(std::is_convertible_v<Scalar, typename Dual::Scalar>, "The chosen Jet and Scalar types are not compatible.");
28
29 // EXPECT(parameters != nullptr);
30 // EXPECT(residuals != nullptr || jacobian != nullptr);
31
32 if (jacobian == nullptr) {
33 return function(parameters, residuals);
34 }
35
36 std::vector<Dual> inputs(function.NumParameters());
37 for (size_t i = 0; i < inputs.size(); ++i) {
38 inputs[i].a = parameters[i];
39 inputs[i].v.setZero();
40 }
41 std::vector<Dual> outputs(function.NumResiduals());
42
43 static auto constexpr D{Dual::DIMENSION};
44 Eigen::Map<Eigen::Matrix<Scalar, -1, -1, JacobianLayout>> jmap(jacobian, outputs.size(), inputs.size());
45
46 for (int s = 0; s < inputs.size(); s += D) {
47 int r = std::min(static_cast<int>(inputs.size()), s + D); // remaining parameters
48
49 for (int i = s; i < r; ++i) {
50 inputs[i].v[i - s] = 1.0;
51 }
52
53 if (!function(inputs.data(), outputs.data())) {
54 return false;
55 }
56
57 for (int i = s; i < r; ++i) {
58 inputs[i].v[i - s] = 0.0;
59 }
60
61 // fill in the jacobian trying to exploit its layout for efficiency
62 if constexpr (JacobianLayout == Eigen::ColMajor) {
63 for (int i = s; i < r; ++i) {
64 std::transform(outputs.cbegin(), outputs.cend(), jmap.col(i).data(), [&](auto const& jet) { return jet.v[i - s]; });
65 }
66 } else {
67 for (auto i = 0; i < outputs.size(); ++i) {
68 std::copy_n(outputs[i].v.data(), r - s, jmap.row(i).data() + s);
69 }
70 }
71 }
72 if (residuals != nullptr) {
73 std::transform(std::cbegin(outputs), std::cend(outputs), residuals, [](auto const& jet) { return jet.a; });
74 }
75 return true;
76 }
77} // namespace detail
78
79template <typename CostFunctor, typename DualType=fJet, typename ScalarType=float,
80 int StorageOrder = Eigen::ColMajor>
82 static constexpr int Stride = DualType::DIMENSION;
83 static constexpr int Storage = StorageOrder;
85
86 enum {
87 NUM_RESIDUALS = Eigen::Dynamic, // NOLINT
88 NUM_PARAMETERS = Eigen::Dynamic, // NOLINT
89 };
90
93 {
94 }
95
96 auto Evaluate(Scalar const* parameters, Scalar* residuals, Scalar* jacobian) const -> bool
97 {
98 return detail::Autodiff<CostFunctor, DualType, ScalarType, StorageOrder>(functor_, parameters, residuals, jacobian);
99 }
100
101 // ceres solver - jacobian must be in row-major format
102 // ceres tiny solver - jacobian must be in col-major format
103 auto operator()(Scalar const* parameters, Scalar* residuals, Scalar* jacobian) const -> bool
104 {
105 return Evaluate(parameters, residuals, jacobian);
106 }
107
108 [[nodiscard]] auto NumResiduals() const -> int { return functor_.NumResiduals(); }
109 [[nodiscard]] auto NumParameters() const -> int { return functor_.NumParameters(); }
110
111 // required by Eigen::LevenbergMarquardt
112 using JacobianType = Eigen::Matrix<Brush::Scalar, -1, -1>;
113 using QRSolver = Eigen::ColPivHouseholderQR<JacobianType>;
114
115 // there is no real documentation but looking at Eigen unit tests, these functions should return zero
116 // see: https://gitlab.com/libeigen/eigen/-/blob/master/unsupported/test/NonLinearOptimization.cpp
117 auto operator()(Eigen::Matrix<Scalar, -1, 1> const& input, Eigen::Matrix<Scalar, -1, 1> &residual) -> int
118 {
119 Evaluate(input.data(), residual.data(), nullptr);
120 return 0;
121 }
122
123 auto df(Eigen::Matrix<Scalar, -1, 1> const& input, Eigen::Matrix<Scalar, -1, -1> &jacobian) -> int // NOLINT
124 {
125 static_assert(StorageOrder == Eigen::ColMajor, "Eigen::LevenbergMarquardt requires the Jacobian to be stored in column-major format.");
126 Evaluate(input.data(), nullptr, jacobian.data());
127 return 0;
128 }
129
130 [[nodiscard]] auto values() const -> int { return NumResiduals(); } // NOLINT
131 [[nodiscard]] auto inputs() const -> int { return NumParameters(); } // NOLINT
132
133private:
135};
136} // namespace Brush
137
138#endif
void bind_engine(py::module &m, string name)
auto Autodiff(CostFunctor const &function, Scalar const *parameters, Scalar *residuals, Scalar *jacobian) -> bool
< nsga2 selection operator for getting the front
Definition data.cpp:12
ceres::Jet< float, stride > fJet
Definition types.h:45
static constexpr int Stride
auto values() const -> int
auto operator()(Scalar const *parameters, Scalar *residuals, Scalar *jacobian) const -> bool
auto Evaluate(Scalar const *parameters, Scalar *residuals, Scalar *jacobian) const -> bool
auto operator()(Eigen::Matrix< Scalar, -1, 1 > const &input, Eigen::Matrix< Scalar, -1, 1 > &residual) -> int
static constexpr int Storage
auto NumResiduals() const -> int
TinyCostFunction(CostFunctor const &functor)
Eigen::ColPivHouseholderQR< JacobianType > QRSolver
auto df(Eigen::Matrix< Scalar, -1, 1 > const &input, Eigen::Matrix< Scalar, -1, -1 > &jacobian) -> int
auto inputs() const -> int
Eigen::Matrix< Brush::Scalar, -1, -1 > JacobianType
auto NumParameters() const -> int