blob: 53986ee386e58b27dec652957f49f8f673e39aba [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/coordinate_descent_minimizer.h"
32
33#include <algorithm>
34#include <iterator>
Austin Schuh3de38b02024-06-25 18:25:10 -070035#include <map>
Austin Schuh70cc9552019-01-21 19:46:48 -080036#include <memory>
37#include <numeric>
Austin Schuh3de38b02024-06-25 18:25:10 -070038#include <set>
39#include <string>
Austin Schuh70cc9552019-01-21 19:46:48 -080040#include <vector>
41
42#include "ceres/evaluator.h"
43#include "ceres/linear_solver.h"
44#include "ceres/minimizer.h"
45#include "ceres/parallel_for.h"
46#include "ceres/parameter_block.h"
47#include "ceres/parameter_block_ordering.h"
48#include "ceres/problem_impl.h"
49#include "ceres/program.h"
50#include "ceres/residual_block.h"
51#include "ceres/solver.h"
52#include "ceres/trust_region_minimizer.h"
53#include "ceres/trust_region_strategy.h"
54
Austin Schuh3de38b02024-06-25 18:25:10 -070055namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080056
57CoordinateDescentMinimizer::CoordinateDescentMinimizer(ContextImpl* context)
58 : context_(context) {
59 CHECK(context_ != nullptr);
60}
61
Austin Schuh3de38b02024-06-25 18:25:10 -070062CoordinateDescentMinimizer::~CoordinateDescentMinimizer() = default;
Austin Schuh70cc9552019-01-21 19:46:48 -080063
64bool CoordinateDescentMinimizer::Init(
65 const Program& program,
66 const ProblemImpl::ParameterMap& parameter_map,
67 const ParameterBlockOrdering& ordering,
Austin Schuh3de38b02024-06-25 18:25:10 -070068 std::string* /*error*/) {
Austin Schuh70cc9552019-01-21 19:46:48 -080069 parameter_blocks_.clear();
70 independent_set_offsets_.clear();
71 independent_set_offsets_.push_back(0);
72
73 // Serialize the OrderedGroups into a vector of parameter block
74 // offsets for parallel access.
Austin Schuh3de38b02024-06-25 18:25:10 -070075
76 // TODO(sameeragarwal): Investigate if parameter_block_index should be an
77 // ordered or an unordered container.
78 std::map<ParameterBlock*, int> parameter_block_index;
79 std::map<int, std::set<double*>> group_to_elements =
80 ordering.group_to_elements();
Austin Schuh70cc9552019-01-21 19:46:48 -080081 for (const auto& g_t_e : group_to_elements) {
82 const auto& elements = g_t_e.second;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080083 for (double* parameter_block : elements) {
Austin Schuh70cc9552019-01-21 19:46:48 -080084 parameter_blocks_.push_back(parameter_map.find(parameter_block)->second);
85 parameter_block_index[parameter_blocks_.back()] =
86 parameter_blocks_.size() - 1;
87 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080088 independent_set_offsets_.push_back(independent_set_offsets_.back() +
89 elements.size());
Austin Schuh70cc9552019-01-21 19:46:48 -080090 }
91
92 // The ordering does not have to contain all parameter blocks, so
93 // assign zero offsets/empty independent sets to these parameter
94 // blocks.
Austin Schuh3de38b02024-06-25 18:25:10 -070095 const std::vector<ParameterBlock*>& parameter_blocks =
96 program.parameter_blocks();
97 for (auto* parameter_block : parameter_blocks) {
98 if (!ordering.IsMember(parameter_block->mutable_user_state())) {
99 parameter_blocks_.push_back(parameter_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800100 independent_set_offsets_.push_back(independent_set_offsets_.back());
101 }
102 }
103
104 // Compute the set of residual blocks that depend on each parameter
105 // block.
106 residual_blocks_.resize(parameter_block_index.size());
Austin Schuh3de38b02024-06-25 18:25:10 -0700107 const std::vector<ResidualBlock*>& residual_blocks =
108 program.residual_blocks();
109 for (auto* residual_block : residual_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800110 const int num_parameter_blocks = residual_block->NumParameterBlocks();
111 for (int j = 0; j < num_parameter_blocks; ++j) {
112 ParameterBlock* parameter_block = residual_block->parameter_blocks()[j];
113 const auto it = parameter_block_index.find(parameter_block);
114 if (it != parameter_block_index.end()) {
115 residual_blocks_[it->second].push_back(residual_block);
116 }
117 }
118 }
119
120 evaluator_options_.linear_solver_type = DENSE_QR;
121 evaluator_options_.num_eliminate_blocks = 0;
122 evaluator_options_.num_threads = 1;
123 evaluator_options_.context = context_;
124
125 return true;
126}
127
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800128void CoordinateDescentMinimizer::Minimize(const Minimizer::Options& options,
129 double* parameters,
Austin Schuh3de38b02024-06-25 18:25:10 -0700130 Solver::Summary* /*summary*/) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800131 // Set the state and mark all parameter blocks constant.
Austin Schuh3de38b02024-06-25 18:25:10 -0700132 for (auto* parameter_block : parameter_blocks_) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800133 parameter_block->SetState(parameters + parameter_block->state_offset());
134 parameter_block->SetConstant();
135 }
136
Austin Schuh3de38b02024-06-25 18:25:10 -0700137 std::vector<std::unique_ptr<LinearSolver>> linear_solvers(
138 options.num_threads);
Austin Schuh70cc9552019-01-21 19:46:48 -0800139
140 LinearSolver::Options linear_solver_options;
141 linear_solver_options.type = DENSE_QR;
142 linear_solver_options.context = context_;
143
144 for (int i = 0; i < options.num_threads; ++i) {
145 linear_solvers[i] = LinearSolver::Create(linear_solver_options);
146 }
147
148 for (int i = 0; i < independent_set_offsets_.size() - 1; ++i) {
149 const int num_problems =
150 independent_set_offsets_[i + 1] - independent_set_offsets_[i];
151 // Avoid parallelization overhead call if the set is empty.
152 if (num_problems == 0) {
153 continue;
154 }
155
156 const int num_inner_iteration_threads =
Austin Schuh3de38b02024-06-25 18:25:10 -0700157 std::min(options.num_threads, num_problems);
Austin Schuh70cc9552019-01-21 19:46:48 -0800158 evaluator_options_.num_threads =
Austin Schuh3de38b02024-06-25 18:25:10 -0700159 std::max(1, options.num_threads / num_inner_iteration_threads);
Austin Schuh70cc9552019-01-21 19:46:48 -0800160
161 // The parameter blocks in each independent set can be optimized
162 // in parallel, since they do not co-occur in any residual block.
163 ParallelFor(
164 context_,
165 independent_set_offsets_[i],
166 independent_set_offsets_[i + 1],
167 num_inner_iteration_threads,
168 [&](int thread_id, int j) {
169 ParameterBlock* parameter_block = parameter_blocks_[j];
170 const int old_index = parameter_block->index();
171 const int old_delta_offset = parameter_block->delta_offset();
Austin Schuh3de38b02024-06-25 18:25:10 -0700172 const int old_state_offset = parameter_block->state_offset();
Austin Schuh70cc9552019-01-21 19:46:48 -0800173 parameter_block->SetVarying();
174 parameter_block->set_index(0);
175 parameter_block->set_delta_offset(0);
Austin Schuh3de38b02024-06-25 18:25:10 -0700176 parameter_block->set_state_offset(0);
Austin Schuh70cc9552019-01-21 19:46:48 -0800177
178 Program inner_program;
179 inner_program.mutable_parameter_blocks()->push_back(parameter_block);
180 *inner_program.mutable_residual_blocks() = residual_blocks_[j];
181
182 // TODO(sameeragarwal): Better error handling. Right now we
183 // assume that this is not going to lead to problems of any
184 // sort. Basically we should be checking for numerical failure
185 // of some sort.
186 //
187 // On the other hand, if the optimization is a failure, that in
188 // some ways is fine, since it won't change the parameters and
189 // we are fine.
190 Solver::Summary inner_summary;
191 Solve(&inner_program,
Austin Schuh3de38b02024-06-25 18:25:10 -0700192 linear_solvers[thread_id].get(),
193 parameters + old_state_offset,
Austin Schuh70cc9552019-01-21 19:46:48 -0800194 &inner_summary);
195
196 parameter_block->set_index(old_index);
197 parameter_block->set_delta_offset(old_delta_offset);
Austin Schuh3de38b02024-06-25 18:25:10 -0700198 parameter_block->set_state_offset(old_state_offset);
Austin Schuh70cc9552019-01-21 19:46:48 -0800199 parameter_block->SetState(parameters +
200 parameter_block->state_offset());
201 parameter_block->SetConstant();
202 });
203 }
204
Austin Schuh3de38b02024-06-25 18:25:10 -0700205 for (auto* parameter_block : parameter_blocks_) {
206 parameter_block->SetVarying();
Austin Schuh70cc9552019-01-21 19:46:48 -0800207 }
208}
209
210// Solve the optimization problem for one parameter block.
211void CoordinateDescentMinimizer::Solve(Program* program,
212 LinearSolver* linear_solver,
213 double* parameter,
214 Solver::Summary* summary) {
215 *summary = Solver::Summary();
216 summary->initial_cost = 0.0;
217 summary->fixed_cost = 0.0;
218 summary->final_cost = 0.0;
Austin Schuh3de38b02024-06-25 18:25:10 -0700219 std::string error;
Austin Schuh70cc9552019-01-21 19:46:48 -0800220
221 Minimizer::Options minimizer_options;
Austin Schuh3de38b02024-06-25 18:25:10 -0700222 minimizer_options.evaluator =
223 Evaluator::Create(evaluator_options_, program, &error);
Austin Schuh70cc9552019-01-21 19:46:48 -0800224 CHECK(minimizer_options.evaluator != nullptr);
Austin Schuh3de38b02024-06-25 18:25:10 -0700225 minimizer_options.jacobian = minimizer_options.evaluator->CreateJacobian();
Austin Schuh70cc9552019-01-21 19:46:48 -0800226 CHECK(minimizer_options.jacobian != nullptr);
227
228 TrustRegionStrategy::Options trs_options;
229 trs_options.linear_solver = linear_solver;
Austin Schuh3de38b02024-06-25 18:25:10 -0700230 minimizer_options.trust_region_strategy =
231 TrustRegionStrategy::Create(trs_options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800232 CHECK(minimizer_options.trust_region_strategy != nullptr);
233 minimizer_options.is_silent = true;
234
235 TrustRegionMinimizer minimizer;
236 minimizer.Minimize(minimizer_options, parameter, summary);
237}
238
239bool CoordinateDescentMinimizer::IsOrderingValid(
240 const Program& program,
241 const ParameterBlockOrdering& ordering,
Austin Schuh3de38b02024-06-25 18:25:10 -0700242 std::string* message) {
243 // TODO(sameeragarwal): Investigate if this should be an ordered or an
244 // unordered group.
245 const std::map<int, std::set<double*>>& group_to_elements =
Austin Schuh70cc9552019-01-21 19:46:48 -0800246 ordering.group_to_elements();
247
248 // Verify that each group is an independent set
249 for (const auto& g_t_e : group_to_elements) {
250 if (!program.IsParameterBlockSetIndependent(g_t_e.second)) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800251 *message = StringPrintf(
252 "The user-provided parameter_blocks_for_inner_iterations does not "
253 "form an independent set. Group Id: %d",
254 g_t_e.first);
Austin Schuh70cc9552019-01-21 19:46:48 -0800255 return false;
256 }
257 }
258 return true;
259}
260
261// Find a recursive decomposition of the Hessian matrix as a set
262// of independent sets of decreasing size and invert it. This
263// seems to work better in practice, i.e., Cameras before
264// points.
Austin Schuh3de38b02024-06-25 18:25:10 -0700265std::shared_ptr<ParameterBlockOrdering>
266CoordinateDescentMinimizer::CreateOrdering(const Program& program) {
267 auto ordering = std::make_shared<ParameterBlockOrdering>();
Austin Schuh70cc9552019-01-21 19:46:48 -0800268 ComputeRecursiveIndependentSetOrdering(program, ordering.get());
269 ordering->Reverse();
Austin Schuh3de38b02024-06-25 18:25:10 -0700270 return ordering;
Austin Schuh70cc9552019-01-21 19:46:48 -0800271}
272
Austin Schuh3de38b02024-06-25 18:25:10 -0700273} // namespace ceres::internal