blob: 93096ac07288816929d3c49bad3b18eeebd476d0 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// 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
31#include "ceres/coordinate_descent_minimizer.h"
32
33#include <algorithm>
34#include <iterator>
35#include <memory>
36#include <numeric>
37#include <vector>
38
39#include "ceres/evaluator.h"
40#include "ceres/linear_solver.h"
41#include "ceres/minimizer.h"
42#include "ceres/parallel_for.h"
43#include "ceres/parameter_block.h"
44#include "ceres/parameter_block_ordering.h"
45#include "ceres/problem_impl.h"
46#include "ceres/program.h"
47#include "ceres/residual_block.h"
48#include "ceres/solver.h"
49#include "ceres/trust_region_minimizer.h"
50#include "ceres/trust_region_strategy.h"
51
52namespace ceres {
53namespace internal {
54
55using std::map;
56using std::max;
57using std::min;
58using std::set;
59using std::string;
60using std::vector;
61
62CoordinateDescentMinimizer::CoordinateDescentMinimizer(ContextImpl* context)
63 : context_(context) {
64 CHECK(context_ != nullptr);
65}
66
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080067CoordinateDescentMinimizer::~CoordinateDescentMinimizer() {}
Austin Schuh70cc9552019-01-21 19:46:48 -080068
69bool CoordinateDescentMinimizer::Init(
70 const Program& program,
71 const ProblemImpl::ParameterMap& parameter_map,
72 const ParameterBlockOrdering& ordering,
73 string* error) {
74 parameter_blocks_.clear();
75 independent_set_offsets_.clear();
76 independent_set_offsets_.push_back(0);
77
78 // Serialize the OrderedGroups into a vector of parameter block
79 // offsets for parallel access.
80 map<ParameterBlock*, int> parameter_block_index;
81 map<int, set<double*>> group_to_elements = ordering.group_to_elements();
82 for (const auto& g_t_e : group_to_elements) {
83 const auto& elements = g_t_e.second;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080084 for (double* parameter_block : elements) {
Austin Schuh70cc9552019-01-21 19:46:48 -080085 parameter_blocks_.push_back(parameter_map.find(parameter_block)->second);
86 parameter_block_index[parameter_blocks_.back()] =
87 parameter_blocks_.size() - 1;
88 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080089 independent_set_offsets_.push_back(independent_set_offsets_.back() +
90 elements.size());
Austin Schuh70cc9552019-01-21 19:46:48 -080091 }
92
93 // The ordering does not have to contain all parameter blocks, so
94 // assign zero offsets/empty independent sets to these parameter
95 // blocks.
96 const vector<ParameterBlock*>& parameter_blocks = program.parameter_blocks();
97 for (int i = 0; i < parameter_blocks.size(); ++i) {
98 if (!ordering.IsMember(parameter_blocks[i]->mutable_user_state())) {
99 parameter_blocks_.push_back(parameter_blocks[i]);
100 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());
107 const vector<ResidualBlock*>& residual_blocks = program.residual_blocks();
108 for (int i = 0; i < residual_blocks.size(); ++i) {
109 ResidualBlock* residual_block = residual_blocks[i];
110 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,
130 Solver::Summary* summary) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800131 // Set the state and mark all parameter blocks constant.
132 for (int i = 0; i < parameter_blocks_.size(); ++i) {
133 ParameterBlock* parameter_block = parameter_blocks_[i];
134 parameter_block->SetState(parameters + parameter_block->state_offset());
135 parameter_block->SetConstant();
136 }
137
138 std::unique_ptr<LinearSolver*[]> linear_solvers(
139 new LinearSolver*[options.num_threads]);
140
141 LinearSolver::Options linear_solver_options;
142 linear_solver_options.type = DENSE_QR;
143 linear_solver_options.context = context_;
144
145 for (int i = 0; i < options.num_threads; ++i) {
146 linear_solvers[i] = LinearSolver::Create(linear_solver_options);
147 }
148
149 for (int i = 0; i < independent_set_offsets_.size() - 1; ++i) {
150 const int num_problems =
151 independent_set_offsets_[i + 1] - independent_set_offsets_[i];
152 // Avoid parallelization overhead call if the set is empty.
153 if (num_problems == 0) {
154 continue;
155 }
156
157 const int num_inner_iteration_threads =
158 min(options.num_threads, num_problems);
159 evaluator_options_.num_threads =
160 max(1, options.num_threads / num_inner_iteration_threads);
161
162 // The parameter blocks in each independent set can be optimized
163 // in parallel, since they do not co-occur in any residual block.
164 ParallelFor(
165 context_,
166 independent_set_offsets_[i],
167 independent_set_offsets_[i + 1],
168 num_inner_iteration_threads,
169 [&](int thread_id, int j) {
170 ParameterBlock* parameter_block = parameter_blocks_[j];
171 const int old_index = parameter_block->index();
172 const int old_delta_offset = parameter_block->delta_offset();
173 parameter_block->SetVarying();
174 parameter_block->set_index(0);
175 parameter_block->set_delta_offset(0);
176
177 Program inner_program;
178 inner_program.mutable_parameter_blocks()->push_back(parameter_block);
179 *inner_program.mutable_residual_blocks() = residual_blocks_[j];
180
181 // TODO(sameeragarwal): Better error handling. Right now we
182 // assume that this is not going to lead to problems of any
183 // sort. Basically we should be checking for numerical failure
184 // of some sort.
185 //
186 // On the other hand, if the optimization is a failure, that in
187 // some ways is fine, since it won't change the parameters and
188 // we are fine.
189 Solver::Summary inner_summary;
190 Solve(&inner_program,
191 linear_solvers[thread_id],
192 parameters + parameter_block->state_offset(),
193 &inner_summary);
194
195 parameter_block->set_index(old_index);
196 parameter_block->set_delta_offset(old_delta_offset);
197 parameter_block->SetState(parameters +
198 parameter_block->state_offset());
199 parameter_block->SetConstant();
200 });
201 }
202
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800203 for (int i = 0; i < parameter_blocks_.size(); ++i) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800204 parameter_blocks_[i]->SetVarying();
205 }
206
207 for (int i = 0; i < options.num_threads; ++i) {
208 delete linear_solvers[i];
209 }
210}
211
212// Solve the optimization problem for one parameter block.
213void CoordinateDescentMinimizer::Solve(Program* program,
214 LinearSolver* linear_solver,
215 double* parameter,
216 Solver::Summary* summary) {
217 *summary = Solver::Summary();
218 summary->initial_cost = 0.0;
219 summary->fixed_cost = 0.0;
220 summary->final_cost = 0.0;
221 string error;
222
223 Minimizer::Options minimizer_options;
224 minimizer_options.evaluator.reset(
225 Evaluator::Create(evaluator_options_, program, &error));
226 CHECK(minimizer_options.evaluator != nullptr);
227 minimizer_options.jacobian.reset(
228 minimizer_options.evaluator->CreateJacobian());
229 CHECK(minimizer_options.jacobian != nullptr);
230
231 TrustRegionStrategy::Options trs_options;
232 trs_options.linear_solver = linear_solver;
233 minimizer_options.trust_region_strategy.reset(
234 TrustRegionStrategy::Create(trs_options));
235 CHECK(minimizer_options.trust_region_strategy != nullptr);
236 minimizer_options.is_silent = true;
237
238 TrustRegionMinimizer minimizer;
239 minimizer.Minimize(minimizer_options, parameter, summary);
240}
241
242bool CoordinateDescentMinimizer::IsOrderingValid(
243 const Program& program,
244 const ParameterBlockOrdering& ordering,
245 string* message) {
246 const map<int, set<double*>>& group_to_elements =
247 ordering.group_to_elements();
248
249 // Verify that each group is an independent set
250 for (const auto& g_t_e : group_to_elements) {
251 if (!program.IsParameterBlockSetIndependent(g_t_e.second)) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800252 *message = StringPrintf(
253 "The user-provided parameter_blocks_for_inner_iterations does not "
254 "form an independent set. Group Id: %d",
255 g_t_e.first);
Austin Schuh70cc9552019-01-21 19:46:48 -0800256 return false;
257 }
258 }
259 return true;
260}
261
262// Find a recursive decomposition of the Hessian matrix as a set
263// of independent sets of decreasing size and invert it. This
264// seems to work better in practice, i.e., Cameras before
265// points.
266ParameterBlockOrdering* CoordinateDescentMinimizer::CreateOrdering(
267 const Program& program) {
268 std::unique_ptr<ParameterBlockOrdering> ordering(new ParameterBlockOrdering);
269 ComputeRecursiveIndependentSetOrdering(program, ordering.get());
270 ordering->Reverse();
271 return ordering.release();
272}
273
274} // namespace internal
275} // namespace ceres