blob: c5d56f30bc3d6f04e78b6432285475951240dcf2 [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
67CoordinateDescentMinimizer::~CoordinateDescentMinimizer() {
68}
69
70bool CoordinateDescentMinimizer::Init(
71 const Program& program,
72 const ProblemImpl::ParameterMap& parameter_map,
73 const ParameterBlockOrdering& ordering,
74 string* error) {
75 parameter_blocks_.clear();
76 independent_set_offsets_.clear();
77 independent_set_offsets_.push_back(0);
78
79 // Serialize the OrderedGroups into a vector of parameter block
80 // offsets for parallel access.
81 map<ParameterBlock*, int> parameter_block_index;
82 map<int, set<double*>> group_to_elements = ordering.group_to_elements();
83 for (const auto& g_t_e : group_to_elements) {
84 const auto& elements = g_t_e.second;
85 for (double* parameter_block: elements) {
86 parameter_blocks_.push_back(parameter_map.find(parameter_block)->second);
87 parameter_block_index[parameter_blocks_.back()] =
88 parameter_blocks_.size() - 1;
89 }
90 independent_set_offsets_.push_back(
91 independent_set_offsets_.back() + elements.size());
92 }
93
94 // The ordering does not have to contain all parameter blocks, so
95 // assign zero offsets/empty independent sets to these parameter
96 // blocks.
97 const vector<ParameterBlock*>& parameter_blocks = program.parameter_blocks();
98 for (int i = 0; i < parameter_blocks.size(); ++i) {
99 if (!ordering.IsMember(parameter_blocks[i]->mutable_user_state())) {
100 parameter_blocks_.push_back(parameter_blocks[i]);
101 independent_set_offsets_.push_back(independent_set_offsets_.back());
102 }
103 }
104
105 // Compute the set of residual blocks that depend on each parameter
106 // block.
107 residual_blocks_.resize(parameter_block_index.size());
108 const vector<ResidualBlock*>& residual_blocks = program.residual_blocks();
109 for (int i = 0; i < residual_blocks.size(); ++i) {
110 ResidualBlock* residual_block = residual_blocks[i];
111 const int num_parameter_blocks = residual_block->NumParameterBlocks();
112 for (int j = 0; j < num_parameter_blocks; ++j) {
113 ParameterBlock* parameter_block = residual_block->parameter_blocks()[j];
114 const auto it = parameter_block_index.find(parameter_block);
115 if (it != parameter_block_index.end()) {
116 residual_blocks_[it->second].push_back(residual_block);
117 }
118 }
119 }
120
121 evaluator_options_.linear_solver_type = DENSE_QR;
122 evaluator_options_.num_eliminate_blocks = 0;
123 evaluator_options_.num_threads = 1;
124 evaluator_options_.context = context_;
125
126 return true;
127}
128
129void CoordinateDescentMinimizer::Minimize(
130 const Minimizer::Options& options,
131 double* parameters,
132 Solver::Summary* summary) {
133 // Set the state and mark all parameter blocks constant.
134 for (int i = 0; i < parameter_blocks_.size(); ++i) {
135 ParameterBlock* parameter_block = parameter_blocks_[i];
136 parameter_block->SetState(parameters + parameter_block->state_offset());
137 parameter_block->SetConstant();
138 }
139
140 std::unique_ptr<LinearSolver*[]> linear_solvers(
141 new LinearSolver*[options.num_threads]);
142
143 LinearSolver::Options linear_solver_options;
144 linear_solver_options.type = DENSE_QR;
145 linear_solver_options.context = context_;
146
147 for (int i = 0; i < options.num_threads; ++i) {
148 linear_solvers[i] = LinearSolver::Create(linear_solver_options);
149 }
150
151 for (int i = 0; i < independent_set_offsets_.size() - 1; ++i) {
152 const int num_problems =
153 independent_set_offsets_[i + 1] - independent_set_offsets_[i];
154 // Avoid parallelization overhead call if the set is empty.
155 if (num_problems == 0) {
156 continue;
157 }
158
159 const int num_inner_iteration_threads =
160 min(options.num_threads, num_problems);
161 evaluator_options_.num_threads =
162 max(1, options.num_threads / num_inner_iteration_threads);
163
164 // The parameter blocks in each independent set can be optimized
165 // in parallel, since they do not co-occur in any residual block.
166 ParallelFor(
167 context_,
168 independent_set_offsets_[i],
169 independent_set_offsets_[i + 1],
170 num_inner_iteration_threads,
171 [&](int thread_id, int j) {
172 ParameterBlock* parameter_block = parameter_blocks_[j];
173 const int old_index = parameter_block->index();
174 const int old_delta_offset = parameter_block->delta_offset();
175 parameter_block->SetVarying();
176 parameter_block->set_index(0);
177 parameter_block->set_delta_offset(0);
178
179 Program inner_program;
180 inner_program.mutable_parameter_blocks()->push_back(parameter_block);
181 *inner_program.mutable_residual_blocks() = residual_blocks_[j];
182
183 // TODO(sameeragarwal): Better error handling. Right now we
184 // assume that this is not going to lead to problems of any
185 // sort. Basically we should be checking for numerical failure
186 // of some sort.
187 //
188 // On the other hand, if the optimization is a failure, that in
189 // some ways is fine, since it won't change the parameters and
190 // we are fine.
191 Solver::Summary inner_summary;
192 Solve(&inner_program,
193 linear_solvers[thread_id],
194 parameters + parameter_block->state_offset(),
195 &inner_summary);
196
197 parameter_block->set_index(old_index);
198 parameter_block->set_delta_offset(old_delta_offset);
199 parameter_block->SetState(parameters +
200 parameter_block->state_offset());
201 parameter_block->SetConstant();
202 });
203 }
204
205 for (int i = 0; i < parameter_blocks_.size(); ++i) {
206 parameter_blocks_[i]->SetVarying();
207 }
208
209 for (int i = 0; i < options.num_threads; ++i) {
210 delete linear_solvers[i];
211 }
212}
213
214// Solve the optimization problem for one parameter block.
215void CoordinateDescentMinimizer::Solve(Program* program,
216 LinearSolver* linear_solver,
217 double* parameter,
218 Solver::Summary* summary) {
219 *summary = Solver::Summary();
220 summary->initial_cost = 0.0;
221 summary->fixed_cost = 0.0;
222 summary->final_cost = 0.0;
223 string error;
224
225 Minimizer::Options minimizer_options;
226 minimizer_options.evaluator.reset(
227 Evaluator::Create(evaluator_options_, program, &error));
228 CHECK(minimizer_options.evaluator != nullptr);
229 minimizer_options.jacobian.reset(
230 minimizer_options.evaluator->CreateJacobian());
231 CHECK(minimizer_options.jacobian != nullptr);
232
233 TrustRegionStrategy::Options trs_options;
234 trs_options.linear_solver = linear_solver;
235 minimizer_options.trust_region_strategy.reset(
236 TrustRegionStrategy::Create(trs_options));
237 CHECK(minimizer_options.trust_region_strategy != nullptr);
238 minimizer_options.is_silent = true;
239
240 TrustRegionMinimizer minimizer;
241 minimizer.Minimize(minimizer_options, parameter, summary);
242}
243
244bool CoordinateDescentMinimizer::IsOrderingValid(
245 const Program& program,
246 const ParameterBlockOrdering& ordering,
247 string* message) {
248 const map<int, set<double*>>& group_to_elements =
249 ordering.group_to_elements();
250
251 // Verify that each group is an independent set
252 for (const auto& g_t_e : group_to_elements) {
253 if (!program.IsParameterBlockSetIndependent(g_t_e.second)) {
254 *message =
255 StringPrintf("The user-provided "
256 "parameter_blocks_for_inner_iterations does not "
257 "form an independent set. Group Id: %d", g_t_e.first);
258 return false;
259 }
260 }
261 return true;
262}
263
264// Find a recursive decomposition of the Hessian matrix as a set
265// of independent sets of decreasing size and invert it. This
266// seems to work better in practice, i.e., Cameras before
267// points.
268ParameterBlockOrdering* CoordinateDescentMinimizer::CreateOrdering(
269 const Program& program) {
270 std::unique_ptr<ParameterBlockOrdering> ordering(new ParameterBlockOrdering);
271 ComputeRecursiveIndependentSetOrdering(program, ordering.get());
272 ordering->Reverse();
273 return ordering.release();
274}
275
276} // namespace internal
277} // namespace ceres