blob: e07e369a97df51d1b688e628cf0250273a70058a [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
Austin Schuh3de38b02024-06-25 18:25:10 -07002// Copyright 2023 Google Inc. All rights reserved.
Austin Schuh70cc9552019-01-21 19:46:48 -08003// 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
31#include "ceres/trust_region_preprocessor.h"
32
33#include <numeric>
34#include <string>
Austin Schuh3de38b02024-06-25 18:25:10 -070035#include <vector>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080036
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include "ceres/callbacks.h"
38#include "ceres/context_impl.h"
39#include "ceres/evaluator.h"
40#include "ceres/linear_solver.h"
41#include "ceres/minimizer.h"
42#include "ceres/parameter_block.h"
43#include "ceres/preconditioner.h"
44#include "ceres/preprocessor.h"
45#include "ceres/problem_impl.h"
46#include "ceres/program.h"
47#include "ceres/reorder_program.h"
48#include "ceres/suitesparse.h"
49#include "ceres/trust_region_strategy.h"
50#include "ceres/wall_time.h"
51
Austin Schuh3de38b02024-06-25 18:25:10 -070052namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080053
54namespace {
55
Austin Schuh3de38b02024-06-25 18:25:10 -070056std::shared_ptr<ParameterBlockOrdering> CreateDefaultLinearSolverOrdering(
Austin Schuh70cc9552019-01-21 19:46:48 -080057 const Program& program) {
Austin Schuh3de38b02024-06-25 18:25:10 -070058 std::shared_ptr<ParameterBlockOrdering> ordering =
59 std::make_shared<ParameterBlockOrdering>();
60 const std::vector<ParameterBlock*>& parameter_blocks =
61 program.parameter_blocks();
62 for (auto* parameter_block : parameter_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -080063 ordering->AddElementToGroup(
Austin Schuh3de38b02024-06-25 18:25:10 -070064 const_cast<double*>(parameter_block->user_state()), 0);
Austin Schuh70cc9552019-01-21 19:46:48 -080065 }
66 return ordering;
67}
68
69// Check if all the user supplied values in the parameter blocks are
70// sane or not, and if the program is feasible or not.
71bool IsProgramValid(const Program& program, std::string* error) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080072 return (program.ParameterBlocksAreFinite(error) && program.IsFeasible(error));
Austin Schuh70cc9552019-01-21 19:46:48 -080073}
74
75void AlternateLinearSolverAndPreconditionerForSchurTypeLinearSolver(
76 Solver::Options* options) {
77 if (!IsSchurType(options->linear_solver_type)) {
78 return;
79 }
80
81 const LinearSolverType linear_solver_type_given = options->linear_solver_type;
82 const PreconditionerType preconditioner_type_given =
83 options->preconditioner_type;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080084 options->linear_solver_type =
85 LinearSolver::LinearSolverForZeroEBlocks(linear_solver_type_given);
Austin Schuh70cc9552019-01-21 19:46:48 -080086
87 std::string message;
88 if (linear_solver_type_given == ITERATIVE_SCHUR) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080089 options->preconditioner_type =
90 Preconditioner::PreconditionerForZeroEBlocks(preconditioner_type_given);
Austin Schuh70cc9552019-01-21 19:46:48 -080091
92 message =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080093 StringPrintf("No E blocks. Switching from %s(%s) to %s(%s).",
94 LinearSolverTypeToString(linear_solver_type_given),
95 PreconditionerTypeToString(preconditioner_type_given),
96 LinearSolverTypeToString(options->linear_solver_type),
97 PreconditionerTypeToString(options->preconditioner_type));
Austin Schuh70cc9552019-01-21 19:46:48 -080098 } else {
99 message =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800100 StringPrintf("No E blocks. Switching from %s to %s.",
101 LinearSolverTypeToString(linear_solver_type_given),
102 LinearSolverTypeToString(options->linear_solver_type));
Austin Schuh70cc9552019-01-21 19:46:48 -0800103 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800104 if (options->logging_type != SILENT) {
105 VLOG(1) << message;
106 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800107}
108
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800109// Reorder the program to reduce fill-in and increase cache coherency.
Austin Schuh70cc9552019-01-21 19:46:48 -0800110bool ReorderProgram(PreprocessedProblem* pp) {
111 const Solver::Options& options = pp->options;
112 if (IsSchurType(options.linear_solver_type)) {
113 return ReorderProgramForSchurTypeLinearSolver(
114 options.linear_solver_type,
115 options.sparse_linear_algebra_library_type,
Austin Schuh3de38b02024-06-25 18:25:10 -0700116 options.linear_solver_ordering_type,
Austin Schuh70cc9552019-01-21 19:46:48 -0800117 pp->problem->parameter_map(),
118 options.linear_solver_ordering.get(),
119 pp->reduced_program.get(),
120 &pp->error);
121 }
122
Austin Schuh70cc9552019-01-21 19:46:48 -0800123 if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY &&
124 !options.dynamic_sparsity) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800125 return ReorderProgramForSparseCholesky(
Austin Schuh70cc9552019-01-21 19:46:48 -0800126 options.sparse_linear_algebra_library_type,
Austin Schuh3de38b02024-06-25 18:25:10 -0700127 options.linear_solver_ordering_type,
Austin Schuh70cc9552019-01-21 19:46:48 -0800128 *options.linear_solver_ordering,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800129 0, /* use all the rows of the jacobian */
130 pp->reduced_program.get(),
131 &pp->error);
132 }
133
134 if (options.linear_solver_type == CGNR &&
135 options.preconditioner_type == SUBSET) {
136 pp->linear_solver_options.subset_preconditioner_start_row_block =
137 ReorderResidualBlocksByPartition(
138 options.residual_blocks_for_subset_preconditioner,
139 pp->reduced_program.get());
140
141 return ReorderProgramForSparseCholesky(
142 options.sparse_linear_algebra_library_type,
Austin Schuh3de38b02024-06-25 18:25:10 -0700143 options.linear_solver_ordering_type,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800144 *options.linear_solver_ordering,
145 pp->linear_solver_options.subset_preconditioner_start_row_block,
Austin Schuh70cc9552019-01-21 19:46:48 -0800146 pp->reduced_program.get(),
147 &pp->error);
148 }
149
150 return true;
151}
152
153// Configure and create a linear solver object. In doing so, if a
154// sparse direct factorization based linear solver is being used, then
155// find a fill reducing ordering and reorder the program as needed
156// too.
157bool SetupLinearSolver(PreprocessedProblem* pp) {
158 Solver::Options& options = pp->options;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800159 pp->linear_solver_options = LinearSolver::Options();
160
161 if (!options.linear_solver_ordering) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800162 // If the user has not supplied a linear solver ordering, then we
163 // assume that they are giving all the freedom to us in choosing
164 // the best possible ordering. This intent can be indicated by
165 // putting all the parameter blocks in the same elimination group.
Austin Schuh3de38b02024-06-25 18:25:10 -0700166 options.linear_solver_ordering =
167 CreateDefaultLinearSolverOrdering(*pp->reduced_program);
Austin Schuh70cc9552019-01-21 19:46:48 -0800168 } else {
169 // If the user supplied an ordering, then check if the first
170 // elimination group is still non-empty after the reduced problem
171 // has been constructed.
172 //
173 // This is important for Schur type linear solvers, where the
174 // first elimination group is special -- it needs to be an
175 // independent set.
176 //
177 // If the first elimination group is empty, then we cannot use the
178 // user's requested linear solver (and a preconditioner as the
179 // case may be) so we must use a different one.
180 ParameterBlockOrdering* ordering = options.linear_solver_ordering.get();
181 const int min_group_id = ordering->MinNonZeroGroup();
182 ordering->Remove(pp->removed_parameter_blocks);
183 if (IsSchurType(options.linear_solver_type) &&
184 min_group_id != ordering->MinNonZeroGroup()) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800185 AlternateLinearSolverAndPreconditionerForSchurTypeLinearSolver(&options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800186 }
187 }
188
189 // Reorder the program to reduce fill in and improve cache coherency
190 // of the Jacobian.
191 if (!ReorderProgram(pp)) {
192 return false;
193 }
194
195 // Configure the linear solver.
Austin Schuh70cc9552019-01-21 19:46:48 -0800196 pp->linear_solver_options.min_num_iterations =
197 options.min_linear_solver_iterations;
198 pp->linear_solver_options.max_num_iterations =
199 options.max_linear_solver_iterations;
200 pp->linear_solver_options.type = options.linear_solver_type;
201 pp->linear_solver_options.preconditioner_type = options.preconditioner_type;
Austin Schuh3de38b02024-06-25 18:25:10 -0700202 pp->linear_solver_options.use_spse_initialization =
203 options.use_spse_initialization;
204 pp->linear_solver_options.spse_tolerance = options.spse_tolerance;
205 pp->linear_solver_options.max_num_spse_iterations =
206 options.max_num_spse_iterations;
Austin Schuh70cc9552019-01-21 19:46:48 -0800207 pp->linear_solver_options.visibility_clustering_type =
208 options.visibility_clustering_type;
209 pp->linear_solver_options.sparse_linear_algebra_library_type =
210 options.sparse_linear_algebra_library_type;
Austin Schuh3de38b02024-06-25 18:25:10 -0700211
Austin Schuh70cc9552019-01-21 19:46:48 -0800212 pp->linear_solver_options.dense_linear_algebra_library_type =
213 options.dense_linear_algebra_library_type;
214 pp->linear_solver_options.use_explicit_schur_complement =
215 options.use_explicit_schur_complement;
216 pp->linear_solver_options.dynamic_sparsity = options.dynamic_sparsity;
217 pp->linear_solver_options.use_mixed_precision_solves =
218 options.use_mixed_precision_solves;
219 pp->linear_solver_options.max_num_refinement_iterations =
220 options.max_num_refinement_iterations;
221 pp->linear_solver_options.num_threads = options.num_threads;
Austin Schuh70cc9552019-01-21 19:46:48 -0800222 pp->linear_solver_options.context = pp->problem->context();
223
224 if (IsSchurType(pp->linear_solver_options.type)) {
225 OrderingToGroupSizes(options.linear_solver_ordering.get(),
226 &pp->linear_solver_options.elimination_groups);
227
228 // Schur type solvers expect at least two elimination groups. If
229 // there is only one elimination group, then it is guaranteed that
230 // this group only contains e_blocks. Thus we add a dummy
231 // elimination group with zero blocks in it.
232 if (pp->linear_solver_options.elimination_groups.size() == 1) {
233 pp->linear_solver_options.elimination_groups.push_back(0);
234 }
Austin Schuh3de38b02024-06-25 18:25:10 -0700235 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800236
Austin Schuh3de38b02024-06-25 18:25:10 -0700237 if (!options.dynamic_sparsity &&
238 AreJacobianColumnsOrdered(options.linear_solver_type,
239 options.preconditioner_type,
240 options.sparse_linear_algebra_library_type,
241 options.linear_solver_ordering_type)) {
242 pp->linear_solver_options.ordering_type = OrderingType::NATURAL;
243 } else {
244 if (options.linear_solver_ordering_type == ceres::AMD) {
245 pp->linear_solver_options.ordering_type = OrderingType::AMD;
246 } else if (options.linear_solver_ordering_type == ceres::NESDIS) {
247 pp->linear_solver_options.ordering_type = OrderingType::NESDIS;
248 } else {
249 LOG(FATAL) << "Congratulations you have found a bug in Ceres Solver."
250 << " Please report this to the maintainers. : "
251 << options.linear_solver_ordering_type;
Austin Schuh70cc9552019-01-21 19:46:48 -0800252 }
253 }
254
Austin Schuh3de38b02024-06-25 18:25:10 -0700255 pp->linear_solver = LinearSolver::Create(pp->linear_solver_options);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800256 return (pp->linear_solver != nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -0800257}
258
259// Configure and create the evaluator.
260bool SetupEvaluator(PreprocessedProblem* pp) {
261 const Solver::Options& options = pp->options;
262 pp->evaluator_options = Evaluator::Options();
263 pp->evaluator_options.linear_solver_type = options.linear_solver_type;
Austin Schuh3de38b02024-06-25 18:25:10 -0700264 pp->evaluator_options.sparse_linear_algebra_library_type =
265 options.sparse_linear_algebra_library_type;
Austin Schuh70cc9552019-01-21 19:46:48 -0800266 pp->evaluator_options.num_eliminate_blocks = 0;
267 if (IsSchurType(options.linear_solver_type)) {
268 pp->evaluator_options.num_eliminate_blocks =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800269 options.linear_solver_ordering->group_to_elements()
270 .begin()
271 ->second.size();
Austin Schuh70cc9552019-01-21 19:46:48 -0800272 }
273
274 pp->evaluator_options.num_threads = options.num_threads;
275 pp->evaluator_options.dynamic_sparsity = options.dynamic_sparsity;
276 pp->evaluator_options.context = pp->problem->context();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800277 pp->evaluator_options.evaluation_callback =
278 pp->reduced_program->mutable_evaluation_callback();
Austin Schuh3de38b02024-06-25 18:25:10 -0700279 pp->evaluator = Evaluator::Create(
280 pp->evaluator_options, pp->reduced_program.get(), &pp->error);
Austin Schuh70cc9552019-01-21 19:46:48 -0800281
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800282 return (pp->evaluator != nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -0800283}
284
285// If the user requested inner iterations, then find an inner
286// iteration ordering as needed and configure and create a
287// CoordinateDescentMinimizer object to perform the inner iterations.
288bool SetupInnerIterationMinimizer(PreprocessedProblem* pp) {
289 Solver::Options& options = pp->options;
290 if (!options.use_inner_iterations) {
291 return true;
292 }
293
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800294 if (pp->reduced_program->mutable_evaluation_callback()) {
295 pp->error = "Inner iterations cannot be used with EvaluationCallbacks";
296 return false;
297 }
298
Austin Schuh70cc9552019-01-21 19:46:48 -0800299 // With just one parameter block, the outer iteration of the trust
300 // region method and inner iterations are doing exactly the same
301 // thing, and thus inner iterations are not needed.
302 if (pp->reduced_program->NumParameterBlocks() == 1) {
303 LOG(WARNING) << "Reduced problem only contains one parameter block."
304 << "Disabling inner iterations.";
305 return true;
306 }
307
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 if (options.inner_iteration_ordering != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800309 // If the user supplied an ordering, then remove the set of
310 // inactive parameter blocks from it
311 options.inner_iteration_ordering->Remove(pp->removed_parameter_blocks);
312 if (options.inner_iteration_ordering->NumElements() == 0) {
313 LOG(WARNING) << "No remaining elements in the inner iteration ordering.";
314 return true;
315 }
316
317 // Validate the reduced ordering.
318 if (!CoordinateDescentMinimizer::IsOrderingValid(
319 *pp->reduced_program,
320 *options.inner_iteration_ordering,
321 &pp->error)) {
322 return false;
323 }
324 } else {
325 // The user did not supply an ordering, so create one.
Austin Schuh3de38b02024-06-25 18:25:10 -0700326 options.inner_iteration_ordering =
327 CoordinateDescentMinimizer::CreateOrdering(*pp->reduced_program);
Austin Schuh70cc9552019-01-21 19:46:48 -0800328 }
329
Austin Schuh3de38b02024-06-25 18:25:10 -0700330 pp->inner_iteration_minimizer =
331 std::make_unique<CoordinateDescentMinimizer>(pp->problem->context());
Austin Schuh70cc9552019-01-21 19:46:48 -0800332 return pp->inner_iteration_minimizer->Init(*pp->reduced_program,
333 pp->problem->parameter_map(),
334 *options.inner_iteration_ordering,
335 &pp->error);
336}
337
338// Configure and create a TrustRegionMinimizer object.
Austin Schuh3de38b02024-06-25 18:25:10 -0700339bool SetupMinimizerOptions(PreprocessedProblem* pp) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800340 const Solver::Options& options = pp->options;
341
342 SetupCommonMinimizerOptions(pp);
343 pp->minimizer_options.is_constrained =
344 pp->reduced_program->IsBoundsConstrained();
Austin Schuh3de38b02024-06-25 18:25:10 -0700345 pp->minimizer_options.jacobian = pp->evaluator->CreateJacobian();
346 if (pp->minimizer_options.jacobian == nullptr) {
347 pp->error =
348 "Unable to create Jacobian matrix. Likely because it is too large.";
349 return false;
350 }
351
Austin Schuh70cc9552019-01-21 19:46:48 -0800352 pp->minimizer_options.inner_iteration_minimizer =
353 pp->inner_iteration_minimizer;
354
355 TrustRegionStrategy::Options strategy_options;
356 strategy_options.linear_solver = pp->linear_solver.get();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800357 strategy_options.initial_radius = options.initial_trust_region_radius;
Austin Schuh70cc9552019-01-21 19:46:48 -0800358 strategy_options.max_radius = options.max_trust_region_radius;
359 strategy_options.min_lm_diagonal = options.min_lm_diagonal;
360 strategy_options.max_lm_diagonal = options.max_lm_diagonal;
361 strategy_options.trust_region_strategy_type =
362 options.trust_region_strategy_type;
363 strategy_options.dogleg_type = options.dogleg_type;
Austin Schuh3de38b02024-06-25 18:25:10 -0700364 strategy_options.context = pp->problem->context();
365 strategy_options.num_threads = options.num_threads;
366 pp->minimizer_options.trust_region_strategy =
367 TrustRegionStrategy::Create(strategy_options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800368 CHECK(pp->minimizer_options.trust_region_strategy != nullptr);
Austin Schuh3de38b02024-06-25 18:25:10 -0700369 return true;
Austin Schuh70cc9552019-01-21 19:46:48 -0800370}
371
372} // namespace
373
Austin Schuh70cc9552019-01-21 19:46:48 -0800374bool TrustRegionPreprocessor::Preprocess(const Solver::Options& options,
375 ProblemImpl* problem,
376 PreprocessedProblem* pp) {
377 CHECK(pp != nullptr);
378 pp->options = options;
379 ChangeNumThreadsIfNeeded(&pp->options);
380
381 pp->problem = problem;
382 Program* program = problem->mutable_program();
383 if (!IsProgramValid(*program, &pp->error)) {
384 return false;
385 }
386
Austin Schuh3de38b02024-06-25 18:25:10 -0700387 pp->reduced_program = program->CreateReducedProgram(
388 &pp->removed_parameter_blocks, &pp->fixed_cost, &pp->error);
Austin Schuh70cc9552019-01-21 19:46:48 -0800389
Austin Schuh3de38b02024-06-25 18:25:10 -0700390 if (pp->reduced_program.get() == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800391 return false;
392 }
393
394 if (pp->reduced_program->NumParameterBlocks() == 0) {
395 // The reduced problem has no parameter or residual blocks. There
396 // is nothing more to do.
397 return true;
398 }
399
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800400 if (!SetupLinearSolver(pp) || !SetupEvaluator(pp) ||
Austin Schuh70cc9552019-01-21 19:46:48 -0800401 !SetupInnerIterationMinimizer(pp)) {
402 return false;
403 }
404
Austin Schuh3de38b02024-06-25 18:25:10 -0700405 return SetupMinimizerOptions(pp);
Austin Schuh70cc9552019-01-21 19:46:48 -0800406}
407
Austin Schuh3de38b02024-06-25 18:25:10 -0700408} // namespace ceres::internal