reorder_program_test.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2015 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: sameeragarwal@google.com (Sameer Agarwal)
  30. #include "ceres/reorder_program.h"
  31. #include <random>
  32. #include <vector>
  33. #include "ceres/internal/config.h"
  34. #include "ceres/parameter_block.h"
  35. #include "ceres/problem_impl.h"
  36. #include "ceres/program.h"
  37. #include "ceres/sized_cost_function.h"
  38. #include "ceres/solver.h"
  39. #include "gmock/gmock.h"
  40. #include "gtest/gtest.h"
  41. namespace ceres {
  42. namespace internal {
  43. // Templated base class for the CostFunction signatures.
  44. template <int kNumResiduals, int... Ns>
  45. class MockCostFunctionBase : public SizedCostFunction<kNumResiduals, Ns...> {
  46. public:
  47. bool Evaluate(double const* const* parameters,
  48. double* residuals,
  49. double** jacobians) const final {
  50. // Do nothing. This is never called.
  51. return true;
  52. }
  53. };
  54. class UnaryCostFunction : public MockCostFunctionBase<2, 1> {};
  55. class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1> {};
  56. class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
  57. TEST(_, ReorderResidualBlockNormalFunction) {
  58. ProblemImpl problem;
  59. double x;
  60. double y;
  61. double z;
  62. problem.AddParameterBlock(&x, 1);
  63. problem.AddParameterBlock(&y, 1);
  64. problem.AddParameterBlock(&z, 1);
  65. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
  66. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &x);
  67. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &y);
  68. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &z);
  69. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
  70. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &y);
  71. auto linear_solver_ordering = std::make_shared<ParameterBlockOrdering>();
  72. linear_solver_ordering->AddElementToGroup(&x, 0);
  73. linear_solver_ordering->AddElementToGroup(&y, 0);
  74. linear_solver_ordering->AddElementToGroup(&z, 1);
  75. Solver::Options options;
  76. options.linear_solver_type = DENSE_SCHUR;
  77. options.linear_solver_ordering = linear_solver_ordering;
  78. const std::vector<ResidualBlock*>& residual_blocks =
  79. problem.program().residual_blocks();
  80. std::vector<ResidualBlock*> expected_residual_blocks;
  81. // This is a bit fragile, but it serves the purpose. We know the
  82. // bucketing algorithm that the reordering function uses, so we
  83. // expect the order for residual blocks for each e_block to be
  84. // filled in reverse.
  85. expected_residual_blocks.push_back(residual_blocks[4]);
  86. expected_residual_blocks.push_back(residual_blocks[1]);
  87. expected_residual_blocks.push_back(residual_blocks[0]);
  88. expected_residual_blocks.push_back(residual_blocks[5]);
  89. expected_residual_blocks.push_back(residual_blocks[2]);
  90. expected_residual_blocks.push_back(residual_blocks[3]);
  91. Program* program = problem.mutable_program();
  92. program->SetParameterOffsetsAndIndex();
  93. std::string message;
  94. EXPECT_TRUE(LexicographicallyOrderResidualBlocks(
  95. 2, problem.mutable_program(), &message));
  96. EXPECT_EQ(residual_blocks.size(), expected_residual_blocks.size());
  97. for (int i = 0; i < expected_residual_blocks.size(); ++i) {
  98. EXPECT_EQ(residual_blocks[i], expected_residual_blocks[i]);
  99. }
  100. }
  101. TEST(_, ApplyOrderingOrderingTooSmall) {
  102. ProblemImpl problem;
  103. double x;
  104. double y;
  105. double z;
  106. problem.AddParameterBlock(&x, 1);
  107. problem.AddParameterBlock(&y, 1);
  108. problem.AddParameterBlock(&z, 1);
  109. ParameterBlockOrdering linear_solver_ordering;
  110. linear_solver_ordering.AddElementToGroup(&x, 0);
  111. linear_solver_ordering.AddElementToGroup(&y, 1);
  112. Program program(problem.program());
  113. std::string message;
  114. EXPECT_FALSE(ApplyOrdering(
  115. problem.parameter_map(), linear_solver_ordering, &program, &message));
  116. }
  117. TEST(_, ApplyOrderingNormal) {
  118. ProblemImpl problem;
  119. double x;
  120. double y;
  121. double z;
  122. problem.AddParameterBlock(&x, 1);
  123. problem.AddParameterBlock(&y, 1);
  124. problem.AddParameterBlock(&z, 1);
  125. ParameterBlockOrdering linear_solver_ordering;
  126. linear_solver_ordering.AddElementToGroup(&x, 0);
  127. linear_solver_ordering.AddElementToGroup(&y, 2);
  128. linear_solver_ordering.AddElementToGroup(&z, 1);
  129. Program* program = problem.mutable_program();
  130. std::string message;
  131. EXPECT_TRUE(ApplyOrdering(
  132. problem.parameter_map(), linear_solver_ordering, program, &message));
  133. const std::vector<ParameterBlock*>& parameter_blocks =
  134. program->parameter_blocks();
  135. EXPECT_EQ(parameter_blocks.size(), 3);
  136. EXPECT_EQ(parameter_blocks[0]->user_state(), &x);
  137. EXPECT_EQ(parameter_blocks[1]->user_state(), &z);
  138. EXPECT_EQ(parameter_blocks[2]->user_state(), &y);
  139. }
  140. #ifndef CERES_NO_SUITESPARSE
  141. class ReorderProgramForSparseCholeskyUsingSuiteSparseTest
  142. : public ::testing::Test {
  143. protected:
  144. void SetUp() override {
  145. problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &x_);
  146. problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &z_, &x_);
  147. problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &z_, &y_);
  148. problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &z_);
  149. problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &x_, &y_);
  150. problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &y_);
  151. }
  152. void ComputeAndValidateOrdering(
  153. const ParameterBlockOrdering& linear_solver_ordering) {
  154. Program* program = problem_.mutable_program();
  155. std::vector<ParameterBlock*> unordered_parameter_blocks =
  156. program->parameter_blocks();
  157. std::string error;
  158. EXPECT_TRUE(ReorderProgramForSparseCholesky(ceres::SUITE_SPARSE,
  159. ceres::AMD,
  160. linear_solver_ordering,
  161. 0, /* use all rows */
  162. program,
  163. &error));
  164. const std::vector<ParameterBlock*>& ordered_parameter_blocks =
  165. program->parameter_blocks();
  166. EXPECT_EQ(ordered_parameter_blocks.size(),
  167. unordered_parameter_blocks.size());
  168. EXPECT_THAT(unordered_parameter_blocks,
  169. ::testing::UnorderedElementsAreArray(ordered_parameter_blocks));
  170. }
  171. ProblemImpl problem_;
  172. double x_;
  173. double y_;
  174. double z_;
  175. };
  176. TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest,
  177. EverythingInGroupZero) {
  178. ParameterBlockOrdering linear_solver_ordering;
  179. linear_solver_ordering.AddElementToGroup(&x_, 0);
  180. linear_solver_ordering.AddElementToGroup(&y_, 0);
  181. linear_solver_ordering.AddElementToGroup(&z_, 0);
  182. ComputeAndValidateOrdering(linear_solver_ordering);
  183. }
  184. TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest, ContiguousGroups) {
  185. ParameterBlockOrdering linear_solver_ordering;
  186. linear_solver_ordering.AddElementToGroup(&x_, 0);
  187. linear_solver_ordering.AddElementToGroup(&y_, 1);
  188. linear_solver_ordering.AddElementToGroup(&z_, 2);
  189. ComputeAndValidateOrdering(linear_solver_ordering);
  190. }
  191. TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest, GroupsWithGaps) {
  192. ParameterBlockOrdering linear_solver_ordering;
  193. linear_solver_ordering.AddElementToGroup(&x_, 0);
  194. linear_solver_ordering.AddElementToGroup(&y_, 2);
  195. linear_solver_ordering.AddElementToGroup(&z_, 2);
  196. ComputeAndValidateOrdering(linear_solver_ordering);
  197. }
  198. TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest,
  199. NonContiguousStartingAtTwo) {
  200. ParameterBlockOrdering linear_solver_ordering;
  201. linear_solver_ordering.AddElementToGroup(&x_, 2);
  202. linear_solver_ordering.AddElementToGroup(&y_, 4);
  203. linear_solver_ordering.AddElementToGroup(&z_, 4);
  204. ComputeAndValidateOrdering(linear_solver_ordering);
  205. }
  206. #endif // CERES_NO_SUITESPARSE
  207. TEST(_, ReorderResidualBlocksbyPartition) {
  208. ProblemImpl problem;
  209. double x;
  210. double y;
  211. double z;
  212. problem.AddParameterBlock(&x, 1);
  213. problem.AddParameterBlock(&y, 1);
  214. problem.AddParameterBlock(&z, 1);
  215. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
  216. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &x);
  217. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &y);
  218. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &z);
  219. problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
  220. problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &y);
  221. std::vector<ResidualBlockId> residual_block_ids;
  222. problem.GetResidualBlocks(&residual_block_ids);
  223. std::vector<ResidualBlock*> residual_blocks =
  224. problem.program().residual_blocks();
  225. auto rng = std::mt19937{};
  226. for (int i = 1; i < 6; ++i) {
  227. std::shuffle(
  228. std::begin(residual_block_ids), std::end(residual_block_ids), rng);
  229. std::unordered_set<ResidualBlockId> bottom(residual_block_ids.begin(),
  230. residual_block_ids.begin() + i);
  231. const int start_bottom =
  232. ReorderResidualBlocksByPartition(bottom, problem.mutable_program());
  233. std::vector<ResidualBlock*> actual_residual_blocks =
  234. problem.program().residual_blocks();
  235. EXPECT_THAT(actual_residual_blocks,
  236. testing::UnorderedElementsAreArray(residual_blocks));
  237. EXPECT_EQ(start_bottom, residual_blocks.size() - i);
  238. for (int j = start_bottom; j < residual_blocks.size(); ++j) {
  239. EXPECT_THAT(bottom, ::testing::Contains(actual_residual_blocks[j]));
  240. }
  241. }
  242. }
  243. } // namespace internal
  244. } // namespace ceres