gradient_checker_test.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  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: wjr@google.com (William Rucklidge)
  30. //
  31. // This file contains tests for the GradientChecker class.
  32. #include "ceres/gradient_checker.h"
  33. #include <cmath>
  34. #include <random>
  35. #include <utility>
  36. #include <vector>
  37. #include "ceres/cost_function.h"
  38. #include "ceres/problem.h"
  39. #include "ceres/solver.h"
  40. #include "ceres/test_util.h"
  41. #include "glog/logging.h"
  42. #include "gtest/gtest.h"
  43. namespace ceres::internal {
  44. const double kTolerance = 1e-12;
  45. // We pick a (non-quadratic) function whose derivative are easy:
  46. //
  47. // f = exp(- a' x).
  48. // df = - f a.
  49. //
  50. // where 'a' is a vector of the same size as 'x'. In the block
  51. // version, they are both block vectors, of course.
  52. class GoodTestTerm : public CostFunction {
  53. public:
  54. template <class UniformRandomFunctor>
  55. GoodTestTerm(int arity, int const* dim, UniformRandomFunctor&& randu)
  56. : arity_(arity), return_value_(true) {
  57. std::uniform_real_distribution<double> distribution(-1.0, 1.0);
  58. // Make 'arity' random vectors.
  59. a_.resize(arity_);
  60. for (int j = 0; j < arity_; ++j) {
  61. a_[j].resize(dim[j]);
  62. for (int u = 0; u < dim[j]; ++u) {
  63. a_[j][u] = randu();
  64. }
  65. }
  66. for (int i = 0; i < arity_; i++) {
  67. mutable_parameter_block_sizes()->push_back(dim[i]);
  68. }
  69. set_num_residuals(1);
  70. }
  71. bool Evaluate(double const* const* parameters,
  72. double* residuals,
  73. double** jacobians) const override {
  74. if (!return_value_) {
  75. return false;
  76. }
  77. // Compute a . x.
  78. double ax = 0;
  79. for (int j = 0; j < arity_; ++j) {
  80. for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
  81. ax += a_[j][u] * parameters[j][u];
  82. }
  83. }
  84. // This is the cost, but also appears as a factor
  85. // in the derivatives.
  86. double f = *residuals = exp(-ax);
  87. // Accumulate 1st order derivatives.
  88. if (jacobians) {
  89. for (int j = 0; j < arity_; ++j) {
  90. if (jacobians[j]) {
  91. for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
  92. // See comments before class.
  93. jacobians[j][u] = -f * a_[j][u];
  94. }
  95. }
  96. }
  97. }
  98. return true;
  99. }
  100. void SetReturnValue(bool return_value) { return_value_ = return_value; }
  101. private:
  102. int arity_;
  103. bool return_value_;
  104. std::vector<std::vector<double>> a_; // our vectors.
  105. };
  106. class BadTestTerm : public CostFunction {
  107. public:
  108. template <class UniformRandomFunctor>
  109. BadTestTerm(int arity, int const* dim, UniformRandomFunctor&& randu)
  110. : arity_(arity) {
  111. // Make 'arity' random vectors.
  112. a_.resize(arity_);
  113. for (int j = 0; j < arity_; ++j) {
  114. a_[j].resize(dim[j]);
  115. for (int u = 0; u < dim[j]; ++u) {
  116. a_[j][u] = randu();
  117. }
  118. }
  119. for (int i = 0; i < arity_; i++) {
  120. mutable_parameter_block_sizes()->push_back(dim[i]);
  121. }
  122. set_num_residuals(1);
  123. }
  124. bool Evaluate(double const* const* parameters,
  125. double* residuals,
  126. double** jacobians) const override {
  127. // Compute a . x.
  128. double ax = 0;
  129. for (int j = 0; j < arity_; ++j) {
  130. for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
  131. ax += a_[j][u] * parameters[j][u];
  132. }
  133. }
  134. // This is the cost, but also appears as a factor
  135. // in the derivatives.
  136. double f = *residuals = exp(-ax);
  137. // Accumulate 1st order derivatives.
  138. if (jacobians) {
  139. for (int j = 0; j < arity_; ++j) {
  140. if (jacobians[j]) {
  141. for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
  142. // See comments before class.
  143. jacobians[j][u] = -f * a_[j][u] + kTolerance;
  144. }
  145. }
  146. }
  147. }
  148. return true;
  149. }
  150. private:
  151. int arity_;
  152. std::vector<std::vector<double>> a_; // our vectors.
  153. };
  154. static void CheckDimensions(const GradientChecker::ProbeResults& results,
  155. const std::vector<int>& parameter_sizes,
  156. const std::vector<int>& local_parameter_sizes,
  157. int residual_size) {
  158. CHECK_EQ(parameter_sizes.size(), local_parameter_sizes.size());
  159. int num_parameters = parameter_sizes.size();
  160. ASSERT_EQ(residual_size, results.residuals.size());
  161. ASSERT_EQ(num_parameters, results.local_jacobians.size());
  162. ASSERT_EQ(num_parameters, results.local_numeric_jacobians.size());
  163. ASSERT_EQ(num_parameters, results.jacobians.size());
  164. ASSERT_EQ(num_parameters, results.numeric_jacobians.size());
  165. for (int i = 0; i < num_parameters; ++i) {
  166. EXPECT_EQ(residual_size, results.local_jacobians.at(i).rows());
  167. EXPECT_EQ(local_parameter_sizes[i], results.local_jacobians.at(i).cols());
  168. EXPECT_EQ(residual_size, results.local_numeric_jacobians.at(i).rows());
  169. EXPECT_EQ(local_parameter_sizes[i],
  170. results.local_numeric_jacobians.at(i).cols());
  171. EXPECT_EQ(residual_size, results.jacobians.at(i).rows());
  172. EXPECT_EQ(parameter_sizes[i], results.jacobians.at(i).cols());
  173. EXPECT_EQ(residual_size, results.numeric_jacobians.at(i).rows());
  174. EXPECT_EQ(parameter_sizes[i], results.numeric_jacobians.at(i).cols());
  175. }
  176. }
  177. TEST(GradientChecker, SmokeTest) {
  178. // Test with 3 blocks of size 2, 3 and 4.
  179. int const num_parameters = 3;
  180. std::vector<int> parameter_sizes(3);
  181. parameter_sizes[0] = 2;
  182. parameter_sizes[1] = 3;
  183. parameter_sizes[2] = 4;
  184. // Make a random set of blocks.
  185. FixedArray<double*> parameters(num_parameters);
  186. std::mt19937 prng;
  187. std::uniform_real_distribution<double> distribution(-1.0, 1.0);
  188. auto randu = [&prng, &distribution] { return distribution(prng); };
  189. for (int j = 0; j < num_parameters; ++j) {
  190. parameters[j] = new double[parameter_sizes[j]];
  191. for (int u = 0; u < parameter_sizes[j]; ++u) {
  192. parameters[j][u] = randu();
  193. }
  194. }
  195. NumericDiffOptions numeric_diff_options;
  196. GradientChecker::ProbeResults results;
  197. // Test that Probe returns true for correct Jacobians.
  198. GoodTestTerm good_term(num_parameters, parameter_sizes.data(), randu);
  199. std::vector<const Manifold*>* manifolds = nullptr;
  200. GradientChecker good_gradient_checker(
  201. &good_term, manifolds, numeric_diff_options);
  202. EXPECT_TRUE(
  203. good_gradient_checker.Probe(parameters.data(), kTolerance, nullptr));
  204. EXPECT_TRUE(
  205. good_gradient_checker.Probe(parameters.data(), kTolerance, &results))
  206. << results.error_log;
  207. // Check that results contain sensible data.
  208. ASSERT_EQ(results.return_value, true);
  209. ASSERT_EQ(results.residuals.size(), 1);
  210. CheckDimensions(results, parameter_sizes, parameter_sizes, 1);
  211. EXPECT_GE(results.maximum_relative_error, 0.0);
  212. EXPECT_TRUE(results.error_log.empty());
  213. // Test that if the cost function return false, Probe should return false.
  214. good_term.SetReturnValue(false);
  215. EXPECT_FALSE(
  216. good_gradient_checker.Probe(parameters.data(), kTolerance, nullptr));
  217. EXPECT_FALSE(
  218. good_gradient_checker.Probe(parameters.data(), kTolerance, &results))
  219. << results.error_log;
  220. // Check that results contain sensible data.
  221. ASSERT_EQ(results.return_value, false);
  222. ASSERT_EQ(results.residuals.size(), 1);
  223. CheckDimensions(results, parameter_sizes, parameter_sizes, 1);
  224. for (int i = 0; i < num_parameters; ++i) {
  225. EXPECT_EQ(results.local_jacobians.at(i).norm(), 0);
  226. EXPECT_EQ(results.local_numeric_jacobians.at(i).norm(), 0);
  227. }
  228. EXPECT_EQ(results.maximum_relative_error, 0.0);
  229. EXPECT_FALSE(results.error_log.empty());
  230. // Test that Probe returns false for incorrect Jacobians.
  231. BadTestTerm bad_term(num_parameters, parameter_sizes.data(), randu);
  232. GradientChecker bad_gradient_checker(
  233. &bad_term, manifolds, numeric_diff_options);
  234. EXPECT_FALSE(
  235. bad_gradient_checker.Probe(parameters.data(), kTolerance, nullptr));
  236. EXPECT_FALSE(
  237. bad_gradient_checker.Probe(parameters.data(), kTolerance, &results));
  238. // Check that results contain sensible data.
  239. ASSERT_EQ(results.return_value, true);
  240. ASSERT_EQ(results.residuals.size(), 1);
  241. CheckDimensions(results, parameter_sizes, parameter_sizes, 1);
  242. EXPECT_GT(results.maximum_relative_error, kTolerance);
  243. EXPECT_FALSE(results.error_log.empty());
  244. // Setting a high threshold should make the test pass.
  245. EXPECT_TRUE(bad_gradient_checker.Probe(parameters.data(), 1.0, &results));
  246. // Check that results contain sensible data.
  247. ASSERT_EQ(results.return_value, true);
  248. ASSERT_EQ(results.residuals.size(), 1);
  249. CheckDimensions(results, parameter_sizes, parameter_sizes, 1);
  250. EXPECT_GT(results.maximum_relative_error, 0.0);
  251. EXPECT_TRUE(results.error_log.empty());
  252. for (int j = 0; j < num_parameters; j++) {
  253. delete[] parameters[j];
  254. }
  255. }
  256. /**
  257. * Helper cost function that multiplies the parameters by the given jacobians
  258. * and adds a constant offset.
  259. */
  260. class LinearCostFunction : public CostFunction {
  261. public:
  262. explicit LinearCostFunction(Vector residuals_offset)
  263. : residuals_offset_(std::move(residuals_offset)) {
  264. set_num_residuals(residuals_offset_.size());
  265. }
  266. bool Evaluate(double const* const* parameter_ptrs,
  267. double* residuals_ptr,
  268. double** residual_J_params) const final {
  269. CHECK_GE(residual_J_params_.size(), 0.0);
  270. VectorRef residuals(residuals_ptr, residual_J_params_[0].rows());
  271. residuals = residuals_offset_;
  272. for (size_t i = 0; i < residual_J_params_.size(); ++i) {
  273. const Matrix& residual_J_param = residual_J_params_[i];
  274. int parameter_size = residual_J_param.cols();
  275. ConstVectorRef param(parameter_ptrs[i], parameter_size);
  276. // Compute residual.
  277. residuals += residual_J_param * param;
  278. // Return Jacobian.
  279. if (residual_J_params != nullptr && residual_J_params[i] != nullptr) {
  280. Eigen::Map<Matrix> residual_J_param_out(residual_J_params[i],
  281. residual_J_param.rows(),
  282. residual_J_param.cols());
  283. if (jacobian_offsets_.count(i) != 0) {
  284. residual_J_param_out = residual_J_param + jacobian_offsets_.at(i);
  285. } else {
  286. residual_J_param_out = residual_J_param;
  287. }
  288. }
  289. }
  290. return true;
  291. }
  292. void AddParameter(const Matrix& residual_J_param) {
  293. CHECK_EQ(num_residuals(), residual_J_param.rows());
  294. residual_J_params_.push_back(residual_J_param);
  295. mutable_parameter_block_sizes()->push_back(residual_J_param.cols());
  296. }
  297. /// Add offset to the given Jacobian before returning it from Evaluate(),
  298. /// thus introducing an error in the computation.
  299. void SetJacobianOffset(size_t index, Matrix offset) {
  300. CHECK_LT(index, residual_J_params_.size());
  301. CHECK_EQ(residual_J_params_[index].rows(), offset.rows());
  302. CHECK_EQ(residual_J_params_[index].cols(), offset.cols());
  303. jacobian_offsets_[index] = offset;
  304. }
  305. private:
  306. std::vector<Matrix> residual_J_params_;
  307. std::map<int, Matrix> jacobian_offsets_;
  308. Vector residuals_offset_;
  309. };
  310. // Helper function to compare two Eigen matrices (used in the test below).
  311. static void ExpectMatricesClose(Matrix p, Matrix q, double tolerance) {
  312. ASSERT_EQ(p.rows(), q.rows());
  313. ASSERT_EQ(p.cols(), q.cols());
  314. ExpectArraysClose(p.size(), p.data(), q.data(), tolerance);
  315. }
  316. // Helper manifold that multiplies the delta vector by the given
  317. // jacobian and adds it to the parameter.
  318. class MatrixManifold : public Manifold {
  319. public:
  320. bool Plus(const double* x,
  321. const double* delta,
  322. double* x_plus_delta) const final {
  323. VectorRef(x_plus_delta, AmbientSize()) =
  324. ConstVectorRef(x, AmbientSize()) +
  325. (global_to_local_ * ConstVectorRef(delta, TangentSize()));
  326. return true;
  327. }
  328. bool PlusJacobian(const double* /*x*/, double* jacobian) const final {
  329. MatrixRef(jacobian, AmbientSize(), TangentSize()) = global_to_local_;
  330. return true;
  331. }
  332. bool Minus(const double* y, const double* x, double* y_minus_x) const final {
  333. LOG(FATAL) << "Should not be called";
  334. return true;
  335. }
  336. bool MinusJacobian(const double* x, double* jacobian) const final {
  337. LOG(FATAL) << "Should not be called";
  338. return true;
  339. }
  340. int AmbientSize() const final { return global_to_local_.rows(); }
  341. int TangentSize() const final { return global_to_local_.cols(); }
  342. Matrix global_to_local_;
  343. };
  344. TEST(GradientChecker, TestCorrectnessWithManifolds) {
  345. // Create cost function.
  346. Eigen::Vector3d residual_offset(100.0, 200.0, 300.0);
  347. LinearCostFunction cost_function(residual_offset);
  348. Eigen::Matrix<double, 3, 3, Eigen::RowMajor> j0;
  349. j0.row(0) << 1.0, 2.0, 3.0;
  350. j0.row(1) << 4.0, 5.0, 6.0;
  351. j0.row(2) << 7.0, 8.0, 9.0;
  352. Eigen::Matrix<double, 3, 2, Eigen::RowMajor> j1;
  353. j1.row(0) << 10.0, 11.0;
  354. j1.row(1) << 12.0, 13.0;
  355. j1.row(2) << 14.0, 15.0;
  356. Eigen::Vector3d param0(1.0, 2.0, 3.0);
  357. Eigen::Vector2d param1(4.0, 5.0);
  358. cost_function.AddParameter(j0);
  359. cost_function.AddParameter(j1);
  360. std::vector<int> parameter_sizes(2);
  361. parameter_sizes[0] = 3;
  362. parameter_sizes[1] = 2;
  363. std::vector<int> tangent_sizes(2);
  364. tangent_sizes[0] = 2;
  365. tangent_sizes[1] = 2;
  366. // Test cost function for correctness.
  367. Eigen::Matrix<double, 3, 3, Eigen::RowMajor> j1_out;
  368. Eigen::Matrix<double, 3, 2, Eigen::RowMajor> j2_out;
  369. Eigen::Vector3d residual;
  370. std::vector<const double*> parameters(2);
  371. parameters[0] = param0.data();
  372. parameters[1] = param1.data();
  373. std::vector<double*> jacobians(2);
  374. jacobians[0] = j1_out.data();
  375. jacobians[1] = j2_out.data();
  376. cost_function.Evaluate(parameters.data(), residual.data(), jacobians.data());
  377. Matrix residual_expected = residual_offset + j0 * param0 + j1 * param1;
  378. ExpectMatricesClose(j1_out, j0, std::numeric_limits<double>::epsilon());
  379. ExpectMatricesClose(j2_out, j1, std::numeric_limits<double>::epsilon());
  380. ExpectMatricesClose(residual, residual_expected, kTolerance);
  381. // Create manifold.
  382. Eigen::Matrix<double, 3, 2, Eigen::RowMajor> global_to_local;
  383. global_to_local.row(0) << 1.5, 2.5;
  384. global_to_local.row(1) << 3.5, 4.5;
  385. global_to_local.row(2) << 5.5, 6.5;
  386. MatrixManifold manifold;
  387. manifold.global_to_local_ = global_to_local;
  388. // Test manifold for correctness.
  389. Eigen::Vector3d x(7.0, 8.0, 9.0);
  390. Eigen::Vector2d delta(10.0, 11.0);
  391. Eigen::Matrix<double, 3, 2, Eigen::RowMajor> global_to_local_out;
  392. manifold.PlusJacobian(x.data(), global_to_local_out.data());
  393. ExpectMatricesClose(global_to_local_out,
  394. global_to_local,
  395. std::numeric_limits<double>::epsilon());
  396. Eigen::Vector3d x_plus_delta;
  397. manifold.Plus(x.data(), delta.data(), x_plus_delta.data());
  398. Eigen::Vector3d x_plus_delta_expected = x + (global_to_local * delta);
  399. ExpectMatricesClose(x_plus_delta, x_plus_delta_expected, kTolerance);
  400. // Now test GradientChecker.
  401. std::vector<const Manifold*> manifolds(2);
  402. manifolds[0] = &manifold;
  403. manifolds[1] = nullptr;
  404. NumericDiffOptions numeric_diff_options;
  405. GradientChecker::ProbeResults results;
  406. GradientChecker gradient_checker(
  407. &cost_function, &manifolds, numeric_diff_options);
  408. Problem::Options problem_options;
  409. problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP;
  410. problem_options.manifold_ownership = DO_NOT_TAKE_OWNERSHIP;
  411. Problem problem(problem_options);
  412. Eigen::Vector3d param0_solver;
  413. Eigen::Vector2d param1_solver;
  414. problem.AddParameterBlock(param0_solver.data(), 3, &manifold);
  415. problem.AddParameterBlock(param1_solver.data(), 2);
  416. problem.AddResidualBlock(
  417. &cost_function, nullptr, param0_solver.data(), param1_solver.data());
  418. // First test case: everything is correct.
  419. EXPECT_TRUE(gradient_checker.Probe(parameters.data(), kTolerance, nullptr));
  420. EXPECT_TRUE(gradient_checker.Probe(parameters.data(), kTolerance, &results))
  421. << results.error_log;
  422. // Check that results contain correct data.
  423. ASSERT_EQ(results.return_value, true);
  424. ExpectMatricesClose(
  425. results.residuals, residual, std::numeric_limits<double>::epsilon());
  426. CheckDimensions(results, parameter_sizes, tangent_sizes, 3);
  427. ExpectMatricesClose(
  428. results.local_jacobians.at(0), j0 * global_to_local, kTolerance);
  429. ExpectMatricesClose(results.local_jacobians.at(1),
  430. j1,
  431. std::numeric_limits<double>::epsilon());
  432. ExpectMatricesClose(
  433. results.local_numeric_jacobians.at(0), j0 * global_to_local, kTolerance);
  434. ExpectMatricesClose(results.local_numeric_jacobians.at(1), j1, kTolerance);
  435. ExpectMatricesClose(
  436. results.jacobians.at(0), j0, std::numeric_limits<double>::epsilon());
  437. ExpectMatricesClose(
  438. results.jacobians.at(1), j1, std::numeric_limits<double>::epsilon());
  439. ExpectMatricesClose(results.numeric_jacobians.at(0), j0, kTolerance);
  440. ExpectMatricesClose(results.numeric_jacobians.at(1), j1, kTolerance);
  441. EXPECT_GE(results.maximum_relative_error, 0.0);
  442. EXPECT_TRUE(results.error_log.empty());
  443. // Test interaction with the 'check_gradients' option in Solver.
  444. Solver::Options solver_options;
  445. solver_options.linear_solver_type = DENSE_QR;
  446. solver_options.check_gradients = true;
  447. solver_options.initial_trust_region_radius = 1e10;
  448. Solver solver;
  449. Solver::Summary summary;
  450. param0_solver = param0;
  451. param1_solver = param1;
  452. solver.Solve(solver_options, &problem, &summary);
  453. EXPECT_EQ(CONVERGENCE, summary.termination_type);
  454. EXPECT_LE(summary.final_cost, 1e-12);
  455. // Second test case: Mess up reported derivatives with respect to 3rd
  456. // component of 1st parameter. Check should fail.
  457. Eigen::Matrix<double, 3, 3, Eigen::RowMajor> j0_offset;
  458. j0_offset.setZero();
  459. j0_offset.col(2).setConstant(0.001);
  460. cost_function.SetJacobianOffset(0, j0_offset);
  461. EXPECT_FALSE(gradient_checker.Probe(parameters.data(), kTolerance, nullptr));
  462. EXPECT_FALSE(gradient_checker.Probe(parameters.data(), kTolerance, &results))
  463. << results.error_log;
  464. // Check that results contain correct data.
  465. ASSERT_EQ(results.return_value, true);
  466. ExpectMatricesClose(
  467. results.residuals, residual, std::numeric_limits<double>::epsilon());
  468. CheckDimensions(results, parameter_sizes, tangent_sizes, 3);
  469. ASSERT_EQ(results.local_jacobians.size(), 2);
  470. ASSERT_EQ(results.local_numeric_jacobians.size(), 2);
  471. ExpectMatricesClose(results.local_jacobians.at(0),
  472. (j0 + j0_offset) * global_to_local,
  473. kTolerance);
  474. ExpectMatricesClose(results.local_jacobians.at(1),
  475. j1,
  476. std::numeric_limits<double>::epsilon());
  477. ExpectMatricesClose(
  478. results.local_numeric_jacobians.at(0), j0 * global_to_local, kTolerance);
  479. ExpectMatricesClose(results.local_numeric_jacobians.at(1), j1, kTolerance);
  480. ExpectMatricesClose(results.jacobians.at(0), j0 + j0_offset, kTolerance);
  481. ExpectMatricesClose(
  482. results.jacobians.at(1), j1, std::numeric_limits<double>::epsilon());
  483. ExpectMatricesClose(results.numeric_jacobians.at(0), j0, kTolerance);
  484. ExpectMatricesClose(results.numeric_jacobians.at(1), j1, kTolerance);
  485. EXPECT_GT(results.maximum_relative_error, 0.0);
  486. EXPECT_FALSE(results.error_log.empty());
  487. // Test interaction with the 'check_gradients' option in Solver.
  488. param0_solver = param0;
  489. param1_solver = param1;
  490. solver.Solve(solver_options, &problem, &summary);
  491. EXPECT_EQ(FAILURE, summary.termination_type);
  492. // Now, zero out the manifold Jacobian with respect to the 3rd component of
  493. // the 1st parameter. This makes the combination of cost function and manifold
  494. // return correct values again.
  495. manifold.global_to_local_.row(2).setZero();
  496. // Verify that the gradient checker does not treat this as an error.
  497. EXPECT_TRUE(gradient_checker.Probe(parameters.data(), kTolerance, &results))
  498. << results.error_log;
  499. // Check that results contain correct data.
  500. ASSERT_EQ(results.return_value, true);
  501. ExpectMatricesClose(
  502. results.residuals, residual, std::numeric_limits<double>::epsilon());
  503. CheckDimensions(results, parameter_sizes, tangent_sizes, 3);
  504. ASSERT_EQ(results.local_jacobians.size(), 2);
  505. ASSERT_EQ(results.local_numeric_jacobians.size(), 2);
  506. ExpectMatricesClose(results.local_jacobians.at(0),
  507. (j0 + j0_offset) * manifold.global_to_local_,
  508. kTolerance);
  509. ExpectMatricesClose(results.local_jacobians.at(1),
  510. j1,
  511. std::numeric_limits<double>::epsilon());
  512. ExpectMatricesClose(results.local_numeric_jacobians.at(0),
  513. j0 * manifold.global_to_local_,
  514. kTolerance);
  515. ExpectMatricesClose(results.local_numeric_jacobians.at(1), j1, kTolerance);
  516. ExpectMatricesClose(results.jacobians.at(0), j0 + j0_offset, kTolerance);
  517. ExpectMatricesClose(
  518. results.jacobians.at(1), j1, std::numeric_limits<double>::epsilon());
  519. ExpectMatricesClose(results.numeric_jacobians.at(0), j0, kTolerance);
  520. ExpectMatricesClose(results.numeric_jacobians.at(1), j1, kTolerance);
  521. EXPECT_GE(results.maximum_relative_error, 0.0);
  522. EXPECT_TRUE(results.error_log.empty());
  523. // Test interaction with the 'check_gradients' option in Solver.
  524. param0_solver = param0;
  525. param1_solver = param1;
  526. solver.Solve(solver_options, &problem, &summary);
  527. EXPECT_EQ(CONVERGENCE, summary.termination_type);
  528. EXPECT_LE(summary.final_cost, 1e-12);
  529. }
  530. } // namespace ceres::internal