gtsam
Loading...
Searching...
No Matches
BatchFactor.h
Go to the documentation of this file.
1/* ----------------------------------------------------------------------------
2
3 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
4 * Atlanta, Georgia 30332-0415
5 * All Rights Reserved
6 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)
7
8 * See LICENSE for the license information
9
10 * -------------------------------------------------------------------------- */
11
19
20#pragma once
21
22#include <gtsam/base/Testable.h>
28
29#include <Eigen/StdVector>
30#include <algorithm>
31#include <cassert>
32#include <map>
33#include <type_traits>
34#include <vector>
35
36namespace gtsam {
37
38namespace detail {
39
48template <typename FactorType, typename K1, typename K2, typename Meas,
49 typename Model, typename... Args>
50static FactorType createFactor(K1 k1, K2 k2, const Meas& z, const Model& model,
51 Args&&... args) {
52 if constexpr (std::is_constructible_v<FactorType, K1, K2, Meas, Model,
53 Args...>) {
54 return FactorType(k1, k2, z, model, std::forward<Args>(args)...);
55 } else if constexpr (std::is_constructible_v<FactorType, Meas, Model, K1, K2,
56 Args...>) {
57 return FactorType(z, model, k1, k2, std::forward<Args>(args)...);
58 } else if constexpr (std::is_constructible_v<FactorType, K1, K2, Meas,
59 Args..., Model>) {
60 return FactorType(k1, k2, z, std::forward<Args>(args)..., model);
61 } else {
62 // This static_assert will trigger if none of the above match.
63 // We repeat the check to produce a readable error message.
64 static_assert(
65 std::is_constructible_v<FactorType, K1, K2, Meas, Model, Args...>,
66 "BatchFactor: Could not find a matching constructor for FactorType. "
67 "Tried: (K1, K2, Z, Model, Args...), (Z, Model, K1, K2, Args...), (K1, "
68 "K2, Z, Args..., Model)");
69 return FactorType(k1, k2, z, model, std::forward<Args>(args)...);
70 }
71}
72
73} // namespace detail
74
114template <typename FactorType, int ErrorDim>
116 public:
117 // Static assertion to ensure FactorType derives from NoiseModelFactor
118 static_assert(std::is_base_of<NoiseModelFactor, FactorType>::value,
119 "FactorType must derive from NoiseModelFactor");
120
121 using Base = NonlinearFactor;
123 using shared_ptr = std::shared_ptr<This>;
124 using FactorVector =
125 std::vector<FactorType, Eigen::aligned_allocator<FactorType>>;
126
127 private:
128 using Allocator = typename FactorVector::allocator_type;
129 FactorVector factors_;
130 struct KeyInfo {
131 Key key;
132 int dim;
133 size_t slot;
134 DenseIndex offset;
135 };
136 std::vector<KeyInfo> keyInfo_;
137 std::vector<std::array<DenseIndex, FactorType::N>> indices_;
138 bool useHessianFactor_{false};
139 bool allDimensionsFixed_{false};
140 bool hasConstrainedNoiseModel_{false};
141 bool allNoiseModelsAreUnit_{true};
142 std::vector<size_t> keyDimensions_;
143
144 template <size_t... Is>
145 static constexpr bool hasFixedDimensions(std::index_sequence<Is...>) {
146 return (
147 (traits<typename FactorType::template ValueType<Is + 1>>::dimension !=
148 Eigen::Dynamic) &&
149 ...);
150 }
151
152 template <size_t... Is>
153 size_t keyDimensionFromSlot(const Key key, size_t slot, const Values& values,
154 std::index_sequence<Is...>) const {
155 const std::array<size_t, FactorType::N> dims = {
157 values.at<typename FactorType::template ValueType<Is + 1>>(key))...};
158
159 if (slot >= dims.size()) {
160 throw std::runtime_error(
161 "BatchFactor::keyDimensionFromSlot: slot out of range.");
162 }
163 const size_t dimension = dims[slot];
164 return dimension;
165 }
166
167 const std::vector<size_t>& keyDimensions() const {
168 if (!allDimensionsFixed_) {
169 throw std::runtime_error(
170 "BatchFactor::keyDimensions: cached dimensions unavailable for dynamic "
171 "key types.");
172 }
173 return keyDimensions_;
174 }
175
176 std::vector<size_t> keyDimensions(const Values& values) const {
177 std::vector<size_t> dims;
178 dims.reserve(keyInfo_.size());
179 for (const auto& info : keyInfo_) {
180 if (info.dim >= 0) {
181 dims.push_back(static_cast<size_t>(info.dim));
182 } else {
183 constexpr auto slots = std::make_index_sequence<FactorType::N>{};
184 const size_t dim = keyDimensionFromSlot(info.key, info.slot, values, slots);
185 if (dim == 0) {
186 throw std::runtime_error(
187 "BatchFactor::keyDimensions: cannot determine dynamic key "
188 "dimension.");
189 }
190 dims.push_back(dim);
191 }
192 }
193 return dims;
194 }
195
196 std::shared_ptr<JacobianFactor> linearizeToJacobianFactor(
197 const Values& values) const {
198 const size_t total_rows = factors_.size() * ErrorDim;
199 const std::vector<size_t>& keyDims =
200 allDimensionsFixed_ ? keyDimensions() : keyDimensions(values);
201 VerticalBlockMatrix Ab(keyDims, total_rows, true);
202 Ab.matrix().setZero();
203
204 std::vector<Matrix> H(FactorType::N);
205 Vector rowSigmas;
206 Vector rowMus;
207 if (hasConstrainedNoiseModel_) {
208 rowSigmas = Vector::Ones(total_rows);
209 rowMus = Vector::Constant(total_rows, 1000.0);
210 }
211 for (size_t i = 0; i < factors_.size(); ++i) {
212 const auto& factor = factors_[i];
213 const size_t row_start = i * ErrorDim;
214 Vector rhs = -factor.unwhitenedError(values, H);
215
216 const auto noise = factor.noiseModel();
217 if (!allNoiseModelsAreUnit_ && noise && !noise->isUnit()) {
218 factor.noiseModel()->WhitenSystem(H, rhs);
219 }
220
221 if (hasConstrainedNoiseModel_ && noise && noise->isConstrained()) {
222 auto constrainedModel =
223 std::dynamic_pointer_cast<noiseModel::Constrained>(noise);
224 if (!constrainedModel) {
225 throw std::runtime_error(
226 "BatchFactor::linearizeToJacobianFactor: unsupported constrained "
227 "noise model type.");
228 }
229 for (size_t r = 0; r < ErrorDim; ++r) {
230 const size_t row = row_start + r;
231 if (constrainedModel->constrained(r)) {
232 rowMus[row] = constrainedModel->mu()[r];
233 rowSigmas[row] = 0.0;
234 } else {
235 rowSigmas[row] = 1.0;
236 }
237 }
238 }
239
240 const auto& indices_i = indices_[i];
241 for (size_t k = 0; k < FactorType::N; ++k) {
242 const DenseIndex index = indices_i[k];
243 Ab(index).block(row_start, 0, ErrorDim, H[k].cols()) = H[k];
244 }
245 Ab(keys().size()).block(row_start, 0, ErrorDim, 1) = rhs;
246 }
247
248 SharedDiagonal jacobianModel = noiseModel::Unit::Create(total_rows);
249 if (hasConstrainedNoiseModel_) {
250 jacobianModel = std::static_pointer_cast<noiseModel::Diagonal>(
251 noiseModel::Constrained::MixedSigmas(rowMus, rowSigmas));
252 }
253 return std::make_shared<JacobianFactor>(
254 keys(), std::move(Ab), jacobianModel);
255 }
256
257 template <size_t... Is>
258 std::shared_ptr<GaussianFactor> linearizeToBatchJacobian(
259 const Values& values, std::index_sequence<Is...>) const {
260 (void)values;
261 using CompactFactor = BatchJacobianFactor<
262 ErrorDim,
264 auto batch = std::make_shared<CompactFactor>(keys(), keyDimensions());
265 batch->reserve(factors_.size());
266
267 std::vector<Matrix> H(FactorType::N);
268 for (size_t i = 0; i < factors_.size(); ++i) {
269 const auto& factor = factors_[i];
270 Vector rhs = -factor.unwhitenedError(values, H);
271
272 if (factor.noiseModel() && !factor.noiseModel()->isUnit()) {
273 factor.noiseModel()->WhitenSystem(H, rhs);
274 }
275
276 batch->addRow(indices_[i], H, rhs);
277 }
278 return batch;
279 }
280
281 public:
284
286 BatchFactor() = default;
287
289 const FactorVector& factors() const { return factors_; }
290
292 size_t numFactors() const { return factors_.size(); }
293
295 explicit BatchFactor(std::vector<FactorType, Allocator>&& factors)
296 : factors_(std::move(factors)) {
297 updateKeys();
298 }
299
301 explicit BatchFactor(const std::vector<FactorType, Allocator>& factors)
302 : factors_(factors) {
303 updateKeys();
304 }
305
307 explicit BatchFactor(const std::vector<FactorType>& factors) {
308 factors_.reserve(factors.size());
309 factors_.assign(factors.begin(), factors.end());
310 updateKeys();
311 }
312
314 explicit BatchFactor(std::vector<FactorType>&& factors) {
315 factors_.reserve(factors.size());
316 for (auto&& f : factors) {
317 factors_.push_back(std::move(f));
318 }
319 updateKeys();
320 }
321
332 template <typename Measurement, typename... Args>
333 BatchFactor(Key key1, const std::map<Key, Measurement>& measurements,
334 const SharedNoiseModel& model, Args&&... args) {
335 factors_.reserve(measurements.size());
336 for (const auto& [key2, z] : measurements) {
337 factors_.push_back(detail::createFactor<FactorType>(
338 key1, key2, z, model, std::forward<Args>(args)...));
339 }
340 updateKeys();
341 }
342
353 template <typename Measurement, typename... Args>
354 BatchFactor(const std::map<Key, Measurement>& measurements, Key key2,
355 const SharedNoiseModel& model, Args&&... args) {
356 factors_.reserve(measurements.size());
357 for (const auto& [key1, z] : measurements) {
358 factors_.push_back(detail::createFactor<FactorType>(
359 key1, key2, z, model, std::forward<Args>(args)...));
360 }
361 updateKeys();
362 }
363
367
369 void print(
370 const std::string& s = "",
371 const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override {
372 Base::print(s, keyFormatter);
373 std::cout << "BatchFactor with " << factors_.size()
374 << " factors:" << std::endl;
375 for (const auto& f : factors_) {
376 f.print("", keyFormatter);
377 }
378 }
379
381 bool equals(const NonlinearFactor& f, double tol = 1e-9) const override {
382 const This* p = dynamic_cast<const This*>(&f);
383 if (!p || factors_.size() != p->factors_.size()) return false;
384 for (size_t i = 0; i < factors_.size(); ++i) {
385 if (!factors_[i].equals(p->factors_[i], tol)) return false;
386 }
387 return true;
388 }
389
393
398 double error(const Values& c) const override {
399 double total_error = 0.0;
400 for (const auto& f : factors_) {
401 total_error += f.error(c);
402 }
403 return total_error;
404 }
405
407 size_t dim() const override { return factors_.size() * ErrorDim; }
408
409 void setUseHessianFactor(bool flag) { useHessianFactor_ = flag; }
410
420 std::shared_ptr<GaussianFactor> linearize(
421 const Values& values) const override {
422 if (factors_.empty()) return std::make_shared<JacobianFactor>();
423
424 if (useHessianFactor_) {
425 auto jacobian = linearizeToJacobianFactor(values);
426 return std::make_shared<HessianFactor>(*jacobian);
427 }
428
429 constexpr auto factorSlots = std::make_index_sequence<FactorType::N>{};
430 if constexpr (hasFixedDimensions(factorSlots)) {
431 if (!hasConstrainedNoiseModel_) {
432 return linearizeToBatchJacobian(values, factorSlots);
433 }
434 }
435 return linearizeToJacobianFactor(values);
436 }
437
439 template <size_t... Is>
440 void collectKeys(const FactorType& f, std::index_sequence<Is...>) {
441 (keyInfo_.push_back(KeyInfo{
442 f.keys()[Is],
444 0}),
445 ...);
446 }
447
449 void updateKeys() {
450 constexpr auto factorSlots = std::make_index_sequence<FactorType::N>{};
451 allDimensionsFixed_ = hasFixedDimensions(factorSlots);
452 hasConstrainedNoiseModel_ = false;
453 allNoiseModelsAreUnit_ = true;
454 keyDimensions_.clear();
455 // 1. Collect all keys and their dimensions
456 keyInfo_.clear();
457 keyInfo_.reserve(factors_.size() * FactorType::N);
458
459 for (const auto& f : factors_) {
460 collectKeys(f, std::make_index_sequence<FactorType::N>{});
461 }
462
463 // 2. Sort and remove duplicates to get unique keys
464 std::sort(keyInfo_.begin(), keyInfo_.end(),
465 [](const KeyInfo& a, const KeyInfo& b) { return a.key < b.key; });
466 auto isDuplicate = [](const KeyInfo& a, const KeyInfo& b) {
467 if (a.key != b.key) return false;
468 assert(a.dim == b.dim);
469 return true;
470 };
471 auto last = std::unique(keyInfo_.begin(), keyInfo_.end(), isDuplicate);
472 keyInfo_.erase(last, keyInfo_.end());
473
474 // 3. Fill keys_ and key information
475 keys_.clear();
476 keys_.reserve(keyInfo_.size());
477 DenseIndex offset = 0;
478 for (auto& info : keyInfo_) {
479 info.offset = offset;
480 keys_.push_back(info.key);
481 if (info.dim >= 0) {
482 offset += static_cast<DenseIndex>(info.dim);
483 }
484 if (allDimensionsFixed_ && info.dim >= 0) {
485 keyDimensions_.push_back(static_cast<size_t>(info.dim));
486 }
487 }
488
489 // Track noise-model traits once at construction time.
490 for (const auto& factor : factors_) {
491 const auto& model = factor.noiseModel();
492 if (!model) continue;
493 hasConstrainedNoiseModel_ |= model->isConstrained();
494 allNoiseModelsAreUnit_ &= model->isUnit();
495 }
496
497 // 4. Cache factor indices
498 // Since keys_ is sorted, we can use binary search
499 indices_.clear();
500 indices_.reserve(factors_.size());
501 for (const auto& f : factors_) {
502 std::array<DenseIndex, FactorType::N> indices_i;
503 for (size_t k = 0; k < FactorType::N; ++k) {
504 auto it = std::lower_bound(keys_.begin(), keys_.end(), f.keys()[k]);
505 indices_i[k] = std::distance(keys_.begin(), it);
506 }
507 indices_.push_back(indices_i);
508 }
509 }
510};
511
512} // namespace gtsam
Concept check for values that can be used in unit tests.
Contains the HessianFactor class, a general quadratic factor.
Row-sparse fixed-dimension batch Jacobian factor.
Non-linear factor base classes.
STL namespace.
Global functions in a separate testing namespace.
Definition chartTesting.h:28
KeyFormatter DefaultKeyFormatter
Assign default key formatter.
Definition Key.cpp:30
ptrdiff_t DenseIndex
The index type for Eigen objects.
Definition types.h:49
std::function< std::string(Key)> KeyFormatter
Typedef for a function to format a key, i.e. to convert it to a string.
Definition Key.h:35
noiseModel::Base::shared_ptr SharedNoiseModel
Aliases.
Definition NoiseModel.h:846
std::uint64_t Key
Integer nonlinear key type.
Definition types.h:43
A manifold defines a space in which there is a notion of a linear tangent space that can be centered ...
Definition Group.h:37
This class stores a dense matrix and allows it to be accessed as a collection of vertical blocks.
Definition VerticalBlockMatrix.h:47
const Matrix & matrix() const
Access to full matrix (including any portions excluded by rowStart(), rowEnd(), and firstBlock()).
Definition VerticalBlockMatrix.h:278
const KeyVector & keys() const
Access the factor's involved variable keys.
Definition Factor.h:143
KeyVector keys_
The keys involved in this factor.
Definition Factor.h:88
size_t size() const
Definition Factor.h:160
Fixed-dimension row-sparse batch Jacobian factor.
Definition BatchJacobianFactor.h:351
static shared_ptr MixedSigmas(const Vector &mu, const Vector &sigmas)
A diagonal noise model created by specifying a Vector of standard deviations, some of which might be ...
Definition NoiseModel.cpp:404
static shared_ptr Create(size_t dim)
Create a unit covariance noise model.
Definition NoiseModel.h:673
bool equals(const NonlinearFactor &f, double tol=1e-9) const override
Check equality with another factor.
Definition BatchFactor.h:381
void updateKeys()
Update keys_ by collecting unique keys from all factors.
Definition BatchFactor.h:449
double error(const Values &c) const override
Calculate the error of the factor.
Definition BatchFactor.h:398
BatchFactor(Key key1, const std::map< Key, Measurement > &measurements, const SharedNoiseModel &model, Args &&... args)
Map-based Constructor (Varying Key2).
Definition BatchFactor.h:333
BatchFactor(const std::vector< FactorType > &factors)
Constructor from a standard vector of factors (copies the vector).
Definition BatchFactor.h:307
void collectKeys(const FactorType &f, std::index_sequence< Is... >)
Helper to collect keys and dimensions using fold expression.
Definition BatchFactor.h:440
BatchFactor(const std::map< Key, Measurement > &measurements, Key key2, const SharedNoiseModel &model, Args &&... args)
Map-based Constructor (Varying Key1).
Definition BatchFactor.h:354
BatchFactor(std::vector< FactorType, Allocator > &&factors)
Constructor from a vector of factors (moves the vector).
Definition BatchFactor.h:295
BatchFactor(std::vector< FactorType > &&factors)
Constructor from a standard vector of factors (moves elements).
Definition BatchFactor.h:314
size_t dim() const override
Get the dimension of the factor (number of rows on linearization).
Definition BatchFactor.h:407
size_t numFactors() const
Return the number of child factors represented by this batch.
Definition BatchFactor.h:292
const FactorVector & factors() const
Return the child factors represented by this batch.
Definition BatchFactor.h:289
void print(const std::string &s="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const override
Print the BatchFactor.
Definition BatchFactor.h:369
std::shared_ptr< GaussianFactor > linearize(const Values &values) const override
Linearize to a single JacobianFactor.
Definition BatchFactor.h:420
BatchFactor()=default
Default constructor.
BatchFactor(const std::vector< FactorType, Allocator > &factors)
Constructor from a vector of factors (copies the vector).
Definition BatchFactor.h:301
NonlinearFactor()
Default constructor for I/O only.
Definition NonlinearFactor.h:86
void print(const std::string &s="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const override
print
Definition NonlinearFactor.cpp:45
A non-templated config holding any types of Manifold-group elements.
Definition Values.h:65
const ValueType at(Key j) const
Retrieve a variable by key j.
Definition Values-inl.h:260