1 // Ceres Solver - A fast non-linear least squares minimizer
2 // Copyright 2015 Google Inc. All rights reserved.
3 // http://ceres-solver.org/
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
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.
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.
29 // Author: sameeragarwal@google.com (Sameer Agarwal)
31 #include "ceres/trust_region_preprocessor.h"
35 #include "ceres/callbacks.h"
36 #include "ceres/evaluator.h"
37 #include "ceres/linear_solver.h"
38 #include "ceres/minimizer.h"
39 #include "ceres/parameter_block.h"
40 #include "ceres/preconditioner.h"
41 #include "ceres/preprocessor.h"
42 #include "ceres/problem_impl.h"
43 #include "ceres/program.h"
44 #include "ceres/reorder_program.h"
45 #include "ceres/suitesparse.h"
46 #include "ceres/trust_region_strategy.h"
47 #include "ceres/wall_time.h"
56 ParameterBlockOrdering* CreateDefaultLinearSolverOrdering(
57 const Program& program) {
58 ParameterBlockOrdering* ordering = new ParameterBlockOrdering;
59 const vector<ParameterBlock*>& parameter_blocks =
60 program.parameter_blocks();
61 for (int i = 0; i < parameter_blocks.size(); ++i) {
62 ordering->AddElementToGroup(
63 const_cast<double*>(parameter_blocks[i]->user_state()), 0);
68 // Check if all the user supplied values in the parameter blocks are
69 // sane or not, and if the program is feasible or not.
70 bool IsProgramValid(const Program& program, std::string* error) {
71 return (program.ParameterBlocksAreFinite(error) &&
72 program.IsFeasible(error));
75 void AlternateLinearSolverAndPreconditionerForSchurTypeLinearSolver(
76 Solver::Options* options) {
77 if (!IsSchurType(options->linear_solver_type)) {
81 const LinearSolverType linear_solver_type_given = options->linear_solver_type;
82 const PreconditionerType preconditioner_type_given =
83 options->preconditioner_type;
84 options->linear_solver_type = LinearSolver::LinearSolverForZeroEBlocks(
85 linear_solver_type_given);
88 if (linear_solver_type_given == ITERATIVE_SCHUR) {
89 options->preconditioner_type = Preconditioner::PreconditionerForZeroEBlocks(
90 preconditioner_type_given);
94 "No E blocks. Switching from %s(%s) to %s(%s).",
95 LinearSolverTypeToString(linear_solver_type_given),
96 PreconditionerTypeToString(preconditioner_type_given),
97 LinearSolverTypeToString(options->linear_solver_type),
98 PreconditionerTypeToString(options->preconditioner_type));
102 "No E blocks. Switching from %s to %s.",
103 LinearSolverTypeToString(linear_solver_type_given),
104 LinearSolverTypeToString(options->linear_solver_type));
107 VLOG_IF(1, options->logging_type != SILENT) << message;
110 // For Schur type and SPARSE_NORMAL_CHOLESKY linear solvers, reorder
111 // the program to reduce fill-in and increase cache coherency.
112 bool ReorderProgram(PreprocessedProblem* pp) {
113 Solver::Options& options = pp->options;
114 if (IsSchurType(options.linear_solver_type)) {
115 return ReorderProgramForSchurTypeLinearSolver(
116 options.linear_solver_type,
117 options.sparse_linear_algebra_library_type,
118 pp->problem->parameter_map(),
119 options.linear_solver_ordering.get(),
120 pp->reduced_program.get(),
125 if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY &&
126 !options.dynamic_sparsity) {
127 return ReorderProgramForSparseNormalCholesky(
128 options.sparse_linear_algebra_library_type,
129 *options.linear_solver_ordering,
130 pp->reduced_program.get(),
137 // Configure and create a linear solver object. In doing so, if a
138 // sparse direct factorization based linear solver is being used, then
139 // find a fill reducing ordering and reorder the program as needed
141 bool SetupLinearSolver(PreprocessedProblem* pp) {
142 Solver::Options& options = pp->options;
143 if (options.linear_solver_ordering.get() == NULL) {
144 // If the user has not supplied a linear solver ordering, then we
145 // assume that they are giving all the freedom to us in choosing
146 // the best possible ordering. This intent can be indicated by
147 // putting all the parameter blocks in the same elimination group.
148 options.linear_solver_ordering.reset(
149 CreateDefaultLinearSolverOrdering(*pp->reduced_program));
151 // If the user supplied an ordering, then check if the first
152 // elimination group is still non-empty after the reduced problem
153 // has been constructed.
155 // This is important for Schur type linear solvers, where the
156 // first elimination group is special -- it needs to be an
159 // If the first elimination group is empty, then we cannot use the
160 // user's requested linear solver (and a preconditioner as the
161 // case may be) so we must use a different one.
162 ParameterBlockOrdering* ordering = options.linear_solver_ordering.get();
163 const int min_group_id = ordering->MinNonZeroGroup();
164 ordering->Remove(pp->removed_parameter_blocks);
165 if (IsSchurType(options.linear_solver_type) &&
166 min_group_id != ordering->MinNonZeroGroup()) {
167 AlternateLinearSolverAndPreconditionerForSchurTypeLinearSolver(
172 // Reorder the program to reduce fill in and improve cache coherency
174 if (!ReorderProgram(pp)) {
178 // Configure the linear solver.
179 pp->linear_solver_options = LinearSolver::Options();
180 pp->linear_solver_options.min_num_iterations =
181 options.min_linear_solver_iterations;
182 pp->linear_solver_options.max_num_iterations =
183 options.max_linear_solver_iterations;
184 pp->linear_solver_options.type = options.linear_solver_type;
185 pp->linear_solver_options.preconditioner_type = options.preconditioner_type;
186 pp->linear_solver_options.visibility_clustering_type =
187 options.visibility_clustering_type;
188 pp->linear_solver_options.sparse_linear_algebra_library_type =
189 options.sparse_linear_algebra_library_type;
190 pp->linear_solver_options.dense_linear_algebra_library_type =
191 options.dense_linear_algebra_library_type;
192 pp->linear_solver_options.use_explicit_schur_complement =
193 options.use_explicit_schur_complement;
194 pp->linear_solver_options.dynamic_sparsity = options.dynamic_sparsity;
195 pp->linear_solver_options.num_threads = options.num_linear_solver_threads;
196 pp->linear_solver_options.use_postordering = options.use_postordering;
198 if (IsSchurType(pp->linear_solver_options.type)) {
199 OrderingToGroupSizes(options.linear_solver_ordering.get(),
200 &pp->linear_solver_options.elimination_groups);
202 // Schur type solvers expect at least two elimination groups. If
203 // there is only one elimination group, then it is guaranteed that
204 // this group only contains e_blocks. Thus we add a dummy
205 // elimination group with zero blocks in it.
206 if (pp->linear_solver_options.elimination_groups.size() == 1) {
207 pp->linear_solver_options.elimination_groups.push_back(0);
210 if (options.linear_solver_type == SPARSE_SCHUR) {
211 // When using SPARSE_SCHUR, we ignore the user's postordering
212 // preferences in certain cases.
214 // 1. SUITE_SPARSE is the sparse linear algebra library requested
215 // but cholmod_camd is not available.
216 // 2. CX_SPARSE is the sparse linear algebra library requested.
218 // This ensures that the linear solver does not assume that a
219 // fill-reducing pre-ordering has been done.
221 // TODO(sameeragarwal): Implement the reordering of parameter
222 // blocks for CX_SPARSE.
223 if ((options.sparse_linear_algebra_library_type == SUITE_SPARSE &&
225 IsConstrainedApproximateMinimumDegreeOrderingAvailable()) ||
226 (options.sparse_linear_algebra_library_type == CX_SPARSE)) {
227 pp->linear_solver_options.use_postordering = true;
232 pp->linear_solver.reset(LinearSolver::Create(pp->linear_solver_options));
233 return (pp->linear_solver.get() != NULL);
236 // Configure and create the evaluator.
237 bool SetupEvaluator(PreprocessedProblem* pp) {
238 const Solver::Options& options = pp->options;
239 pp->evaluator_options = Evaluator::Options();
240 pp->evaluator_options.linear_solver_type = options.linear_solver_type;
241 pp->evaluator_options.num_eliminate_blocks = 0;
242 if (IsSchurType(options.linear_solver_type)) {
243 pp->evaluator_options.num_eliminate_blocks =
245 .linear_solver_ordering
246 ->group_to_elements().begin()
250 pp->evaluator_options.num_threads = options.num_threads;
251 pp->evaluator_options.dynamic_sparsity = options.dynamic_sparsity;
252 pp->evaluator.reset(Evaluator::Create(pp->evaluator_options,
253 pp->reduced_program.get(),
256 return (pp->evaluator.get() != NULL);
259 // If the user requested inner iterations, then find an inner
260 // iteration ordering as needed and configure and create a
261 // CoordinateDescentMinimizer object to perform the inner iterations.
262 bool SetupInnerIterationMinimizer(PreprocessedProblem* pp) {
263 Solver::Options& options = pp->options;
264 if (!options.use_inner_iterations) {
268 // With just one parameter block, the outer iteration of the trust
269 // region method and inner iterations are doing exactly the same
270 // thing, and thus inner iterations are not needed.
271 if (pp->reduced_program->NumParameterBlocks() == 1) {
272 LOG(WARNING) << "Reduced problem only contains one parameter block."
273 << "Disabling inner iterations.";
277 if (options.inner_iteration_ordering.get() != NULL) {
278 // If the user supplied an ordering, then remove the set of
279 // inactive parameter blocks from it
280 options.inner_iteration_ordering->Remove(pp->removed_parameter_blocks);
281 if (options.inner_iteration_ordering->NumElements() == 0) {
282 LOG(WARNING) << "No remaining elements in the inner iteration ordering.";
286 // Validate the reduced ordering.
287 if (!CoordinateDescentMinimizer::IsOrderingValid(
288 *pp->reduced_program,
289 *options.inner_iteration_ordering,
294 // The user did not supply an ordering, so create one.
295 options.inner_iteration_ordering.reset(
296 CoordinateDescentMinimizer::CreateOrdering(*pp->reduced_program));
299 pp->inner_iteration_minimizer.reset(new CoordinateDescentMinimizer);
300 return pp->inner_iteration_minimizer->Init(*pp->reduced_program,
301 pp->problem->parameter_map(),
302 *options.inner_iteration_ordering,
306 // Configure and create a TrustRegionMinimizer object.
307 void SetupMinimizerOptions(PreprocessedProblem* pp) {
308 const Solver::Options& options = pp->options;
310 SetupCommonMinimizerOptions(pp);
311 pp->minimizer_options.is_constrained =
312 pp->reduced_program->IsBoundsConstrained();
313 pp->minimizer_options.jacobian.reset(pp->evaluator->CreateJacobian());
314 pp->minimizer_options.inner_iteration_minimizer =
315 pp->inner_iteration_minimizer;
317 TrustRegionStrategy::Options strategy_options;
318 strategy_options.linear_solver = pp->linear_solver.get();
319 strategy_options.initial_radius =
320 options.initial_trust_region_radius;
321 strategy_options.max_radius = options.max_trust_region_radius;
322 strategy_options.min_lm_diagonal = options.min_lm_diagonal;
323 strategy_options.max_lm_diagonal = options.max_lm_diagonal;
324 strategy_options.trust_region_strategy_type =
325 options.trust_region_strategy_type;
326 strategy_options.dogleg_type = options.dogleg_type;
327 pp->minimizer_options.trust_region_strategy.reset(
328 CHECK_NOTNULL(TrustRegionStrategy::Create(strategy_options)));
333 TrustRegionPreprocessor::~TrustRegionPreprocessor() {
336 bool TrustRegionPreprocessor::Preprocess(const Solver::Options& options,
337 ProblemImpl* problem,
338 PreprocessedProblem* pp) {
340 pp->options = options;
341 ChangeNumThreadsIfNeeded(&pp->options);
343 pp->problem = problem;
344 Program* program = problem->mutable_program();
345 if (!IsProgramValid(*program, &pp->error)) {
349 pp->reduced_program.reset(
350 program->CreateReducedProgram(&pp->removed_parameter_blocks,
354 if (pp->reduced_program.get() == NULL) {
358 if (pp->reduced_program->NumParameterBlocks() == 0) {
359 // The reduced problem has no parameter or residual blocks. There
360 // is nothing more to do.
364 if (!SetupLinearSolver(pp) ||
365 !SetupEvaluator(pp) ||
366 !SetupInnerIterationMinimizer(pp)) {
370 SetupMinimizerOptions(pp);
374 } // namespace internal