blob: 6e8362dacc342b3b7ce0ede700f789c4536f782a [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/covariance_impl.h"
32
33#include <algorithm>
34#include <cstdlib>
35#include <memory>
36#include <numeric>
37#include <sstream>
38#include <unordered_set>
39#include <utility>
40#include <vector>
41
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080042#include "Eigen/SVD"
Austin Schuh70cc9552019-01-21 19:46:48 -080043#include "Eigen/SparseCore"
44#include "Eigen/SparseQR"
Austin Schuh70cc9552019-01-21 19:46:48 -080045#include "ceres/compressed_col_sparse_matrix_utils.h"
46#include "ceres/compressed_row_sparse_matrix.h"
47#include "ceres/covariance.h"
48#include "ceres/crs_matrix.h"
49#include "ceres/internal/eigen.h"
50#include "ceres/map_util.h"
51#include "ceres/parallel_for.h"
52#include "ceres/parallel_utils.h"
53#include "ceres/parameter_block.h"
54#include "ceres/problem_impl.h"
55#include "ceres/residual_block.h"
56#include "ceres/suitesparse.h"
57#include "ceres/wall_time.h"
58#include "glog/logging.h"
59
Austin Schuh3de38b02024-06-25 18:25:10 -070060namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080061
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080062using CovarianceBlocks = std::vector<std::pair<const double*, const double*>>;
Austin Schuh70cc9552019-01-21 19:46:48 -080063
64CovarianceImpl::CovarianceImpl(const Covariance::Options& options)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080065 : options_(options), is_computed_(false), is_valid_(false) {
Austin Schuh70cc9552019-01-21 19:46:48 -080066 evaluate_options_.num_threads = options_.num_threads;
67 evaluate_options_.apply_loss_function = options_.apply_loss_function;
68}
69
Austin Schuh3de38b02024-06-25 18:25:10 -070070CovarianceImpl::~CovarianceImpl() = default;
Austin Schuh70cc9552019-01-21 19:46:48 -080071
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080072template <typename T>
73void CheckForDuplicates(std::vector<T> blocks) {
Austin Schuh3de38b02024-06-25 18:25:10 -070074 std::sort(blocks.begin(), blocks.end());
75 auto it = std::adjacent_find(blocks.begin(), blocks.end());
Austin Schuh70cc9552019-01-21 19:46:48 -080076 if (it != blocks.end()) {
77 // In case there are duplicates, we search for their location.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080078 std::map<T, std::vector<int>> blocks_map;
Austin Schuh70cc9552019-01-21 19:46:48 -080079 for (int i = 0; i < blocks.size(); ++i) {
80 blocks_map[blocks[i]].push_back(i);
81 }
82
83 std::ostringstream duplicates;
84 while (it != blocks.end()) {
85 duplicates << "(";
86 for (int i = 0; i < blocks_map[*it].size() - 1; ++i) {
87 duplicates << blocks_map[*it][i] << ", ";
88 }
89 duplicates << blocks_map[*it].back() << ")";
90 it = std::adjacent_find(it + 1, blocks.end());
91 if (it < blocks.end()) {
92 duplicates << " and ";
93 }
94 }
95
96 LOG(FATAL) << "Covariance::Compute called with duplicate blocks at "
97 << "indices " << duplicates.str();
98 }
99}
100
101bool CovarianceImpl::Compute(const CovarianceBlocks& covariance_blocks,
102 ProblemImpl* problem) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800103 CheckForDuplicates<std::pair<const double*, const double*>>(
104 covariance_blocks);
Austin Schuh70cc9552019-01-21 19:46:48 -0800105 problem_ = problem;
106 parameter_block_to_row_index_.clear();
Austin Schuh3de38b02024-06-25 18:25:10 -0700107 covariance_matrix_ = nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800108 is_valid_ = (ComputeCovarianceSparsity(covariance_blocks, problem) &&
109 ComputeCovarianceValues());
110 is_computed_ = true;
111 return is_valid_;
112}
113
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800114bool CovarianceImpl::Compute(const std::vector<const double*>& parameter_blocks,
Austin Schuh70cc9552019-01-21 19:46:48 -0800115 ProblemImpl* problem) {
116 CheckForDuplicates<const double*>(parameter_blocks);
117 CovarianceBlocks covariance_blocks;
118 for (int i = 0; i < parameter_blocks.size(); ++i) {
119 for (int j = i; j < parameter_blocks.size(); ++j) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800120 covariance_blocks.push_back(
121 std::make_pair(parameter_blocks[i], parameter_blocks[j]));
Austin Schuh70cc9552019-01-21 19:46:48 -0800122 }
123 }
124
125 return Compute(covariance_blocks, problem);
126}
127
128bool CovarianceImpl::GetCovarianceBlockInTangentOrAmbientSpace(
129 const double* original_parameter_block1,
130 const double* original_parameter_block2,
131 bool lift_covariance_to_ambient_space,
132 double* covariance_block) const {
133 CHECK(is_computed_)
134 << "Covariance::GetCovarianceBlock called before Covariance::Compute";
135 CHECK(is_valid_)
136 << "Covariance::GetCovarianceBlock called when Covariance::Compute "
137 << "returned false.";
138
139 // If either of the two parameter blocks is constant, then the
140 // covariance block is also zero.
141 if (constant_parameter_blocks_.count(original_parameter_block1) > 0 ||
142 constant_parameter_blocks_.count(original_parameter_block2) > 0) {
143 const ProblemImpl::ParameterMap& parameter_map = problem_->parameter_map();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800144 ParameterBlock* block1 = FindOrDie(
145 parameter_map, const_cast<double*>(original_parameter_block1));
Austin Schuh70cc9552019-01-21 19:46:48 -0800146
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800147 ParameterBlock* block2 = FindOrDie(
148 parameter_map, const_cast<double*>(original_parameter_block2));
Austin Schuh70cc9552019-01-21 19:46:48 -0800149
150 const int block1_size = block1->Size();
151 const int block2_size = block2->Size();
Austin Schuh3de38b02024-06-25 18:25:10 -0700152 const int block1_tangent_size = block1->TangentSize();
153 const int block2_tangent_size = block2->TangentSize();
Austin Schuh70cc9552019-01-21 19:46:48 -0800154 if (!lift_covariance_to_ambient_space) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700155 MatrixRef(covariance_block, block1_tangent_size, block2_tangent_size)
Austin Schuh70cc9552019-01-21 19:46:48 -0800156 .setZero();
157 } else {
158 MatrixRef(covariance_block, block1_size, block2_size).setZero();
159 }
160 return true;
161 }
162
163 const double* parameter_block1 = original_parameter_block1;
164 const double* parameter_block2 = original_parameter_block2;
165 const bool transpose = parameter_block1 > parameter_block2;
166 if (transpose) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700167 std::swap(parameter_block1, parameter_block2);
Austin Schuh70cc9552019-01-21 19:46:48 -0800168 }
169
170 // Find where in the covariance matrix the block is located.
171 const int row_begin =
172 FindOrDie(parameter_block_to_row_index_, parameter_block1);
173 const int col_begin =
174 FindOrDie(parameter_block_to_row_index_, parameter_block2);
175 const int* rows = covariance_matrix_->rows();
176 const int* cols = covariance_matrix_->cols();
177 const int row_size = rows[row_begin + 1] - rows[row_begin];
178 const int* cols_begin = cols + rows[row_begin];
179
180 // The only part that requires work is walking the compressed column
Austin Schuh3de38b02024-06-25 18:25:10 -0700181 // vector to determine where the set of columns corresponding to the
Austin Schuh70cc9552019-01-21 19:46:48 -0800182 // covariance block begin.
183 int offset = 0;
184 while (cols_begin[offset] != col_begin && offset < row_size) {
185 ++offset;
186 }
187
188 if (offset == row_size) {
189 LOG(ERROR) << "Unable to find covariance block for "
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800190 << original_parameter_block1 << " " << original_parameter_block2;
Austin Schuh70cc9552019-01-21 19:46:48 -0800191 return false;
192 }
193
194 const ProblemImpl::ParameterMap& parameter_map = problem_->parameter_map();
195 ParameterBlock* block1 =
196 FindOrDie(parameter_map, const_cast<double*>(parameter_block1));
197 ParameterBlock* block2 =
198 FindOrDie(parameter_map, const_cast<double*>(parameter_block2));
Austin Schuh3de38b02024-06-25 18:25:10 -0700199 const Manifold* manifold1 = block1->manifold();
200 const Manifold* manifold2 = block2->manifold();
Austin Schuh70cc9552019-01-21 19:46:48 -0800201 const int block1_size = block1->Size();
Austin Schuh3de38b02024-06-25 18:25:10 -0700202 const int block1_tangent_size = block1->TangentSize();
Austin Schuh70cc9552019-01-21 19:46:48 -0800203 const int block2_size = block2->Size();
Austin Schuh3de38b02024-06-25 18:25:10 -0700204 const int block2_tangent_size = block2->TangentSize();
Austin Schuh70cc9552019-01-21 19:46:48 -0800205
Austin Schuh3de38b02024-06-25 18:25:10 -0700206 ConstMatrixRef cov(covariance_matrix_->values() + rows[row_begin],
207 block1_tangent_size,
208 row_size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800209
Austin Schuh3de38b02024-06-25 18:25:10 -0700210 // Fast path when there are no manifolds or if the user does not want it
211 // lifted to the ambient space.
212 if ((manifold1 == nullptr && manifold2 == nullptr) ||
Austin Schuh70cc9552019-01-21 19:46:48 -0800213 !lift_covariance_to_ambient_space) {
214 if (transpose) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700215 MatrixRef(covariance_block, block2_tangent_size, block1_tangent_size) =
216 cov.block(0, offset, block1_tangent_size, block2_tangent_size)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800217 .transpose();
Austin Schuh70cc9552019-01-21 19:46:48 -0800218 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700219 MatrixRef(covariance_block, block1_tangent_size, block2_tangent_size) =
220 cov.block(0, offset, block1_tangent_size, block2_tangent_size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800221 }
222 return true;
223 }
224
Austin Schuh3de38b02024-06-25 18:25:10 -0700225 // If manifolds are used then the covariance that has been computed is in the
226 // tangent space and it needs to be lifted back to the ambient space.
Austin Schuh70cc9552019-01-21 19:46:48 -0800227 //
228 // This is given by the formula
229 //
230 // C'_12 = J_1 C_12 J_2'
231 //
232 // Where C_12 is the local tangent space covariance for parameter
233 // blocks 1 and 2. J_1 and J_2 are respectively the local to global
234 // jacobians for parameter blocks 1 and 2.
235 //
236 // See Result 5.11 on page 142 of Hartley & Zisserman (2nd Edition)
237 // for a proof.
238 //
Austin Schuh3de38b02024-06-25 18:25:10 -0700239 // TODO(sameeragarwal): Add caching the manifold plus_jacobian, so that they
240 // are computed just once per parameter block.
241 Matrix block1_jacobian(block1_size, block1_tangent_size);
242 if (manifold1 == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800243 block1_jacobian.setIdentity();
244 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700245 manifold1->PlusJacobian(parameter_block1, block1_jacobian.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800246 }
247
Austin Schuh3de38b02024-06-25 18:25:10 -0700248 Matrix block2_jacobian(block2_size, block2_tangent_size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800249 // Fast path if the user is requesting a diagonal block.
250 if (parameter_block1 == parameter_block2) {
251 block2_jacobian = block1_jacobian;
252 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700253 if (manifold2 == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800254 block2_jacobian.setIdentity();
255 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700256 manifold2->PlusJacobian(parameter_block2, block2_jacobian.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800257 }
258 }
259
260 if (transpose) {
261 MatrixRef(covariance_block, block2_size, block1_size) =
262 block2_jacobian *
Austin Schuh3de38b02024-06-25 18:25:10 -0700263 cov.block(0, offset, block1_tangent_size, block2_tangent_size)
264 .transpose() *
Austin Schuh70cc9552019-01-21 19:46:48 -0800265 block1_jacobian.transpose();
266 } else {
267 MatrixRef(covariance_block, block1_size, block2_size) =
268 block1_jacobian *
Austin Schuh3de38b02024-06-25 18:25:10 -0700269 cov.block(0, offset, block1_tangent_size, block2_tangent_size) *
Austin Schuh70cc9552019-01-21 19:46:48 -0800270 block2_jacobian.transpose();
271 }
272
273 return true;
274}
275
276bool CovarianceImpl::GetCovarianceMatrixInTangentOrAmbientSpace(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800277 const std::vector<const double*>& parameters,
Austin Schuh70cc9552019-01-21 19:46:48 -0800278 bool lift_covariance_to_ambient_space,
279 double* covariance_matrix) const {
280 CHECK(is_computed_)
281 << "Covariance::GetCovarianceMatrix called before Covariance::Compute";
282 CHECK(is_valid_)
283 << "Covariance::GetCovarianceMatrix called when Covariance::Compute "
284 << "returned false.";
285
286 const ProblemImpl::ParameterMap& parameter_map = problem_->parameter_map();
287 // For OpenMP compatibility we need to define these vectors in advance
288 const int num_parameters = parameters.size();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800289 std::vector<int> parameter_sizes;
290 std::vector<int> cum_parameter_size;
Austin Schuh70cc9552019-01-21 19:46:48 -0800291 parameter_sizes.reserve(num_parameters);
292 cum_parameter_size.resize(num_parameters + 1);
293 cum_parameter_size[0] = 0;
294 for (int i = 0; i < num_parameters; ++i) {
295 ParameterBlock* block =
296 FindOrDie(parameter_map, const_cast<double*>(parameters[i]));
297 if (lift_covariance_to_ambient_space) {
298 parameter_sizes.push_back(block->Size());
299 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700300 parameter_sizes.push_back(block->TangentSize());
Austin Schuh70cc9552019-01-21 19:46:48 -0800301 }
302 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800303 std::partial_sum(parameter_sizes.begin(),
304 parameter_sizes.end(),
Austin Schuh70cc9552019-01-21 19:46:48 -0800305 cum_parameter_size.begin() + 1);
306 const int max_covariance_block_size =
307 *std::max_element(parameter_sizes.begin(), parameter_sizes.end());
308 const int covariance_size = cum_parameter_size.back();
309
310 // Assemble the blocks in the covariance matrix.
311 MatrixRef covariance(covariance_matrix, covariance_size, covariance_size);
312 const int num_threads = options_.num_threads;
Austin Schuh3de38b02024-06-25 18:25:10 -0700313 auto workspace = std::make_unique<double[]>(
314 num_threads * max_covariance_block_size * max_covariance_block_size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800315
316 bool success = true;
317
318 // Technically the following code is a double nested loop where
319 // i = 1:n, j = i:n.
320 int iteration_count = (num_parameters * (num_parameters + 1)) / 2;
321 problem_->context()->EnsureMinimumThreads(num_threads);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800322 ParallelFor(problem_->context(),
323 0,
324 iteration_count,
325 num_threads,
326 [&](int thread_id, int k) {
327 int i, j;
328 LinearIndexToUpperTriangularIndex(k, num_parameters, &i, &j);
Austin Schuh70cc9552019-01-21 19:46:48 -0800329
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800330 int covariance_row_idx = cum_parameter_size[i];
331 int covariance_col_idx = cum_parameter_size[j];
332 int size_i = parameter_sizes[i];
333 int size_j = parameter_sizes[j];
334 double* covariance_block =
335 workspace.get() + thread_id * max_covariance_block_size *
336 max_covariance_block_size;
337 if (!GetCovarianceBlockInTangentOrAmbientSpace(
338 parameters[i],
339 parameters[j],
340 lift_covariance_to_ambient_space,
341 covariance_block)) {
342 success = false;
343 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800344
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800345 covariance.block(
346 covariance_row_idx, covariance_col_idx, size_i, size_j) =
347 MatrixRef(covariance_block, size_i, size_j);
Austin Schuh70cc9552019-01-21 19:46:48 -0800348
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800349 if (i != j) {
350 covariance.block(
351 covariance_col_idx, covariance_row_idx, size_j, size_i) =
352 MatrixRef(covariance_block, size_i, size_j).transpose();
353 }
354 });
Austin Schuh70cc9552019-01-21 19:46:48 -0800355 return success;
356}
357
358// Determine the sparsity pattern of the covariance matrix based on
359// the block pairs requested by the user.
360bool CovarianceImpl::ComputeCovarianceSparsity(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800361 const CovarianceBlocks& original_covariance_blocks, ProblemImpl* problem) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800362 EventLogger event_logger("CovarianceImpl::ComputeCovarianceSparsity");
363
364 // Determine an ordering for the parameter block, by sorting the
365 // parameter blocks by their pointers.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800366 std::vector<double*> all_parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800367 problem->GetParameterBlocks(&all_parameter_blocks);
368 const ProblemImpl::ParameterMap& parameter_map = problem->parameter_map();
369 std::unordered_set<ParameterBlock*> parameter_blocks_in_use;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800370 std::vector<ResidualBlock*> residual_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800371 problem->GetResidualBlocks(&residual_blocks);
372
Austin Schuh3de38b02024-06-25 18:25:10 -0700373 for (auto* residual_block : residual_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800374 parameter_blocks_in_use.insert(residual_block->parameter_blocks(),
375 residual_block->parameter_blocks() +
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800376 residual_block->NumParameterBlocks());
Austin Schuh70cc9552019-01-21 19:46:48 -0800377 }
378
379 constant_parameter_blocks_.clear();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800380 std::vector<double*>& active_parameter_blocks =
Austin Schuh70cc9552019-01-21 19:46:48 -0800381 evaluate_options_.parameter_blocks;
382 active_parameter_blocks.clear();
Austin Schuh3de38b02024-06-25 18:25:10 -0700383 for (auto* parameter_block : all_parameter_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800384 ParameterBlock* block = FindOrDie(parameter_map, parameter_block);
385 if (!block->IsConstant() && (parameter_blocks_in_use.count(block) > 0)) {
386 active_parameter_blocks.push_back(parameter_block);
387 } else {
388 constant_parameter_blocks_.insert(parameter_block);
389 }
390 }
391
392 std::sort(active_parameter_blocks.begin(), active_parameter_blocks.end());
393
394 // Compute the number of rows. Map each parameter block to the
395 // first row corresponding to it in the covariance matrix using the
396 // ordering of parameter blocks just constructed.
397 int num_rows = 0;
398 parameter_block_to_row_index_.clear();
Austin Schuh3de38b02024-06-25 18:25:10 -0700399 for (auto* parameter_block : active_parameter_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800400 const int parameter_block_size =
Austin Schuh3de38b02024-06-25 18:25:10 -0700401 problem->ParameterBlockTangentSize(parameter_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800402 parameter_block_to_row_index_[parameter_block] = num_rows;
403 num_rows += parameter_block_size;
404 }
405
406 // Compute the number of non-zeros in the covariance matrix. Along
407 // the way flip any covariance blocks which are in the lower
408 // triangular part of the matrix.
409 int num_nonzeros = 0;
410 CovarianceBlocks covariance_blocks;
Austin Schuh3de38b02024-06-25 18:25:10 -0700411 for (const auto& block_pair : original_covariance_blocks) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800412 if (constant_parameter_blocks_.count(block_pair.first) > 0 ||
413 constant_parameter_blocks_.count(block_pair.second) > 0) {
414 continue;
415 }
416
417 int index1 = FindOrDie(parameter_block_to_row_index_, block_pair.first);
418 int index2 = FindOrDie(parameter_block_to_row_index_, block_pair.second);
Austin Schuh3de38b02024-06-25 18:25:10 -0700419 const int size1 = problem->ParameterBlockTangentSize(block_pair.first);
420 const int size2 = problem->ParameterBlockTangentSize(block_pair.second);
Austin Schuh70cc9552019-01-21 19:46:48 -0800421 num_nonzeros += size1 * size2;
422
423 // Make sure we are constructing a block upper triangular matrix.
424 if (index1 > index2) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800425 covariance_blocks.push_back(
426 std::make_pair(block_pair.second, block_pair.first));
Austin Schuh70cc9552019-01-21 19:46:48 -0800427 } else {
428 covariance_blocks.push_back(block_pair);
429 }
430 }
431
Austin Schuh3de38b02024-06-25 18:25:10 -0700432 if (covariance_blocks.empty()) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800433 VLOG(2) << "No non-zero covariance blocks found";
Austin Schuh3de38b02024-06-25 18:25:10 -0700434 covariance_matrix_ = nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800435 return true;
436 }
437
438 // Sort the block pairs. As a consequence we get the covariance
439 // blocks as they will occur in the CompressedRowSparseMatrix that
440 // will store the covariance.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800441 std::sort(covariance_blocks.begin(), covariance_blocks.end());
Austin Schuh70cc9552019-01-21 19:46:48 -0800442
443 // Fill the sparsity pattern of the covariance matrix.
Austin Schuh3de38b02024-06-25 18:25:10 -0700444 covariance_matrix_ = std::make_unique<CompressedRowSparseMatrix>(
445 num_rows, num_rows, num_nonzeros);
Austin Schuh70cc9552019-01-21 19:46:48 -0800446
447 int* rows = covariance_matrix_->mutable_rows();
448 int* cols = covariance_matrix_->mutable_cols();
449
450 // Iterate over parameter blocks and in turn over the rows of the
451 // covariance matrix. For each parameter block, look in the upper
452 // triangular part of the covariance matrix to see if there are any
453 // blocks requested by the user. If this is the case then fill out a
454 // set of compressed rows corresponding to this parameter block.
455 //
456 // The key thing that makes this loop work is the fact that the
457 // row/columns of the covariance matrix are ordered by the pointer
458 // values of the parameter blocks. Thus iterating over the keys of
459 // parameter_block_to_row_index_ corresponds to iterating over the
460 // rows of the covariance matrix in order.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800461 int i = 0; // index into covariance_blocks.
Austin Schuh70cc9552019-01-21 19:46:48 -0800462 int cursor = 0; // index into the covariance matrix.
463 for (const auto& entry : parameter_block_to_row_index_) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800464 const double* row_block = entry.first;
Austin Schuh3de38b02024-06-25 18:25:10 -0700465 const int row_block_size = problem->ParameterBlockTangentSize(row_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800466 int row_begin = entry.second;
467
468 // Iterate over the covariance blocks contained in this row block
469 // and count the number of columns in this row block.
470 int num_col_blocks = 0;
Austin Schuh70cc9552019-01-21 19:46:48 -0800471 for (int j = i; j < covariance_blocks.size(); ++j, ++num_col_blocks) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800472 const std::pair<const double*, const double*>& block_pair =
Austin Schuh70cc9552019-01-21 19:46:48 -0800473 covariance_blocks[j];
474 if (block_pair.first != row_block) {
475 break;
476 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800477 }
478
479 // Fill out all the compressed rows for this parameter block.
480 for (int r = 0; r < row_block_size; ++r) {
481 rows[row_begin + r] = cursor;
482 for (int c = 0; c < num_col_blocks; ++c) {
483 const double* col_block = covariance_blocks[i + c].second;
Austin Schuh3de38b02024-06-25 18:25:10 -0700484 const int col_block_size =
485 problem->ParameterBlockTangentSize(col_block);
Austin Schuh70cc9552019-01-21 19:46:48 -0800486 int col_begin = FindOrDie(parameter_block_to_row_index_, col_block);
487 for (int k = 0; k < col_block_size; ++k) {
488 cols[cursor++] = col_begin++;
489 }
490 }
491 }
492
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800493 i += num_col_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800494 }
495
496 rows[num_rows] = cursor;
497 return true;
498}
499
500bool CovarianceImpl::ComputeCovarianceValues() {
501 if (options_.algorithm_type == DENSE_SVD) {
502 return ComputeCovarianceValuesUsingDenseSVD();
503 }
504
505 if (options_.algorithm_type == SPARSE_QR) {
506 if (options_.sparse_linear_algebra_library_type == EIGEN_SPARSE) {
507 return ComputeCovarianceValuesUsingEigenSparseQR();
508 }
509
510 if (options_.sparse_linear_algebra_library_type == SUITE_SPARSE) {
511#if !defined(CERES_NO_SUITESPARSE)
512 return ComputeCovarianceValuesUsingSuiteSparseQR();
513#else
514 LOG(ERROR) << "SuiteSparse is required to use the SPARSE_QR algorithm "
515 << "with "
516 << "Covariance::Options::sparse_linear_algebra_library_type "
517 << "= SUITE_SPARSE.";
518 return false;
519#endif
520 }
521
522 LOG(ERROR) << "Unsupported "
523 << "Covariance::Options::sparse_linear_algebra_library_type "
524 << "= "
525 << SparseLinearAlgebraLibraryTypeToString(
526 options_.sparse_linear_algebra_library_type);
527 return false;
528 }
529
530 LOG(ERROR) << "Unsupported Covariance::Options::algorithm_type = "
531 << CovarianceAlgorithmTypeToString(options_.algorithm_type);
532 return false;
533}
534
535bool CovarianceImpl::ComputeCovarianceValuesUsingSuiteSparseQR() {
536 EventLogger event_logger(
537 "CovarianceImpl::ComputeCovarianceValuesUsingSparseQR");
538
539#ifndef CERES_NO_SUITESPARSE
Austin Schuh3de38b02024-06-25 18:25:10 -0700540 if (covariance_matrix_ == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800541 // Nothing to do, all zeros covariance matrix.
542 return true;
543 }
544
545 CRSMatrix jacobian;
Austin Schuh3de38b02024-06-25 18:25:10 -0700546 problem_->Evaluate(evaluate_options_, nullptr, nullptr, nullptr, &jacobian);
Austin Schuh70cc9552019-01-21 19:46:48 -0800547 event_logger.AddEvent("Evaluate");
548
549 // Construct a compressed column form of the Jacobian.
550 const int num_rows = jacobian.num_rows;
551 const int num_cols = jacobian.num_cols;
552 const int num_nonzeros = jacobian.values.size();
553
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800554 std::vector<SuiteSparse_long> transpose_rows(num_cols + 1, 0);
555 std::vector<SuiteSparse_long> transpose_cols(num_nonzeros, 0);
556 std::vector<double> transpose_values(num_nonzeros, 0);
Austin Schuh70cc9552019-01-21 19:46:48 -0800557
558 for (int idx = 0; idx < num_nonzeros; ++idx) {
559 transpose_rows[jacobian.cols[idx] + 1] += 1;
560 }
561
562 for (int i = 1; i < transpose_rows.size(); ++i) {
563 transpose_rows[i] += transpose_rows[i - 1];
564 }
565
566 for (int r = 0; r < num_rows; ++r) {
567 for (int idx = jacobian.rows[r]; idx < jacobian.rows[r + 1]; ++idx) {
568 const int c = jacobian.cols[idx];
569 const int transpose_idx = transpose_rows[c];
570 transpose_cols[transpose_idx] = r;
571 transpose_values[transpose_idx] = jacobian.values[idx];
572 ++transpose_rows[c];
573 }
574 }
575
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800576 for (int i = transpose_rows.size() - 1; i > 0; --i) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800577 transpose_rows[i] = transpose_rows[i - 1];
578 }
579 transpose_rows[0] = 0;
580
581 cholmod_sparse cholmod_jacobian;
582 cholmod_jacobian.nrow = num_rows;
583 cholmod_jacobian.ncol = num_cols;
584 cholmod_jacobian.nzmax = num_nonzeros;
Austin Schuh3de38b02024-06-25 18:25:10 -0700585 cholmod_jacobian.nz = nullptr;
586 cholmod_jacobian.p = reinterpret_cast<void*>(transpose_rows.data());
587 cholmod_jacobian.i = reinterpret_cast<void*>(transpose_cols.data());
588 cholmod_jacobian.x = reinterpret_cast<void*>(transpose_values.data());
589 cholmod_jacobian.z = nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800590 cholmod_jacobian.stype = 0; // Matrix is not symmetric.
591 cholmod_jacobian.itype = CHOLMOD_LONG;
592 cholmod_jacobian.xtype = CHOLMOD_REAL;
593 cholmod_jacobian.dtype = CHOLMOD_DOUBLE;
594 cholmod_jacobian.sorted = 1;
595 cholmod_jacobian.packed = 1;
596
597 cholmod_common cc;
598 cholmod_l_start(&cc);
599
Austin Schuh3de38b02024-06-25 18:25:10 -0700600 cholmod_sparse* R = nullptr;
601 SuiteSparse_long* permutation = nullptr;
Austin Schuh70cc9552019-01-21 19:46:48 -0800602
603 // Compute a Q-less QR factorization of the Jacobian. Since we are
604 // only interested in inverting J'J = R'R, we do not need Q. This
605 // saves memory and gives us R as a permuted compressed column
606 // sparse matrix.
607 //
608 // TODO(sameeragarwal): Currently the symbolic factorization and the
609 // numeric factorization is done at the same time, and this does not
610 // explicitly account for the block column and row structure in the
611 // matrix. When using AMD, we have observed in the past that
612 // computing the ordering with the block matrix is significantly
613 // more efficient, both in runtime as well as the quality of
614 // ordering computed. So, it maybe worth doing that analysis
615 // separately.
Austin Schuh3de38b02024-06-25 18:25:10 -0700616 const SuiteSparse_long rank = SuiteSparseQR<double>(
617 SPQR_ORDERING_BESTAMD,
618 options_.column_pivot_threshold < 0 ? SPQR_DEFAULT_TOL
619 : options_.column_pivot_threshold,
620 static_cast<int64_t>(cholmod_jacobian.ncol),
621 &cholmod_jacobian,
622 &R,
623 &permutation,
624 &cc);
Austin Schuh70cc9552019-01-21 19:46:48 -0800625 event_logger.AddEvent("Numeric Factorization");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800626 if (R == nullptr) {
627 LOG(ERROR) << "Something is wrong. SuiteSparseQR returned R = nullptr.";
628 free(permutation);
629 cholmod_l_finish(&cc);
630 return false;
631 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800632
633 if (rank < cholmod_jacobian.ncol) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700634 LOG(WARNING) << "Jacobian matrix is rank deficient. "
635 << "Number of columns: " << cholmod_jacobian.ncol
636 << " rank: " << rank;
Austin Schuh70cc9552019-01-21 19:46:48 -0800637 free(permutation);
638 cholmod_l_free_sparse(&R, &cc);
639 cholmod_l_finish(&cc);
640 return false;
641 }
642
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800643 std::vector<int> inverse_permutation(num_cols);
Austin Schuh70cc9552019-01-21 19:46:48 -0800644 if (permutation) {
645 for (SuiteSparse_long i = 0; i < num_cols; ++i) {
646 inverse_permutation[permutation[i]] = i;
647 }
648 } else {
649 for (SuiteSparse_long i = 0; i < num_cols; ++i) {
650 inverse_permutation[i] = i;
651 }
652 }
653
654 const int* rows = covariance_matrix_->rows();
655 const int* cols = covariance_matrix_->cols();
656 double* values = covariance_matrix_->mutable_values();
657
658 // The following loop exploits the fact that the i^th column of A^{-1}
659 // is given by the solution to the linear system
660 //
661 // A x = e_i
662 //
663 // where e_i is a vector with e(i) = 1 and all other entries zero.
664 //
665 // Since the covariance matrix is symmetric, the i^th row and column
666 // are equal.
667 const int num_threads = options_.num_threads;
Austin Schuh3de38b02024-06-25 18:25:10 -0700668 auto workspace = std::make_unique<double[]>(num_threads * num_cols);
Austin Schuh70cc9552019-01-21 19:46:48 -0800669
670 problem_->context()->EnsureMinimumThreads(num_threads);
671 ParallelFor(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800672 problem_->context(), 0, num_cols, num_threads, [&](int thread_id, int r) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800673 const int row_begin = rows[r];
674 const int row_end = rows[r + 1];
675 if (row_end != row_begin) {
676 double* solution = workspace.get() + thread_id * num_cols;
677 SolveRTRWithSparseRHS<SuiteSparse_long>(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800678 num_cols,
679 static_cast<SuiteSparse_long*>(R->i),
680 static_cast<SuiteSparse_long*>(R->p),
681 static_cast<double*>(R->x),
682 inverse_permutation[r],
683 solution);
Austin Schuh70cc9552019-01-21 19:46:48 -0800684 for (int idx = row_begin; idx < row_end; ++idx) {
685 const int c = cols[idx];
686 values[idx] = solution[inverse_permutation[c]];
687 }
688 }
689 });
690
691 free(permutation);
692 cholmod_l_free_sparse(&R, &cc);
693 cholmod_l_finish(&cc);
694 event_logger.AddEvent("Inversion");
695 return true;
696
697#else // CERES_NO_SUITESPARSE
698
699 return false;
700
701#endif // CERES_NO_SUITESPARSE
702}
703
704bool CovarianceImpl::ComputeCovarianceValuesUsingDenseSVD() {
705 EventLogger event_logger(
706 "CovarianceImpl::ComputeCovarianceValuesUsingDenseSVD");
Austin Schuh3de38b02024-06-25 18:25:10 -0700707 if (covariance_matrix_ == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800708 // Nothing to do, all zeros covariance matrix.
709 return true;
710 }
711
712 CRSMatrix jacobian;
Austin Schuh3de38b02024-06-25 18:25:10 -0700713 problem_->Evaluate(evaluate_options_, nullptr, nullptr, nullptr, &jacobian);
Austin Schuh70cc9552019-01-21 19:46:48 -0800714 event_logger.AddEvent("Evaluate");
715
716 Matrix dense_jacobian(jacobian.num_rows, jacobian.num_cols);
717 dense_jacobian.setZero();
718 for (int r = 0; r < jacobian.num_rows; ++r) {
719 for (int idx = jacobian.rows[r]; idx < jacobian.rows[r + 1]; ++idx) {
720 const int c = jacobian.cols[idx];
721 dense_jacobian(r, c) = jacobian.values[idx];
722 }
723 }
724 event_logger.AddEvent("ConvertToDenseMatrix");
725
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800726 Eigen::BDCSVD<Matrix> svd(dense_jacobian,
727 Eigen::ComputeThinU | Eigen::ComputeThinV);
Austin Schuh70cc9552019-01-21 19:46:48 -0800728
729 event_logger.AddEvent("SingularValueDecomposition");
730
731 const Vector singular_values = svd.singularValues();
732 const int num_singular_values = singular_values.rows();
733 Vector inverse_squared_singular_values(num_singular_values);
734 inverse_squared_singular_values.setZero();
735
736 const double max_singular_value = singular_values[0];
737 const double min_singular_value_ratio =
738 sqrt(options_.min_reciprocal_condition_number);
739
740 const bool automatic_truncation = (options_.null_space_rank < 0);
741 const int max_rank = std::min(num_singular_values,
742 num_singular_values - options_.null_space_rank);
743
744 // Compute the squared inverse of the singular values. Truncate the
745 // computation based on min_singular_value_ratio and
746 // null_space_rank. When either of these two quantities are active,
747 // the resulting covariance matrix is a Moore-Penrose inverse
748 // instead of a regular inverse.
749 for (int i = 0; i < max_rank; ++i) {
750 const double singular_value_ratio = singular_values[i] / max_singular_value;
751 if (singular_value_ratio < min_singular_value_ratio) {
752 // Since the singular values are in decreasing order, if
753 // automatic truncation is enabled, then from this point on
754 // all values will fail the ratio test and there is nothing to
755 // do in this loop.
756 if (automatic_truncation) {
757 break;
758 } else {
759 LOG(ERROR) << "Error: Covariance matrix is near rank deficient "
760 << "and the user did not specify a non-zero"
761 << "Covariance::Options::null_space_rank "
762 << "to enable the computation of a Pseudo-Inverse. "
763 << "Reciprocal condition number: "
764 << singular_value_ratio * singular_value_ratio << " "
765 << "min_reciprocal_condition_number: "
766 << options_.min_reciprocal_condition_number;
767 return false;
768 }
769 }
770
771 inverse_squared_singular_values[i] =
772 1.0 / (singular_values[i] * singular_values[i]);
773 }
774
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800775 Matrix dense_covariance = svd.matrixV() *
776 inverse_squared_singular_values.asDiagonal() *
777 svd.matrixV().transpose();
Austin Schuh70cc9552019-01-21 19:46:48 -0800778 event_logger.AddEvent("PseudoInverse");
779
780 const int num_rows = covariance_matrix_->num_rows();
781 const int* rows = covariance_matrix_->rows();
782 const int* cols = covariance_matrix_->cols();
783 double* values = covariance_matrix_->mutable_values();
784
785 for (int r = 0; r < num_rows; ++r) {
786 for (int idx = rows[r]; idx < rows[r + 1]; ++idx) {
787 const int c = cols[idx];
788 values[idx] = dense_covariance(r, c);
789 }
790 }
791 event_logger.AddEvent("CopyToCovarianceMatrix");
792 return true;
793}
794
795bool CovarianceImpl::ComputeCovarianceValuesUsingEigenSparseQR() {
796 EventLogger event_logger(
797 "CovarianceImpl::ComputeCovarianceValuesUsingEigenSparseQR");
Austin Schuh3de38b02024-06-25 18:25:10 -0700798 if (covariance_matrix_ == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800799 // Nothing to do, all zeros covariance matrix.
800 return true;
801 }
802
803 CRSMatrix jacobian;
Austin Schuh3de38b02024-06-25 18:25:10 -0700804 problem_->Evaluate(evaluate_options_, nullptr, nullptr, nullptr, &jacobian);
Austin Schuh70cc9552019-01-21 19:46:48 -0800805 event_logger.AddEvent("Evaluate");
806
Austin Schuh3de38b02024-06-25 18:25:10 -0700807 using EigenSparseMatrix = Eigen::SparseMatrix<double, Eigen::ColMajor>;
Austin Schuh70cc9552019-01-21 19:46:48 -0800808
809 // Convert the matrix to column major order as required by SparseQR.
810 EigenSparseMatrix sparse_jacobian =
Austin Schuh3de38b02024-06-25 18:25:10 -0700811 Eigen::Map<Eigen::SparseMatrix<double, Eigen::RowMajor>>(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800812 jacobian.num_rows,
813 jacobian.num_cols,
Austin Schuh70cc9552019-01-21 19:46:48 -0800814 static_cast<int>(jacobian.values.size()),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800815 jacobian.rows.data(),
816 jacobian.cols.data(),
817 jacobian.values.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800818 event_logger.AddEvent("ConvertToSparseMatrix");
819
Austin Schuh3de38b02024-06-25 18:25:10 -0700820 Eigen::SparseQR<EigenSparseMatrix, Eigen::COLAMDOrdering<int>> qr;
821 if (options_.column_pivot_threshold > 0) {
822 qr.setPivotThreshold(options_.column_pivot_threshold);
823 }
824
825 qr.compute(sparse_jacobian);
Austin Schuh70cc9552019-01-21 19:46:48 -0800826 event_logger.AddEvent("QRDecomposition");
827
Austin Schuh3de38b02024-06-25 18:25:10 -0700828 if (qr.info() != Eigen::Success) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800829 LOG(ERROR) << "Eigen::SparseQR decomposition failed.";
830 return false;
831 }
832
Austin Schuh3de38b02024-06-25 18:25:10 -0700833 if (qr.rank() < jacobian.num_cols) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800834 LOG(ERROR) << "Jacobian matrix is rank deficient. "
835 << "Number of columns: " << jacobian.num_cols
Austin Schuh3de38b02024-06-25 18:25:10 -0700836 << " rank: " << qr.rank();
Austin Schuh70cc9552019-01-21 19:46:48 -0800837 return false;
838 }
839
840 const int* rows = covariance_matrix_->rows();
841 const int* cols = covariance_matrix_->cols();
842 double* values = covariance_matrix_->mutable_values();
843
844 // Compute the inverse column permutation used by QR factorization.
845 Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> inverse_permutation =
Austin Schuh3de38b02024-06-25 18:25:10 -0700846 qr.colsPermutation().inverse();
Austin Schuh70cc9552019-01-21 19:46:48 -0800847
848 // The following loop exploits the fact that the i^th column of A^{-1}
849 // is given by the solution to the linear system
850 //
851 // A x = e_i
852 //
853 // where e_i is a vector with e(i) = 1 and all other entries zero.
854 //
855 // Since the covariance matrix is symmetric, the i^th row and column
856 // are equal.
857 const int num_cols = jacobian.num_cols;
858 const int num_threads = options_.num_threads;
Austin Schuh3de38b02024-06-25 18:25:10 -0700859 auto workspace = std::make_unique<double[]>(num_threads * num_cols);
Austin Schuh70cc9552019-01-21 19:46:48 -0800860
861 problem_->context()->EnsureMinimumThreads(num_threads);
862 ParallelFor(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800863 problem_->context(), 0, num_cols, num_threads, [&](int thread_id, int r) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800864 const int row_begin = rows[r];
865 const int row_end = rows[r + 1];
866 if (row_end != row_begin) {
867 double* solution = workspace.get() + thread_id * num_cols;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800868 SolveRTRWithSparseRHS<int>(num_cols,
Austin Schuh3de38b02024-06-25 18:25:10 -0700869 qr.matrixR().innerIndexPtr(),
870 qr.matrixR().outerIndexPtr(),
871 &qr.matrixR().data().value(0),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800872 inverse_permutation.indices().coeff(r),
873 solution);
Austin Schuh70cc9552019-01-21 19:46:48 -0800874
875 // Assign the values of the computed covariance using the
876 // inverse permutation used in the QR factorization.
877 for (int idx = row_begin; idx < row_end; ++idx) {
878 const int c = cols[idx];
879 values[idx] = solution[inverse_permutation.indices().coeff(c)];
880 }
881 }
882 });
883
884 event_logger.AddEvent("Inverse");
885
886 return true;
887}
888
Austin Schuh3de38b02024-06-25 18:25:10 -0700889} // namespace ceres::internal