tiny_solver.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 Google Inc. All rights reserved.
  3. // http://ceres-solver.org/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Google Inc. nor the names of its contributors may be
  14. // used to endorse or promote products derived from this software without
  15. // specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. // POSSIBILITY OF SUCH DAMAGE.
  28. //
  29. // Author: mierle@gmail.com (Keir Mierle)
  30. //
  31. // WARNING WARNING WARNING
  32. // WARNING WARNING WARNING Tiny solver is experimental and will change.
  33. // WARNING WARNING WARNING
  34. //
  35. // A tiny least squares solver using Levenberg-Marquardt, intended for solving
  36. // small dense problems with low latency and low overhead. The implementation
  37. // takes care to do all allocation up front, so that no memory is allocated
  38. // during solving. This is especially useful when solving many similar problems;
  39. // for example, inverse pixel distortion for every pixel on a grid.
  40. //
  41. // Note: This code has no dependencies beyond Eigen, including on other parts of
  42. // Ceres, so it is possible to take this file alone and put it in another
  43. // project without the rest of Ceres.
  44. //
  45. // Algorithm based off of:
  46. //
  47. // [1] K. Madsen, H. Nielsen, O. Tingleoff.
  48. // Methods for Non-linear Least Squares Problems.
  49. // http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3215/pdf/imm3215.pdf
  50. #ifndef CERES_PUBLIC_TINY_SOLVER_H_
  51. #define CERES_PUBLIC_TINY_SOLVER_H_
  52. #include <cassert>
  53. #include <cmath>
  54. #include "Eigen/Dense"
  55. namespace ceres {
  56. // To use tiny solver, create a class or struct that allows computing the cost
  57. // function (described below). This is similar to a ceres::CostFunction, but is
  58. // different to enable statically allocating all memory for the solver
  59. // (specifically, enum sizes). Key parts are the Scalar typedef, the enums to
  60. // describe problem sizes (needed to remove all heap allocations), and the
  61. // operator() overload to evaluate the cost and (optionally) jacobians.
  62. //
  63. // struct TinySolverCostFunctionTraits {
  64. // typedef double Scalar;
  65. // enum {
  66. // NUM_RESIDUALS = <int> OR Eigen::Dynamic,
  67. // NUM_PARAMETERS = <int> OR Eigen::Dynamic,
  68. // };
  69. // bool operator()(const double* parameters,
  70. // double* residuals,
  71. // double* jacobian) const;
  72. //
  73. // int NumResiduals() const; -- Needed if NUM_RESIDUALS == Eigen::Dynamic.
  74. // int NumParameters() const; -- Needed if NUM_PARAMETERS == Eigen::Dynamic.
  75. // };
  76. //
  77. // For operator(), the size of the objects is:
  78. //
  79. // double* parameters -- NUM_PARAMETERS or NumParameters()
  80. // double* residuals -- NUM_RESIDUALS or NumResiduals()
  81. // double* jacobian -- NUM_RESIDUALS * NUM_PARAMETERS in column-major format
  82. // (Eigen's default); or nullptr if no jacobian
  83. // requested.
  84. //
  85. // An example (fully statically sized):
  86. //
  87. // struct MyCostFunctionExample {
  88. // typedef double Scalar;
  89. // enum {
  90. // NUM_RESIDUALS = 2,
  91. // NUM_PARAMETERS = 3,
  92. // };
  93. // bool operator()(const double* parameters,
  94. // double* residuals,
  95. // double* jacobian) const {
  96. // residuals[0] = x + 2*y + 4*z;
  97. // residuals[1] = y * z;
  98. // if (jacobian) {
  99. // jacobian[0 * 2 + 0] = 1; // First column (x).
  100. // jacobian[0 * 2 + 1] = 0;
  101. //
  102. // jacobian[1 * 2 + 0] = 2; // Second column (y).
  103. // jacobian[1 * 2 + 1] = z;
  104. //
  105. // jacobian[2 * 2 + 0] = 4; // Third column (z).
  106. // jacobian[2 * 2 + 1] = y;
  107. // }
  108. // return true;
  109. // }
  110. // };
  111. //
  112. // The solver supports either statically or dynamically sized cost
  113. // functions. If the number of residuals is dynamic then the Function
  114. // must define:
  115. //
  116. // int NumResiduals() const;
  117. //
  118. // If the number of parameters is dynamic then the Function must
  119. // define:
  120. //
  121. // int NumParameters() const;
  122. //
  123. template <typename Function,
  124. typename LinearSolver =
  125. Eigen::LDLT<Eigen::Matrix<typename Function::Scalar, //
  126. Function::NUM_PARAMETERS, //
  127. Function::NUM_PARAMETERS>>>
  128. class TinySolver {
  129. public:
  130. // This class needs to have an Eigen aligned operator new as it contains
  131. // fixed-size Eigen types.
  132. EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  133. enum {
  134. NUM_RESIDUALS = Function::NUM_RESIDUALS,
  135. NUM_PARAMETERS = Function::NUM_PARAMETERS
  136. };
  137. using Scalar = typename Function::Scalar;
  138. using Parameters = typename Eigen::Matrix<Scalar, NUM_PARAMETERS, 1>;
  139. enum Status {
  140. // max_norm |J'(x) * f(x)| < gradient_tolerance
  141. GRADIENT_TOO_SMALL,
  142. // ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance)
  143. RELATIVE_STEP_SIZE_TOO_SMALL,
  144. // cost_threshold > ||f(x)||^2 / 2
  145. COST_TOO_SMALL,
  146. // num_iterations >= max_num_iterations
  147. HIT_MAX_ITERATIONS,
  148. // (new_cost - old_cost) < function_tolerance * old_cost
  149. COST_CHANGE_TOO_SMALL,
  150. // TODO(sameeragarwal): Deal with numerical failures.
  151. };
  152. struct Options {
  153. int max_num_iterations = 50;
  154. // max_norm |J'(x) * f(x)| < gradient_tolerance
  155. Scalar gradient_tolerance = 1e-10;
  156. // ||dx|| <= parameter_tolerance * (||x|| + parameter_tolerance)
  157. Scalar parameter_tolerance = 1e-8;
  158. // (new_cost - old_cost) < function_tolerance * old_cost
  159. Scalar function_tolerance = 1e-6;
  160. // cost_threshold > ||f(x)||^2 / 2
  161. Scalar cost_threshold = std::numeric_limits<Scalar>::epsilon();
  162. Scalar initial_trust_region_radius = 1e4;
  163. };
  164. struct Summary {
  165. // 1/2 ||f(x_0)||^2
  166. Scalar initial_cost = -1;
  167. // 1/2 ||f(x)||^2
  168. Scalar final_cost = -1;
  169. // max_norm(J'f(x))
  170. Scalar gradient_max_norm = -1;
  171. int iterations = -1;
  172. Status status = HIT_MAX_ITERATIONS;
  173. };
  174. bool Update(const Function& function, const Parameters& x) {
  175. if (!function(x.data(), residuals_.data(), jacobian_.data())) {
  176. return false;
  177. }
  178. residuals_ = -residuals_;
  179. // On the first iteration, compute a diagonal (Jacobi) scaling
  180. // matrix, which we store as a vector.
  181. if (summary.iterations == 0) {
  182. // jacobi_scaling = 1 / (1 + diagonal(J'J))
  183. //
  184. // 1 is added to the denominator to regularize small diagonal
  185. // entries.
  186. jacobi_scaling_ = 1.0 / (1.0 + jacobian_.colwise().norm().array());
  187. }
  188. // This explicitly computes the normal equations, which is numerically
  189. // unstable. Nevertheless, it is often good enough and is fast.
  190. //
  191. // TODO(sameeragarwal): Refactor this to allow for DenseQR
  192. // factorization.
  193. jacobian_ = jacobian_ * jacobi_scaling_.asDiagonal();
  194. jtj_ = jacobian_.transpose() * jacobian_;
  195. g_ = jacobian_.transpose() * residuals_;
  196. summary.gradient_max_norm = g_.array().abs().maxCoeff();
  197. cost_ = residuals_.squaredNorm() / 2;
  198. return true;
  199. }
  200. const Summary& Solve(const Function& function, Parameters* x_and_min) {
  201. Initialize<NUM_RESIDUALS, NUM_PARAMETERS>(function);
  202. assert(x_and_min);
  203. Parameters& x = *x_and_min;
  204. summary = Summary();
  205. summary.iterations = 0;
  206. // TODO(sameeragarwal): Deal with failure here.
  207. Update(function, x);
  208. summary.initial_cost = cost_;
  209. summary.final_cost = cost_;
  210. if (summary.gradient_max_norm < options.gradient_tolerance) {
  211. summary.status = GRADIENT_TOO_SMALL;
  212. return summary;
  213. }
  214. if (cost_ < options.cost_threshold) {
  215. summary.status = COST_TOO_SMALL;
  216. return summary;
  217. }
  218. Scalar u = 1.0 / options.initial_trust_region_radius;
  219. Scalar v = 2;
  220. for (summary.iterations = 1;
  221. summary.iterations < options.max_num_iterations;
  222. summary.iterations++) {
  223. jtj_regularized_ = jtj_;
  224. const Scalar min_diagonal = 1e-6;
  225. const Scalar max_diagonal = 1e32;
  226. for (int i = 0; i < dx_.rows(); ++i) {
  227. jtj_regularized_(i, i) +=
  228. u * (std::min)((std::max)(jtj_(i, i), min_diagonal), max_diagonal);
  229. }
  230. // TODO(sameeragarwal): Check for failure and deal with it.
  231. linear_solver_.compute(jtj_regularized_);
  232. lm_step_ = linear_solver_.solve(g_);
  233. dx_ = jacobi_scaling_.asDiagonal() * lm_step_;
  234. // Adding parameter_tolerance to x.norm() ensures that this
  235. // works if x is near zero.
  236. const Scalar parameter_tolerance =
  237. options.parameter_tolerance *
  238. (x.norm() + options.parameter_tolerance);
  239. if (dx_.norm() < parameter_tolerance) {
  240. summary.status = RELATIVE_STEP_SIZE_TOO_SMALL;
  241. break;
  242. }
  243. x_new_ = x + dx_;
  244. // TODO(keir): Add proper handling of errors from user eval of cost
  245. // functions.
  246. function(&x_new_[0], &f_x_new_[0], nullptr);
  247. const Scalar cost_change = (2 * cost_ - f_x_new_.squaredNorm());
  248. // TODO(sameeragarwal): Better more numerically stable evaluation.
  249. const Scalar model_cost_change = lm_step_.dot(2 * g_ - jtj_ * lm_step_);
  250. // rho is the ratio of the actual reduction in error to the reduction
  251. // in error that would be obtained if the problem was linear. See [1]
  252. // for details.
  253. Scalar rho(cost_change / model_cost_change);
  254. if (rho > 0) {
  255. // Accept the Levenberg-Marquardt step because the linear
  256. // model fits well.
  257. x = x_new_;
  258. if (std::abs(cost_change) < options.function_tolerance) {
  259. cost_ = f_x_new_.squaredNorm() / 2;
  260. summary.status = COST_CHANGE_TOO_SMALL;
  261. break;
  262. }
  263. // TODO(sameeragarwal): Deal with failure.
  264. Update(function, x);
  265. if (summary.gradient_max_norm < options.gradient_tolerance) {
  266. summary.status = GRADIENT_TOO_SMALL;
  267. break;
  268. }
  269. if (cost_ < options.cost_threshold) {
  270. summary.status = COST_TOO_SMALL;
  271. break;
  272. }
  273. Scalar tmp = Scalar(2 * rho - 1);
  274. u = u * (std::max)(Scalar(1 / 3.), Scalar(1) - tmp * tmp * tmp);
  275. v = 2;
  276. } else {
  277. // Reject the update because either the normal equations failed to solve
  278. // or the local linear model was not good (rho < 0).
  279. // Additionally if the cost change is too small, then terminate.
  280. if (std::abs(cost_change) < options.function_tolerance) {
  281. // Terminate
  282. summary.status = COST_CHANGE_TOO_SMALL;
  283. break;
  284. }
  285. // Reduce the size of the trust region.
  286. u *= v;
  287. v *= 2;
  288. }
  289. }
  290. summary.final_cost = cost_;
  291. return summary;
  292. }
  293. Options options;
  294. Summary summary;
  295. private:
  296. // Preallocate everything, including temporary storage needed for solving the
  297. // linear system. This allows reusing the intermediate storage across solves.
  298. LinearSolver linear_solver_;
  299. Scalar cost_;
  300. Parameters dx_, x_new_, g_, jacobi_scaling_, lm_step_;
  301. Eigen::Matrix<Scalar, NUM_RESIDUALS, 1> residuals_, f_x_new_;
  302. Eigen::Matrix<Scalar, NUM_RESIDUALS, NUM_PARAMETERS> jacobian_;
  303. Eigen::Matrix<Scalar, NUM_PARAMETERS, NUM_PARAMETERS> jtj_, jtj_regularized_;
  304. // The following definitions are needed for template metaprogramming.
  305. template <bool Condition, typename T>
  306. struct enable_if;
  307. template <typename T>
  308. struct enable_if<true, T> {
  309. using type = T;
  310. };
  311. // The number of parameters and residuals are dynamically sized.
  312. template <int R, int P>
  313. typename enable_if<(R == Eigen::Dynamic && P == Eigen::Dynamic), void>::type
  314. Initialize(const Function& function) {
  315. Initialize(function.NumResiduals(), function.NumParameters());
  316. }
  317. // The number of parameters is dynamically sized and the number of
  318. // residuals is statically sized.
  319. template <int R, int P>
  320. typename enable_if<(R == Eigen::Dynamic && P != Eigen::Dynamic), void>::type
  321. Initialize(const Function& function) {
  322. Initialize(function.NumResiduals(), P);
  323. }
  324. // The number of parameters is statically sized and the number of
  325. // residuals is dynamically sized.
  326. template <int R, int P>
  327. typename enable_if<(R != Eigen::Dynamic && P == Eigen::Dynamic), void>::type
  328. Initialize(const Function& function) {
  329. Initialize(R, function.NumParameters());
  330. }
  331. // The number of parameters and residuals are statically sized.
  332. template <int R, int P>
  333. typename enable_if<(R != Eigen::Dynamic && P != Eigen::Dynamic), void>::type
  334. Initialize(const Function& /* function */) {}
  335. void Initialize(int num_residuals, int num_parameters) {
  336. dx_.resize(num_parameters);
  337. x_new_.resize(num_parameters);
  338. g_.resize(num_parameters);
  339. jacobi_scaling_.resize(num_parameters);
  340. lm_step_.resize(num_parameters);
  341. residuals_.resize(num_residuals);
  342. f_x_new_.resize(num_residuals);
  343. jacobian_.resize(num_residuals, num_parameters);
  344. jtj_.resize(num_parameters, num_parameters);
  345. jtj_regularized_.resize(num_parameters, num_parameters);
  346. }
  347. };
  348. } // namespace ceres
  349. #endif // CERES_PUBLIC_TINY_SOLVER_H_