evaluation_callback_test.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2018 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. #include "ceres/evaluation_callback.h"
  31. #include <cmath>
  32. #include <limits>
  33. #include <memory>
  34. #include <vector>
  35. #include "ceres/autodiff_cost_function.h"
  36. #include "ceres/problem.h"
  37. #include "ceres/problem_impl.h"
  38. #include "ceres/sized_cost_function.h"
  39. #include "ceres/solver.h"
  40. #include "gtest/gtest.h"
  41. namespace ceres::internal {
  42. // Use an inline hash function to avoid portability wrangling. Algorithm from
  43. // Daniel Bernstein, known as the "djb2" hash.
  44. template <typename T>
  45. uint64_t Djb2Hash(const T* data, const int size) {
  46. uint64_t hash = 5381;
  47. const auto* data_as_bytes = reinterpret_cast<const uint8_t*>(data);
  48. for (int i = 0; i < sizeof(*data) * size; ++i) {
  49. hash = hash * 33 + data_as_bytes[i];
  50. }
  51. return hash;
  52. }
  53. const double kUninitialized = 0;
  54. // Generally multiple inheritance is a terrible idea, but in this (test)
  55. // case it makes for a relatively elegant test implementation.
  56. struct WigglyBowlCostFunctionAndEvaluationCallback : SizedCostFunction<2, 2>,
  57. EvaluationCallback {
  58. explicit WigglyBowlCostFunctionAndEvaluationCallback(double* parameter)
  59. : EvaluationCallback(),
  60. user_parameter_block(parameter),
  61. prepare_num_calls(0),
  62. prepare_requested_jacobians(false),
  63. prepare_new_evaluation_point(false),
  64. prepare_parameter_hash(kUninitialized),
  65. evaluate_num_calls(0),
  66. evaluate_last_parameter_hash(kUninitialized) {}
  67. // Evaluation callback interface. This checks that all the preconditions are
  68. // met at the point that Ceres calls into it.
  69. void PrepareForEvaluation(bool evaluate_jacobians,
  70. bool new_evaluation_point) final {
  71. // At this point, the incoming parameters are implicitly pushed by Ceres
  72. // into the user parameter blocks; in contrast to in Evaluate().
  73. uint64_t incoming_parameter_hash = Djb2Hash(user_parameter_block, 2);
  74. // Check: Prepare() & Evaluate() come in pairs, in that order. Before this
  75. // call, the number of calls excluding this one should match.
  76. EXPECT_EQ(prepare_num_calls, evaluate_num_calls);
  77. // Check: new_evaluation_point indicates that the parameter has changed.
  78. if (new_evaluation_point) {
  79. // If it's a new evaluation point, then the parameter should have
  80. // changed. Technically, it's not required that it must change but
  81. // in practice it does, and that helps with testing.
  82. EXPECT_NE(evaluate_last_parameter_hash, incoming_parameter_hash);
  83. EXPECT_NE(prepare_parameter_hash, incoming_parameter_hash);
  84. } else {
  85. // If this is the same evaluation point as last time, ensure that
  86. // the parameters match both from the previous evaluate, the
  87. // previous prepare, and the current prepare.
  88. EXPECT_EQ(evaluate_last_parameter_hash, prepare_parameter_hash);
  89. EXPECT_EQ(evaluate_last_parameter_hash, incoming_parameter_hash);
  90. }
  91. // Save details for to check at the next call to Evaluate().
  92. prepare_num_calls++;
  93. prepare_requested_jacobians = evaluate_jacobians;
  94. prepare_new_evaluation_point = new_evaluation_point;
  95. prepare_parameter_hash = incoming_parameter_hash;
  96. }
  97. // Cost function interface. This checks that preconditions that were
  98. // set as part of the PrepareForEvaluation() call are met in this one.
  99. bool Evaluate(double const* const* parameters,
  100. double* residuals,
  101. double** jacobians) const final {
  102. // Cost function implementation of the "Wiggly Bowl" function:
  103. //
  104. // 1/2 * [(y - a*sin(x))^2 + x^2],
  105. //
  106. // expressed as a Ceres cost function with two residuals:
  107. //
  108. // r[0] = y - a*sin(x)
  109. // r[1] = x.
  110. //
  111. // This is harder to optimize than the Rosenbrock function because the
  112. // minimizer has to navigate a sine-shaped valley while descending the 1D
  113. // parabola formed along the y axis. Note that the "a" needs to be more
  114. // than 5 to get a strong enough wiggle effect in the cost surface to
  115. // trigger failed iterations in the optimizer.
  116. const double a = 10.0;
  117. double x = (*parameters)[0];
  118. double y = (*parameters)[1];
  119. residuals[0] = y - a * sin(x);
  120. residuals[1] = x;
  121. if (jacobians != nullptr) {
  122. (*jacobians)[2 * 0 + 0] = -a * cos(x); // df1/dx
  123. (*jacobians)[2 * 0 + 1] = 1.0; // df1/dy
  124. (*jacobians)[2 * 1 + 0] = 1.0; // df2/dx
  125. (*jacobians)[2 * 1 + 1] = 0.0; // df2/dy
  126. }
  127. uint64_t incoming_parameter_hash = Djb2Hash(*parameters, 2);
  128. // Check: PrepareForEvaluation() & Evaluate() come in pairs, in that order.
  129. EXPECT_EQ(prepare_num_calls, evaluate_num_calls + 1);
  130. // Check: if new_evaluation_point indicates that the parameter has
  131. // changed, it has changed; otherwise it is the same.
  132. if (prepare_new_evaluation_point) {
  133. EXPECT_NE(evaluate_last_parameter_hash, incoming_parameter_hash);
  134. } else {
  135. EXPECT_NE(evaluate_last_parameter_hash, kUninitialized);
  136. EXPECT_EQ(evaluate_last_parameter_hash, incoming_parameter_hash);
  137. }
  138. // Check: Parameter matches value in in parameter blocks during prepare.
  139. EXPECT_EQ(prepare_parameter_hash, incoming_parameter_hash);
  140. // Check: jacobians are requested if they were in PrepareForEvaluation().
  141. EXPECT_EQ(prepare_requested_jacobians, jacobians != nullptr);
  142. evaluate_num_calls++;
  143. evaluate_last_parameter_hash = incoming_parameter_hash;
  144. return true;
  145. }
  146. // Pointer to the parameter block associated with this cost function.
  147. // Contents should get set by Ceres before calls to PrepareForEvaluation()
  148. // and Evaluate().
  149. double* user_parameter_block;
  150. // Track state: PrepareForEvaluation().
  151. //
  152. // These track details from the PrepareForEvaluation() call (hence the
  153. // "prepare_" prefix), which are checked for consistency in Evaluate().
  154. int prepare_num_calls;
  155. bool prepare_requested_jacobians;
  156. bool prepare_new_evaluation_point;
  157. uint64_t prepare_parameter_hash;
  158. // Track state: Evaluate().
  159. //
  160. // These track details from the Evaluate() call (hence the "evaluate_"
  161. // prefix), which are then checked for consistency in the calls to
  162. // PrepareForEvaluation(). Mutable is reasonable for this case.
  163. mutable int evaluate_num_calls;
  164. mutable uint64_t evaluate_last_parameter_hash;
  165. };
  166. TEST(EvaluationCallback, WithTrustRegionMinimizer) {
  167. double parameters[2] = {50.0, 50.0};
  168. const uint64_t original_parameters_hash = Djb2Hash(parameters, 2);
  169. WigglyBowlCostFunctionAndEvaluationCallback cost_function(parameters);
  170. Problem::Options problem_options;
  171. problem_options.evaluation_callback = &cost_function;
  172. problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
  173. Problem problem(problem_options);
  174. problem.AddResidualBlock(&cost_function, nullptr, parameters);
  175. Solver::Options options;
  176. options.linear_solver_type = DENSE_QR;
  177. options.max_num_iterations = 50;
  178. // Run the solve. Checking is done inside the cost function / callback.
  179. Solver::Summary summary;
  180. Solve(options, &problem, &summary);
  181. // Ensure that this was a hard cost function (not all steps succeed).
  182. EXPECT_GT(summary.num_successful_steps, 10);
  183. EXPECT_GT(summary.num_unsuccessful_steps, 10);
  184. // Ensure PrepareForEvaluation() is called the appropriate number of times.
  185. EXPECT_EQ(
  186. cost_function.prepare_num_calls,
  187. // Unsuccessful steps are evaluated only once (no jacobians).
  188. summary.num_unsuccessful_steps +
  189. // Successful steps are evaluated twice: with and without jacobians.
  190. 2 * summary.num_successful_steps
  191. // Final iteration doesn't re-evaluate the jacobian.
  192. // Note: This may be sensitive to tweaks to the TR algorithm; if
  193. // this becomes too brittle, remove this EXPECT_EQ() entirely.
  194. - 1);
  195. // Ensure the callback calls ran a reasonable number of times.
  196. EXPECT_GT(cost_function.prepare_num_calls, 0);
  197. EXPECT_GT(cost_function.evaluate_num_calls, 0);
  198. EXPECT_EQ(cost_function.prepare_num_calls, cost_function.evaluate_num_calls);
  199. // Ensure that the parameters did actually change.
  200. EXPECT_NE(Djb2Hash(parameters, 2), original_parameters_hash);
  201. }
  202. // r = 1 - x
  203. struct LinearResidual {
  204. template <typename T>
  205. bool operator()(const T* x, T* residuals) const {
  206. residuals[0] = 1.0 - x[0];
  207. return true;
  208. }
  209. static CostFunction* Create() {
  210. return new AutoDiffCostFunction<LinearResidual, 1, 1>(new LinearResidual);
  211. };
  212. };
  213. // Increments a counter everytime PrepareForEvaluation is called.
  214. class IncrementingEvaluationCallback : public EvaluationCallback {
  215. public:
  216. void PrepareForEvaluation(bool evaluate_jacobians,
  217. bool new_evaluation_point) final {
  218. (void)evaluate_jacobians;
  219. (void)new_evaluation_point;
  220. counter_ += 1.0;
  221. }
  222. double counter() const { return counter_; }
  223. private:
  224. double counter_ = -1;
  225. };
  226. // r = IncrementingEvaluationCallback::counter - x
  227. struct EvaluationCallbackResidual {
  228. explicit EvaluationCallbackResidual(
  229. const IncrementingEvaluationCallback& callback)
  230. : callback(callback) {}
  231. template <typename T>
  232. bool operator()(const T* x, T* residuals) const {
  233. residuals[0] = callback.counter() - x[0];
  234. return true;
  235. }
  236. const IncrementingEvaluationCallback& callback;
  237. static CostFunction* Create(IncrementingEvaluationCallback& callback) {
  238. return new AutoDiffCostFunction<EvaluationCallbackResidual, 1, 1>(
  239. new EvaluationCallbackResidual(callback));
  240. };
  241. };
  242. // The following test, constructs a problem with residual blocks all
  243. // of whose parameters are constant, so they are evaluated once
  244. // outside the Minimizer to compute Solver::Summary::fixed_cost.
  245. //
  246. // The cost function for this residual block depends on the
  247. // IncrementingEvaluationCallback::counter_, by checking the value of
  248. // the fixed cost, we can check if the IncrementingEvaluationCallback
  249. // was called.
  250. TEST(EvaluationCallback, EvaluationCallbackIsCalledBeforeFixedCostIsEvaluated) {
  251. double x = 1;
  252. double y = 2;
  253. std::unique_ptr<IncrementingEvaluationCallback> callback(
  254. new IncrementingEvaluationCallback);
  255. Problem::Options problem_options;
  256. problem_options.evaluation_callback = callback.get();
  257. Problem problem(problem_options);
  258. problem.AddResidualBlock(LinearResidual::Create(), nullptr, &x);
  259. problem.AddResidualBlock(
  260. EvaluationCallbackResidual::Create(*callback), nullptr, &y);
  261. problem.SetParameterBlockConstant(&y);
  262. Solver::Options options;
  263. options.linear_solver_type = DENSE_QR;
  264. Solver::Summary summary;
  265. Solve(options, &problem, &summary);
  266. EXPECT_EQ(summary.fixed_cost, 2.0);
  267. EXPECT_EQ(summary.final_cost, summary.fixed_cost);
  268. EXPECT_GT(callback->counter(), 0);
  269. }
  270. static void WithLineSearchMinimizerImpl(
  271. LineSearchType line_search,
  272. LineSearchDirectionType line_search_direction,
  273. LineSearchInterpolationType line_search_interpolation) {
  274. double parameters[2] = {50.0, 50.0};
  275. const uint64_t original_parameters_hash = Djb2Hash(parameters, 2);
  276. WigglyBowlCostFunctionAndEvaluationCallback cost_function(parameters);
  277. Problem::Options problem_options;
  278. problem_options.evaluation_callback = &cost_function;
  279. problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
  280. Problem problem(problem_options);
  281. problem.AddResidualBlock(&cost_function, nullptr, parameters);
  282. Solver::Options options;
  283. options.linear_solver_type = DENSE_QR;
  284. options.max_num_iterations = 50;
  285. options.minimizer_type = ceres::LINE_SEARCH;
  286. options.line_search_type = line_search;
  287. options.line_search_direction_type = line_search_direction;
  288. options.line_search_interpolation_type = line_search_interpolation;
  289. // Run the solve. Checking is done inside the cost function / callback.
  290. Solver::Summary summary;
  291. Solve(options, &problem, &summary);
  292. // Ensure the callback calls ran a reasonable number of times.
  293. EXPECT_GT(summary.num_line_search_steps, 10);
  294. EXPECT_GT(cost_function.prepare_num_calls, 30);
  295. EXPECT_EQ(cost_function.prepare_num_calls, cost_function.evaluate_num_calls);
  296. // Ensure that the parameters did actually change.
  297. EXPECT_NE(Djb2Hash(parameters, 2), original_parameters_hash);
  298. }
  299. // Note: These tests omit combinations of Wolfe line search with bisection.
  300. // Due to an implementation quirk in Wolfe line search with bisection, there
  301. // are calls to re-evaluate an existing point with new_point = true. That
  302. // causes the (overly) strict tests to break, since they check the new_point
  303. // preconditions in an if-and-only-if way. Strictly speaking, if new_point =
  304. // true, the interface does not *require* that the point has changed; only that
  305. // if new_point = false, the same point is reused.
  306. //
  307. // Since the strict checking is useful to verify that there aren't missed
  308. // optimizations, omit tests of the Wolfe with bisection cases.
  309. // Wolfe with L-BFGS.
  310. TEST(EvaluationCallback, WithLineSearchMinimizerWolfeLbfgsCubic) {
  311. WithLineSearchMinimizerImpl(WOLFE, LBFGS, CUBIC);
  312. }
  313. TEST(EvaluationCallback, WithLineSearchMinimizerWolfeLbfgsQuadratic) {
  314. WithLineSearchMinimizerImpl(WOLFE, LBFGS, QUADRATIC);
  315. }
  316. // Wolfe with full BFGS.
  317. TEST(EvaluationCallback, WithLineSearchMinimizerWolfeBfgsCubic) {
  318. WithLineSearchMinimizerImpl(WOLFE, BFGS, CUBIC);
  319. }
  320. TEST(EvaluationCallback, WithLineSearchMinimizerWolfeBfgsQuadratic) {
  321. WithLineSearchMinimizerImpl(WOLFE, BFGS, QUADRATIC);
  322. }
  323. // Armijo with nonlinear conjugate gradient.
  324. TEST(EvaluationCallback, WithLineSearchMinimizerArmijoCubic) {
  325. WithLineSearchMinimizerImpl(ARMIJO, NONLINEAR_CONJUGATE_GRADIENT, CUBIC);
  326. }
  327. TEST(EvaluationCallback, WithLineSearchMinimizerArmijoBisection) {
  328. WithLineSearchMinimizerImpl(ARMIJO, NONLINEAR_CONJUGATE_GRADIENT, BISECTION);
  329. }
  330. TEST(EvaluationCallback, WithLineSearchMinimizerArmijoQuadratic) {
  331. WithLineSearchMinimizerImpl(ARMIJO, NONLINEAR_CONJUGATE_GRADIENT, QUADRATIC);
  332. }
  333. } // namespace ceres::internal