gtsam
Loading...
Searching...
No Matches
ISAM2-impl.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
18
19#pragma once
20
23
24#include <gtsam/base/debug.h>
25#include <gtsam/inference/JunctionTree-inst.h> // We need the inst file because we'll make a special JT templated on ISAM2
30
31#include <algorithm>
32#include <limits>
33#include <string>
34#include <utility>
35#include <variant>
36#include <cassert>
37
38namespace gtsam {
39
40/* ************************************************************************* */
41// Special BayesTree class that uses ISAM2 cliques - this is the result of
42// reeliminating ISAM2 subtrees.
43class ISAM2BayesTree : public ISAM2::Base {
44 public:
45 typedef ISAM2::Base Base;
46 typedef ISAM2BayesTree This;
47 typedef std::shared_ptr<This> shared_ptr;
48
49 ISAM2BayesTree() {}
50};
51
52/* ************************************************************************* */
53// Special JunctionTree class that produces ISAM2 BayesTree cliques, used for
54// reeliminating ISAM2 subtrees.
55class ISAM2JunctionTree
56 : public JunctionTree<ISAM2BayesTree, GaussianFactorGraph> {
57 public:
59 typedef ISAM2JunctionTree This;
60 typedef std::shared_ptr<This> shared_ptr;
61
62 explicit ISAM2JunctionTree(const GaussianEliminationTree& eliminationTree)
63 : Base(eliminationTree) {}
64};
65
66/* ************************************************************************* */
67struct GTSAM_EXPORT DeltaImpl {
68 struct GTSAM_EXPORT PartialSolveResult {
69 ISAM2::sharedClique bayesTree;
70 };
71
72 struct GTSAM_EXPORT ReorderingMode {
73 size_t nFullSystemVars;
74 enum { /*AS_ADDED,*/ COLAMD } algorithm;
75 enum { NO_CONSTRAINT, CONSTRAIN_LAST } constrain;
76 std::optional<FastMap<Key, int> > constrainedKeys;
77 };
78
82 static size_t UpdateGaussNewtonDelta(const ISAM2::Roots& roots,
83 const KeySet& replacedKeys,
84 double wildfireThreshold,
85 VectorValues* delta);
86
91 static size_t UpdateRgProd(const ISAM2::Roots& roots,
92 const KeySet& replacedKeys,
93 const VectorValues& gradAtZero,
94 VectorValues* RgProd);
95
99 static VectorValues ComputeGradientSearch(const VectorValues& gradAtZero,
100 const VectorValues& RgProd);
101};
102
103/* ************************************************************************* */
109struct GTSAM_EXPORT UpdateImpl {
110 const ISAM2Params& params_;
111 const ISAM2UpdateParams& updateParams_;
112 UpdateImpl(const ISAM2Params& params, const ISAM2UpdateParams& updateParams)
113 : params_(params), updateParams_(updateParams) {}
114
115 // Provide some debugging information at the start of update
116 static void LogStartingUpdate(const NonlinearFactorGraph& newFactors,
117 const ISAM2& isam2) {
118 gttic(pushBackFactors);
119 const bool debug = ISDEBUG("ISAM2 update");
120 const bool verbose = ISDEBUG("ISAM2 update verbose");
121
122 if (verbose) {
123 std::cout << "ISAM2::update\n";
124 isam2.print("ISAM2: ");
125 }
126
127 if (debug || verbose) {
128 newFactors.print("The new factors are: ");
129 }
130 }
131
132 // Check relinearization if we're at the nth step, or we are using a looser
133 // loop relinerization threshold.
134 bool relinarizationNeeded(size_t update_count) const {
135 return updateParams_.force_relinearize ||
136 (params_.enableRelinearization &&
137 update_count % params_.relinearizeSkip == 0);
138 }
139
140 // Add any new factors \Factors:=\Factors\cup\Factors'.
141 void pushBackFactors(const NonlinearFactorGraph& newFactors,
142 NonlinearFactorGraph* nonlinearFactors,
143 GaussianFactorGraph* linearFactors,
144 VariableIndex* variableIndex,
145 FactorIndices* newFactorsIndices,
146 KeySet* keysWithRemovedFactors) const {
147 gttic(pushBackFactors);
148
149 // Perform the first part of the bookkeeping updates for adding new factors.
150 // Adds them to the complete list of nonlinear factors, and populates the
151 // list of new factor indices, both optionally finding and reusing empty
152 // factor slots.
153 *newFactorsIndices = nonlinearFactors->add_factors(
154 newFactors, params_.findUnusedFactorSlots);
155
156 // Remove the removed factors
157 NonlinearFactorGraph removedFactors;
158 removedFactors.reserve(updateParams_.removeFactorIndices.size());
159 for (const auto index : updateParams_.removeFactorIndices) {
160 removedFactors.push_back(nonlinearFactors->at(index));
161 nonlinearFactors->remove(index);
162 if (params_.cacheLinearizedFactors) linearFactors->remove(index);
163 }
164
165 // Remove removed factors from the variable index so we do not attempt to
166 // relinearize them
167 variableIndex->remove(updateParams_.removeFactorIndices.begin(),
168 updateParams_.removeFactorIndices.end(),
169 removedFactors);
170 *keysWithRemovedFactors = removedFactors.keys();
171 }
172
173 // Get keys from removed factors and new factors, and compute unused keys,
174 // i.e., keys that are empty now and do not appear in the new factors.
175 void computeUnusedKeys(const NonlinearFactorGraph& newFactors,
176 const VariableIndex& variableIndex,
177 const KeySet& keysWithRemovedFactors,
178 KeySet* unusedKeys) const {
179 gttic(computeUnusedKeys);
180 // Most incremental updates remove no factors, so no key can become unused.
181 if (keysWithRemovedFactors.empty()) return;
182 KeySet removedAndEmpty;
183 for (Key key : keysWithRemovedFactors) {
184 if (variableIndex.empty(key))
185 removedAndEmpty.insert(removedAndEmpty.end(), key);
186 }
187 KeySet newFactorSymbKeys = newFactors.keys();
188 std::set_difference(removedAndEmpty.begin(), removedAndEmpty.end(),
189 newFactorSymbKeys.begin(), newFactorSymbKeys.end(),
190 std::inserter(*unusedKeys, unusedKeys->end()));
191 }
192
193 // Calculate nonlinear error
194 void error(const NonlinearFactorGraph& nonlinearFactors,
195 const Values& estimate, std::optional<double>* result) const {
196 gttic(error);
197 *result = nonlinearFactors.error(estimate);
198 }
199
200 // Mark linear update
201 void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors,
202 const NonlinearFactorGraph& nonlinearFactors,
203 const KeySet& keysWithRemovedFactors,
204 KeySet* markedKeys) const {
205 gttic(gatherInvolvedKeys);
206 *markedKeys = newFactors.keys(); // Get keys from new factors
207 // Also mark keys involved in removed factors
208 markedKeys->insert(keysWithRemovedFactors.begin(),
209 keysWithRemovedFactors.end());
210
211 // Also mark any provided extra re-eliminate keys
212 if (updateParams_.extraReelimKeys) {
213 for (Key key : *updateParams_.extraReelimKeys) {
214 markedKeys->insert(key);
215 }
216 }
217
218 // Also, keys that were not observed in existing factors, but whose affected
219 // keys have been extended now (e.g. smart factors)
220 if (updateParams_.newAffectedKeys) {
221 for (const auto& factorAddedKeys : *updateParams_.newAffectedKeys) {
222 const auto factorIdx = factorAddedKeys.first;
223 const auto& affectedKeys = nonlinearFactors.at(factorIdx)->keys();
224 markedKeys->insert(affectedKeys.begin(), affectedKeys.end());
225 }
226 }
227 }
228
229 // Update detail, unused, and observed keys from markedKeys
230 void updateKeys(const KeySet& markedKeys, ISAM2Result* result) const {
231 gttic(updateKeys);
232 // Observed keys for detailed results
233 if (result->detail && params_.enableDetailedResults) {
234 for (Key key : markedKeys) {
235 result->detail->variableStatus[key].isObserved = true;
236 }
237 }
238
239 for (Key index : markedKeys) {
240 // Only add if not unused
241 if (result->unusedKeys.find(index) == result->unusedKeys.end())
242 // Make a copy of these, as we'll soon add to them
243 result->observedKeys.push_back(index);
244 }
245 }
246
247 static void CheckRelinearizationRecursiveMap(
248 const FastMap<char, Vector>& thresholds, const VectorValues& delta,
249 const ISAM2::sharedClique& clique, KeySet* relinKeys) {
250 // Check the current clique for relinearization
251 bool relinearize = false;
252 for (Key var : *clique->conditional()) {
253 // Find the threshold for this variable type
254 const Vector& threshold = thresholds.find(Symbol(var).chr())->second;
255
256 const Vector& deltaVar = delta[var];
257
258 // Verify the threshold vector matches the actual variable size
259 if (threshold.rows() != deltaVar.rows())
260 throw std::invalid_argument(
261 "Relinearization threshold vector dimensionality for '" +
262 std::string(1, Symbol(var).chr()) +
263 "' passed into iSAM2 parameters does not match actual variable "
264 "dimensionality.");
265
266 // Check for relinearization
267 if ((deltaVar.array().abs() > threshold.array()).any()) {
268 relinKeys->insert(var);
269 relinearize = true;
270 }
271 }
272
273 // If this node was relinearized, also check its children
274 if (relinearize) {
275 for (const ISAM2::sharedClique& child : clique->children) {
276 CheckRelinearizationRecursiveMap(thresholds, delta, child, relinKeys);
277 }
278 }
279 }
280
281 static void CheckRelinearizationRecursiveDouble(
282 double threshold, const VectorValues& delta,
283 const ISAM2::sharedClique& clique, KeySet* relinKeys) {
284 // Check the current clique for relinearization
285 bool relinearize = false;
286 for (Key var : *clique->conditional()) {
287 double maxDelta = delta[var].lpNorm<Eigen::Infinity>();
288 if (maxDelta >= threshold) {
289 relinKeys->insert(var);
290 relinearize = true;
291 }
292 }
293
294 // If this node was relinearized, also check its children
295 if (relinearize) {
296 for (const ISAM2::sharedClique& child : clique->children) {
297 CheckRelinearizationRecursiveDouble(threshold, delta, child, relinKeys);
298 }
299 }
300 }
301
316 const ISAM2::Roots& roots, const VectorValues& delta,
317 const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) {
318 KeySet relinKeys;
319 for (const ISAM2::sharedClique& root : roots) {
320 if (std::holds_alternative<double>(relinearizeThreshold)) {
321 CheckRelinearizationRecursiveDouble(
322 std::get<double>(relinearizeThreshold), delta, root, &relinKeys);
323 } else if (std::holds_alternative<FastMap<char, Vector>>(relinearizeThreshold)) {
324 CheckRelinearizationRecursiveMap(
325 std::get<FastMap<char, Vector> >(relinearizeThreshold), delta,
326 root, &relinKeys);
327 }
328 }
329 return relinKeys;
330 }
331
344 const VectorValues& delta,
345 const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) {
346 KeySet relinKeys;
347
348 if (const double* threshold = std::get_if<double>(&relinearizeThreshold)) {
349 for (const VectorValues::KeyValuePair& key_delta : delta) {
350 double maxDelta = key_delta.second.lpNorm<Eigen::Infinity>();
351 if (maxDelta >= *threshold) relinKeys.insert(key_delta.first);
352 }
353 } else if (const FastMap<char, Vector>* thresholds =
354 std::get_if<FastMap<char, Vector> >(&relinearizeThreshold)) {
355 for (const VectorValues::KeyValuePair& key_delta : delta) {
356 const Vector& threshold =
357 thresholds->find(Symbol(key_delta.first).chr())->second;
358 if (threshold.rows() != key_delta.second.rows())
359 throw std::invalid_argument(
360 "Relinearization threshold vector dimensionality for '" +
361 std::string(1, Symbol(key_delta.first).chr()) +
362 "' passed into iSAM2 parameters does not match actual variable "
363 "dimensionality.");
364 if ((key_delta.second.array().abs() > threshold.array()).any())
365 relinKeys.insert(key_delta.first);
366 }
367 }
368
369 return relinKeys;
370 }
371
372 // Mark keys in \Delta above threshold \beta:
373 KeySet gatherRelinearizeKeys(const ISAM2::Roots& roots,
374 const VectorValues& delta,
375 const KeySet& fixedVariables,
376 KeySet* markedKeys) const {
377 gttic(gatherRelinearizeKeys);
378 // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}.
379 KeySet relinKeys =
380 params_.enablePartialRelinearizationCheck
381 ? CheckRelinearizationPartial(roots, delta,
382 params_.relinearizeThreshold)
383 : CheckRelinearizationFull(delta, params_.relinearizeThreshold);
384 if (updateParams_.forceFullSolve)
385 relinKeys = CheckRelinearizationFull(delta, 0.0); // for debugging
386
387 // Remove from relinKeys any keys whose linearization points are fixed
388 for (Key key : fixedVariables) {
389 relinKeys.erase(key);
390 }
391 if (updateParams_.noRelinKeys) {
392 for (Key key : *updateParams_.noRelinKeys) {
393 relinKeys.erase(key);
394 }
395 }
396
397 // Add the variables being relinearized to the marked keys
398 markedKeys->insert(relinKeys.begin(), relinKeys.end());
399 return relinKeys;
400 }
401
402 // Record relinerization threshold keys in detailed results
403 void recordRelinearizeDetail(const KeySet& relinKeys,
404 ISAM2Result::DetailedResults* detail) const {
405 if (detail && params_.enableDetailedResults) {
406 for (Key key : relinKeys) {
407 detail->variableStatus[key].isAboveRelinThreshold = true;
408 detail->variableStatus[key].isRelinearized = true;
409 }
410 }
411 }
412
413 // Mark all cliques that involve marked variables \Theta_{J} and all
414 // their ancestors.
415 void findFluid(const ISAM2::Roots& roots, const KeySet& relinKeys,
416 KeySet* markedKeys,
417 ISAM2Result::DetailedResults* detail) const {
418 gttic(findFluid);
419 for (const auto& root : roots)
420 // add other cliques that have the marked ones in the separator
421 root->findAll(relinKeys, markedKeys);
422
423 // Relinearization-involved keys for detailed results
424 if (detail && params_.enableDetailedResults) {
425 KeySet involvedRelinKeys;
426 for (const auto& root : roots)
427 root->findAll(relinKeys, &involvedRelinKeys);
428 for (Key key : involvedRelinKeys) {
429 if (!detail->variableStatus[key].isAboveRelinThreshold) {
430 detail->variableStatus[key].isRelinearizeInvolved = true;
431 detail->variableStatus[key].isRelinearized = true;
432 }
433 }
434 }
435 }
436
437 // Linearize new factors
438 void linearizeNewFactors(const NonlinearFactorGraph& newFactors,
439 const Values& theta, size_t numNonlinearFactors,
440 const FactorIndices& newFactorsIndices,
441 GaussianFactorGraph* linearFactors) const {
442 gttic(linearizeNewFactors);
443 auto linearized = newFactors.linearize(theta);
444 if (params_.findUnusedFactorSlots) {
445 linearFactors->resize(numNonlinearFactors);
446 for (size_t i = 0; i < newFactors.size(); ++i)
447 (*linearFactors)[newFactorsIndices[i]] = (*linearized)[i];
448 } else {
449 linearFactors->push_back(*linearized);
450 }
451 assert(linearFactors->size() == numNonlinearFactors);
452 }
453
454 void augmentVariableIndex(const NonlinearFactorGraph& newFactors,
455 const FactorIndices& newFactorsIndices,
456 VariableIndex* variableIndex) const {
457 gttic(augmentVariableIndex);
458 // Augment the variable index with the new factors
459 if (params_.findUnusedFactorSlots)
460 variableIndex->augment(newFactors, newFactorsIndices);
461 else
462 variableIndex->augment(newFactors);
463
464 // Augment it with existing factors which now affect to more variables:
465 if (updateParams_.newAffectedKeys) {
466 for (const auto& factorAddedKeys : *updateParams_.newAffectedKeys) {
467 const auto factorIdx = factorAddedKeys.first;
468 variableIndex->augmentExistingFactor(factorIdx, factorAddedKeys.second);
469 }
470 }
471 }
472
473 static void LogRecalculateKeys(const ISAM2Result& result) {
474 const bool debug = ISDEBUG("ISAM2 recalculate");
475
476 if (debug) {
477 std::cout << "markedKeys: ";
478 for (const Key key : result.markedKeys) {
479 std::cout << key << " ";
480 }
481 std::cout << std::endl;
482 std::cout << "observedKeys: ";
483 for (const Key key : result.observedKeys) {
484 std::cout << key << " ";
485 }
486 std::cout << std::endl;
487 }
488 }
489
490 static FactorIndexSet GetAffectedFactors(const KeyList& keys,
491 const VariableIndex& variableIndex) {
492 gttic(GetAffectedFactors);
493 FactorIndexSet indices;
494 for (const Key key : keys) {
495 const FactorIndices& factors(variableIndex[key]);
496 indices.insert(factors.begin(), factors.end());
497 }
498 return indices;
499 }
500
501 // find intermediate (linearized) factors from cache that are passed into
502 // the affected area
503 static GaussianFactorGraph GetCachedBoundaryFactors(
504 const ISAM2::Cliques& orphans) {
505 GaussianFactorGraph cachedBoundary;
506
507 for (const auto& orphan : orphans) {
508 // retrieve the cached factor and add to boundary
509 cachedBoundary.push_back(orphan->cachedFactor());
510 }
511
512 return cachedBoundary;
513 }
514};
515
516} // namespace gtsam
Global debugging flags.
The junction tree, template bodies.
Gaussian Bayes Tree, the result of eliminating a GaussianJunctionTree.
Incremental update functionality (ISAM2) for BayesTree, with fluid relinearization.
Class that stores detailed iSAM2 result.
Global functions in a separate testing namespace.
Definition chartTesting.h:28
FastVector< FactorIndex > FactorIndices
Define collection types:
Definition Factor.h:37
std::uint64_t Key
Integer nonlinear key type.
Definition types.h:43
FastMap is a thin wrapper around std::map that uses the boost fast_pool_allocator instead of the defa...
Definition FastMap.h:40
KeySet keys() const
Potentially slow function to return all keys involved, sorted, as a set.
Definition FactorGraph-inst.h:85
FactorIndices add_factors(const CONTAINER &factors, bool useEmptySlots=false)
Add new factors to a factor graph and returns a list of new factor indices, optionally finding and re...
Definition FactorGraph-inst.h:109
IsDerived< DERIVEDFACTOR > push_back(std::shared_ptr< DERIVEDFACTOR > factor)
Add a factor directly using a shared_ptr.
Definition FactorGraph.h:147
void remove(size_t i)
delete factor without re-arranging indexes by inserting a nullptr pointer
Definition FactorGraph.h:371
const sharedFactor at(size_t i) const
Get a specific factor by index (this checks array bounds and may throw an exception,...
Definition FactorGraph.h:306
void reserve(size_t size)
Reserve space for the specified number of factors if you know in advance how many there will be (work...
Definition FactorGraph.h:143
void print(const std::string &s="", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const
print
Definition BayesTree-inst.h:253
FastVector< sharedClique > Roots
Definition BayesTree.h:105
JunctionTree(const EliminationTree< ETREE_BAYESNET, ETREE_GRAPH > &eliminationTree)
EliminatableClusterTree< ISAM2BayesTree, GaussianFactorGraph > Base
Definition JunctionTree.h:56
Character and index key used to refer to variables.
Definition Symbol.h:37
unsigned char chr() const
Retrieve key character.
Definition Symbol.h:75
The VariableIndex class computes and stores the block column structure of a factor graph.
Definition VariableIndex.h:41
void remove(ITERATOR firstFactor, ITERATOR lastFactor, const FG &factors)
Remove entries corresponding to the specified factors.
Definition VariableIndex-inl.h:53
bool empty(Key variable) const
Return true if no factors associated with a variable.
Definition VariableIndex.cpp:40
Definition GaussianEliminationTree.h:29
A Linear Factor Graph is a factor graph where all factors are Gaussian, i.e.
Definition GaussianFactorGraph.h:77
VectorValues represents a collection of vector-valued variables associated each with a unique integer...
Definition VectorValues.h:73
value_type KeyValuePair
Typedef to pair<Key, Vector>.
Definition VectorValues.h:84
Definition ISAM2-impl.h:67
static size_t UpdateGaussNewtonDelta(const ISAM2::Roots &roots, const KeySet &replacedKeys, double wildfireThreshold, VectorValues *delta)
Update the Newton's method step point, using wildfire.
Definition ISAM2-impl.cpp:48
static VectorValues ComputeGradientSearch(const VectorValues &gradAtZero, const VectorValues &RgProd)
Compute the gradient-search point.
Definition ISAM2-impl.cpp:146
static size_t UpdateRgProd(const ISAM2::Roots &roots, const KeySet &replacedKeys, const VectorValues &gradAtZero, VectorValues *RgProd)
Update the RgProd (R*g) incrementally taking into account which variables have been recalculated in r...
Definition ISAM2-impl.cpp:131
Definition ISAM2-impl.h:68
Definition ISAM2-impl.h:72
static KeySet CheckRelinearizationFull(const VectorValues &delta, const ISAM2Params::RelinearizationThreshold &relinearizeThreshold)
Find the set of variables to be relinearized according to relinearizeThreshold.
Definition ISAM2-impl.h:343
static KeySet CheckRelinearizationPartial(const ISAM2::Roots &roots, const VectorValues &delta, const ISAM2Params::RelinearizationThreshold &relinearizeThreshold)
Find the set of variables to be relinearized according to relinearizeThreshold.
Definition ISAM2-impl.h:315
Implementation of the full ISAM2 algorithm for incremental nonlinear optimization.
Definition ISAM2.h:45
BayesTree< ISAM2Clique > Base
The BayesTree base class.
Definition ISAM2.h:104
Base::Cliques Cliques
List of Cliques.
Definition ISAM2.h:107
Base::sharedClique sharedClique
Shared pointer to a clique.
Definition ISAM2.h:106
Definition ISAM2Params.h:199
std::variant< double, FastMap< char, Vector > > RelinearizationThreshold
Either a constant relinearization threshold or a per-variable-type set of thresholds.
Definition ISAM2Params.h:206
This struct is returned from ISAM2::update() and contains information about the update that is useful...
Definition ISAM2Result.h:39
std::optional< DetailedResults > detail
Detailed results, if enabled by ISAM2Params::enableDetailedResults.
Definition ISAM2Result.h:167
KeySet unusedKeys
Unused keys, and indices for unused keys, i.e., keys that are empty now and do not appear in the new ...
Definition ISAM2Result.h:111
KeyVector observedKeys
keys for variables that were observed, i.e., not unused.
Definition ISAM2Result.h:114
A struct holding detailed results, which must be enabled with ISAM2Params::enableDetailedResults.
Definition ISAM2Result.h:126
This struct is used by ISAM2::update() to pass additional parameters to give the user a fine-grained ...
Definition ISAM2UpdateParams.h:32
std::optional< FastList< Key > > noRelinKeys
An optional set of nonlinear keys that iSAM2 will hold at a constant linearization point,...
Definition ISAM2UpdateParams.h:44
std::optional< FastMap< FactorIndex, KeySet > > newAffectedKeys
An optional set of new Keys that are now affected by factors, indexed by factor indices (as returned ...
Definition ISAM2UpdateParams.h:66
bool forceFullSolve
By default, iSAM2 uses a wildfire update scheme that stops updating when the deltas become too small ...
Definition ISAM2UpdateParams.h:71
Definition NonlinearFactorGraph.h:57
void print(const std::string &str="NonlinearFactorGraph: ", const KeyFormatter &keyFormatter=DefaultKeyFormatter) const override
print
Definition NonlinearFactorGraph.cpp:56
virtual double error(const Values &values) const
unnormalized error, in the most common case
Definition NonlinearFactorGraph.cpp:171
A non-templated config holding any types of Manifold-group elements.
Definition Values.h:65