My Project 1.0
Holistic Fusion: A Generic Framework for Multimodal Sensor Fusion
Loading...
Searching...
No Matches
OptimizerLMBatch.hpp
Go to the documentation of this file.
1/*
2Copyright 2024 by Julian Nubert, Robotic Systems Lab, ETH Zurich.
3All rights reserved.
4This file is released under the "BSD-3-Clause License".
5Please see the LICENSE file that has been included as part of this package.
6 */
7
8#ifndef OPTIMIZER_LM_BATCH_HPP
9#define OPTIMIZER_LM_BATCH_HPP
10
11// GTSAM
12#include <gtsam/nonlinear/Marginals.h>
13
14// Workspace
16
17namespace holistic_fusion {
18
20 public:
21 explicit OptimizerLMBatch(const std::shared_ptr<GraphConfig> graphConfigPtr) : OptimizerLM(graphConfigPtr) {
22 // Initialize Slow Bundle Adjustement Smoother (if desired) -----------------------------------------------
23 if (graphConfigPtr_->useAdditionalSlowBatchSmootherFlag_) {
24 REGULAR_COUT << YELLOW_START << "HolisticFusion: OptimizerLMBatch" << GREEN_START
25 << " Initializing slow batch smoother that is optimized with LM." << COLOR_END << std::endl;
26 // Set whether to use Cholesky factorization
27 if (graphConfigPtr_->slowBatchSmootherUseCholeskyFactorizationFlag_) {
28 lmParams_.linearSolverType = gtsam::NonlinearOptimizerParams::MULTIFRONTAL_CHOLESKY;
29 REGULAR_COUT << "Using Multifrontal-Cholesky factorization for slow batch smoother (LM)." << std::endl;
30 } else {
31 lmParams_.linearSolverType = gtsam::NonlinearOptimizerParams::MULTIFRONTAL_QR;
32 REGULAR_COUT << "Using Multifrontal-QR factorization for slow batch smoother (LM)." << std::endl;
33 }
34 // Print
35 lmParams_.print("HolisticFusion: OptimizerLMBatch, LM Parameters:");
36 }
37 }
38 ~OptimizerLMBatch() = default;
39
40 // Empty update call --> do nothing
41 bool update() override { return true; }
42
43 // Add to graph without running optimization
44 bool update(const gtsam::NonlinearFactorGraph& newGraphFactors, const gtsam::Values& newGraphValues,
45 const std::map<gtsam::Key, double>& newGraphKeysTimeStampMap, const int depth = 0) override {
46 // Add Bundle Adjustement Smoother factors to batch without running optimization
47 containerBatchSmootherFactors_.add(newGraphFactors);
48 containerBatchSmootherValues_.insert(newGraphValues);
49 // Add to keyTimestampMap
50 batchSmootherKeyTimestampMap_.insert(newGraphKeysTimeStampMap.begin(), newGraphKeysTimeStampMap.end());
51 return true;
52 }
53
54 // Implementation
55 bool updateExistingValues(const gtsam::Values& newGraphValues) override {
56 // Update Values
57 for (const auto& key : newGraphValues.keys()) {
58 // Check whether key is in the batchSmootherValues
59 if (containerBatchSmootherValues_.exists(key)) {
60 // Remove old value
62 // Insert new value
63 containerBatchSmootherValues_.insert(key, newGraphValues.at(key));
64 // std::cout << "HolisticFusion: OptimizerLMBatch: updateExistingValues: Key " << gtsam::Symbol(key) << " updated." << std::endl;
65 } else {
66 throw std::logic_error("HolisticFusion: OptimizerLMBatch: updateExistingValues: Key " + gtsam::Symbol(key).string() +
67 " does not exist in batchSmootherValues.");
68 }
69 }
70 return true;
71 }
72
73 // Optimize
74 void optimize(int maxIterations) override {
75 // Print
76 std::cout << YELLOW_START << "HolisticFusion: OptimizerIsam2Batch" << GREEN_START << " Optimizing slow batch smoother." << COLOR_END
77 << std::endl;
78
79 // Set LM Parameters
80 lmParams_.maxIterations = maxIterations;
81
82 // Initialize Slow Bundle Adjustment Smoother
84 std::make_shared<gtsam::LevenbergMarquardtOptimizer>(containerBatchSmootherFactors_, containerBatchSmootherValues_, lmParams_);
85
86 // Do not reset containers --> Will build up graph again from scratch for next optimization
87 // containerBatchSmootherFactors_.resize(0);
88 // containerBatchSmootherValues_.clear();
89 // Log State of graph in order to compute marginal covariance if desired
91
92 // Optimize
96
97 // Set Sorted Keys by Time Flag
99 }
100
101 // Optimize bundle adjustement smoother (if desired)
102 const gtsam::Values& getAllOptimizedStates() override {
103 // Check
105 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: getAllOptimizedStates: No optimization has been performed yet.");
106 }
107
108 // Return result
110 }
111
112 // Get all keys of optimized states
113 gtsam::KeyVector getAllOptimizedKeys() override { return valuesLastOptimizedResult_.keys(); }
114
115 // Get nonlinear factor graph
116 [[nodiscard]] const gtsam::NonlinearFactorGraph& getNonlinearFactorGraph() const override { return batchSmootherPtr_->graph(); }
117
118 // Get keyTimestampMap
119 const std::map<gtsam::Key, double>& getFullKeyTimestampMap() override { return batchSmootherKeyTimestampMap_; }
120
121 // Calculate State / Covariance --------------------------------------------------------------
122 // Pose3
123 gtsam::Pose3 calculateEstimatedPose3(const gtsam::Key& key) override {
125 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: calculateEstimatedPose: No optimization has been performed yet.");
126 }
127 return valuesLastOptimizedResult_.at<gtsam::Pose3>(key);
128 }
129 // Velocity3
130 gtsam::Vector3 calculateEstimatedVelocity3(const gtsam::Key& key) override {
132 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: calculateEstimatedVelocity: No optimization has been performed yet.");
133 }
134 return valuesLastOptimizedResult_.at<gtsam::Vector3>(key);
135 }
136 // Bias
137 gtsam::imuBias::ConstantBias calculateEstimatedBias(const gtsam::Key& key) override {
139 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: calculateEstimatedBias: No optimization has been performed yet.");
140 }
141 return valuesLastOptimizedResult_.at<gtsam::imuBias::ConstantBias>(key);
142 }
143 // Point3
144 gtsam::Point3 calculateEstimatedPoint3(const gtsam::Key& key) override {
146 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: calculateEstimatedDisplacement: No optimization has been performed yet.");
147 }
148 return valuesLastOptimizedResult_.at<gtsam::Point3>(key);
149 }
150 // Vector
151 gtsam::Vector calculateEstimatedVector(const gtsam::Key& key) override {
153 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: calculateStateAtKey: No optimization has been performed yet.");
154 }
155 return valuesLastOptimizedResult_.at<gtsam::Vector>(key);
156 }
157
158 static std::vector<double> linspace(double start, double end, int num) {
159 std::vector<double> result;
160 if (num <= 0) return result; // Empty vector if num is 0 or negative
161 if (num == 1) { // Single point case
162 result.push_back(start);
163 return result;
164 }
165
166 double step = (end - start) / (num - 1);
167 for (int i = 0; i < num; ++i) {
168 result.push_back(start + i * step);
169 }
170
171 return result;
172 }
173
174 static int filterFactorsConnectedToKey(gtsam::NonlinearFactorGraph& graph, const gtsam::Key& keyToFilter) {
175 gtsam::NonlinearFactorGraph filteredGraph;
176 int numFilteredFactors = 0;
177 for (const auto& factor : graph) {
178 bool foundKeyInFactor = false;
179 for (const gtsam::Key& key : factor->keys()) {
180 if (key == keyToFilter) {
181 foundKeyInFactor = true;
182 break;
183 }
184 }
185 if (!foundKeyInFactor) {
186 filteredGraph.add(factor);
187 } else {
188 numFilteredFactors++;
189 }
190 }
191 graph = filteredGraph;
192 return numFilteredFactors;
193 }
194
195 static gtsam::Marginals computeMarginals(gtsam::NonlinearFactorGraph& graph, gtsam::Values& values) {
196 // // Create linearized factor graph
197 // gtsam::GaussianFactorGraph linearizedGraph = *graph.linearize(values);
198
199 // Try to create marginals, filter out ill-conditioned states if necessary
200 gtsam::Marginals marginals;
201 try {
202 marginals = gtsam::Marginals(graph, values);
203 } catch (const gtsam::IndeterminateSystemException& e) {
204 std::cout << "HolisticFusion: OptimizerLMBatch: computing marginals failed with error: " << e.what() << std::endl;
205 // Filter out ill-conditioned states
206 std::cout << "HolisticFusion: OptimizerLMBatch: filtering out ill-conditioned state: " << gtsam::Symbol(e.nearbyVariable()) << std::endl;
207 // Values
208 bool foundInValues = false;
209 for (const gtsam::Key& key : values.keys()) {
210 if (key == e.nearbyVariable()) {
211 values.erase(key);
212 foundInValues = true;
213 break;
214 }
215 }
216 // Factors
217 int numFilteredFactors = filterFactorsConnectedToKey(graph, e.nearbyVariable());
218
219 // Filtered out
220 if (foundInValues) {
221 std::cout << "HolisticFusion: OptimizerLMBatch: Filtered out state from values (" << gtsam::Symbol(e.nearbyVariable()) << ")."
222 << std::endl;
223 std::cout << "HolisticFusion: OptimizerLMBatch: Filtered out " << numFilteredFactors << " factors. Retrying to compute marginals."
224 << std::endl;
225 // Retry
226 marginals = OptimizerLMBatch::computeMarginals(graph, values);
227 } else {
228 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: computing marginals failed and could not filter out ill-conditioned state.");
229 }
230 } catch (const gtsam::ValuesKeyDoesNotExist& e) {
231 std::cout << "HolisticFusion: OptimizerLMBatch: computing marginals failed with error: " << e.what() << std::endl;
232
233 // Filtering out the factors that contain the not existing key
234 int numFilteredFactors = filterFactorsConnectedToKey(graph, e.key());
235 std::cout << "HolisticFusion: OptimizerLMBatch: Filtered out " << numFilteredFactors << " factors. Retrying to compute marginals."
236 << std::endl;
237 // Retry
238 marginals = OptimizerLMBatch::computeMarginals(graph, values);
239 }
240
241 // Return
242 return marginals;
243 }
244
246 // Check
248 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: computeAndStoreAllMarginals: No optimization has been performed yet.");
249 }
250
251 // All Keys
252 gtsam::KeyVector keysSorted = valuesLastOptimizedResult_.keys();
253
254 // Sort keys by time (given in keyTimestampMap)
255 std::sort(keysSorted.begin(), keysSorted.end(), [&](const gtsam::Key& key1, const gtsam::Key& key2) {
256 return batchSmootherKeyTimestampMap_.at(key1) < batchSmootherKeyTimestampMap_.at(key2);
257 });
258
259 // Get time span and number of keys
260 double startTime = batchSmootherKeyTimestampMap_.at(keysSorted.front());
261 double endTime = batchSmootherKeyTimestampMap_.at(keysSorted.back());
262 int numKeys = keysSorted.size();
263 double timeDifference = endTime - startTime;
264
265 // Print
266 std::cout << "HolisticFusion: OptimizerLMBatch: Graph consisting of " << numKeys << " keys is spanning " << timeDifference << " seconds."
267 << std::endl;
268
269 // Determine how many overlapping sub-graphs of size windowSizeSecond to create
270 const double& windowSizeSeconds = graphConfigPtr_->windowSizeSecondsForMarginalsComputation_; // Alias
271 int numSubGraphs = std::ceil(timeDifference / windowSizeSeconds);
272 std::cout << "HolisticFusion: OptimizerLMBatch: Dividing graph into " << numSubGraphs << " main windows." << std::endl;
273 numSubGraphs += (numSubGraphs - 1); // Overlapping windows
274 std::cout << "HolisticFusion: OptimizerLMBatch: Dividing graph into " << numSubGraphs
275 << " overlapping windows (to make sure there are no edges)." << std::endl;
276
277 // Placeholders
278 std::vector<double> subGraphCenterTimes;
279 // Sub-graph start and end times
280 std::vector<double> subGraphStartTimes;
281 std::vector<double> subGraphEndTimes;
282
283 // More than one sub-graph
284 if (numSubGraphs > 1) {
285 // Sub-graph Center Times: linearly spaced from startTime + windowSizeSeconds/2 to endTime - windowSizeSeconds/2
286 subGraphCenterTimes = linspace(startTime + windowSizeSeconds / 2, endTime - windowSizeSeconds / 2, numSubGraphs);
287 for (int i = 0; i < numSubGraphs; ++i) {
288 subGraphStartTimes.push_back(subGraphCenterTimes[i] - windowSizeSeconds / 2);
289 subGraphEndTimes.push_back(subGraphCenterTimes[i] + windowSizeSeconds / 2);
290 }
291 // One sub-graph
292 } else {
293 // Only one sub-graph
294 subGraphCenterTimes.push_back((startTime + endTime) / 2);
295 subGraphStartTimes.push_back(startTime);
296 subGraphEndTimes.push_back(endTime);
297 }
298
299 // Print Relative Times
300 for (int i = 0; i < numSubGraphs; ++i) {
301 std::cout << "Subgraph " << i << " starts at relative " << subGraphStartTimes[i] - startTime << " seconds and ends at relative "
302 << subGraphEndTimes[i] - startTime << " seconds." << std::endl;
303 std::cout << "Center time: " << subGraphCenterTimes[i] - startTime << " seconds." << std::endl;
304 }
305
306 // Reset all containers
307 subGraphs_.clear();
310
311 // Create sub-graphs
312 subGraphs_.resize(numSubGraphs);
314 marginalsForSubGraphs_.resize(numSubGraphs);
315
316 // Go through all factors and add them to the sub-graph if they are within the time window
317 for (const auto& factorPtr : graphLastOptimizedResult_) {
318 bool factorAtLeastInOneSubGraph = false;
319 // Check whether the factor contains any key within the window of all sub-graphs
320 for (int i = 0; i < numSubGraphs; ++i) {
321 // Go through all keys of factor
322 int numReferenceFrameKeysInFactor = 0;
323 for (const auto& factorKey : factorPtr->keys()) {
324 // Skip keys of reference frame alignment factors for determining timestamp (as they can span a lot of time) unless it is random
325 // walk factor with two reference frame keys
326 if (gtsam::Symbol(factorKey).chr() == 'r') {
327 numReferenceFrameKeysInFactor++;
328 if (numReferenceFrameKeysInFactor == 1) {
329 continue; // Skip timestamp of first reference frame key
330 } else if (numReferenceFrameKeysInFactor > 1) {
331 std::cout << "Found random walk factor with (at least) two reference frame keys (including " << gtsam::Symbol(factorKey)
332 << ")." << std::endl; // Do not skip
333 }
334 }
335
336 // Check whether key is within window
337 if (batchSmootherKeyTimestampMap_[factorKey] >= subGraphStartTimes[i] &&
338 batchSmootherKeyTimestampMap_[factorKey] <= subGraphEndTimes[i]) {
339 subGraphs_[i].add(factorPtr);
340 factorAtLeastInOneSubGraph = true;
341 break;
342 }
343 }
344 }
345 // Has to be in at least one sub-graph
346 if (!factorAtLeastInOneSubGraph) {
347 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: Factor does not contain any key within the time window of any sub-graph.");
348 }
349 }
350
351 // Assign each key to the sub-graph with the closest center time to know in which sub-graph to compute marginals
352 for (const auto& key : keysSorted) {
353 double keyTime = batchSmootherKeyTimestampMap_[key];
354 // Find the closest center time
355 double minTimeDifference = std::numeric_limits<double>::max();
356 int closestSubGraphIndex = -1;
357 // Go through all sub-graphs
358 for (int i = 0; i < numSubGraphs; ++i) {
359 const double timeDifference = std::abs(subGraphCenterTimes[i] - keyTime);
360 if (timeDifference < minTimeDifference) {
361 minTimeDifference = timeDifference;
362 closestSubGraphIndex = i;
363 }
364 }
365 // Assign key to sub-graph
366 keyToSubGraphIndexMap_[key] = closestSubGraphIndex;
367 }
368
369 // Optimize each sub-graph and compute marginals
370 for (int i = 0; i < numSubGraphs; ++i) {
371 // Print sub-graph information
372 std::cout << "HolisticFusion: OptimizerLMBatch: Optimizing subgraph " << i << " with " << subGraphs_[i].size() << " factors and "
373 << subGraphs_[i].keys().size() << " keys." << std::endl;
374 // Optimize
376 // Print
377 std::cout << "HolisticFusion: OptimizerLMBatch: Subgraph " << i << " optimized and marginals computed." << std::endl;
378 }
379
380 // Set flag
382
383 // Print
384 std::cout << "HolisticFusion: OptimizerLMBatch: Divided graph into subgraphs." << std::endl;
385 }
386
387 // Marginal Covariance
388 gtsam::Matrix calculateMarginalCovarianceMatrixAtKey(const gtsam::Key& valueKey) override {
390 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: marginalCovariance: No optimization has been performed yet.");
391 }
392
393 // Super slow check
394 if constexpr (false) {
395 // Check whether key exists in optimized result
396 if (!valuesLastOptimizedResult_.exists(valueKey)) {
397 std::cout << "Key " << gtsam::Symbol(valueKey) << " does not exist in optimized result." << std::endl;
398 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: marginalCovariance: Key does not exist in optimized result.");
399 }
400 // Check whether key exists in nonlinar factor graph
401 if (!graphLastOptimizedResult_.keys().exists(valueKey)) {
402 std::cout << "Key " << gtsam::Symbol(valueKey) << " does not exist in nonlinear factor graph." << std::endl;
403 throw std::runtime_error("HolisticFusion: OptimizerLMBatch: marginalCovariance: Key does not exist in nonlinear factor graph.");
404 }
405 }
406
407 // Only use window around key for marginal covariance ------------------------------------------------
408 if (graphConfigPtr_->useWindowForMarginalsComputationFlag_) {
411 }
412 // Get sub-graph index
413 const int& subGraphsIndex_ = keyToSubGraphIndexMap_[valueKey]; // Alias
414 if (subGraphsIndex_ != currentOptimizedSubgraphIndex_) {
415 std::cout << GREEN_START << "Switching to sub-graph " << subGraphsIndex_ << " for marginal covariance computation." << COLOR_END
416 << std::endl;
417 currentOptimizedSubgraphIndex_ = subGraphsIndex_;
418 }
419 // Compute marginal covariance and measure time
420 gtsam::Matrix marginalCovariance = marginalsForSubGraphs_[subGraphsIndex_].marginalCovariance(valueKey);
421 return marginalCovariance;
422 }
423 // Normal case: Compute marginals for whole graph
424 else {
425 // Have to compute all marginals (if not done already for this result)
427 std::cout << "Getting new marginals for last optimized result." << std::endl;
430 }
431 // Return
432 return marginalsForLastOptimizedResult_.marginalCovariance(valueKey);
433 }
434 }
435
436 private:
437 // Optimizer itself
438 std::shared_ptr<gtsam::LevenbergMarquardtOptimizer> batchSmootherPtr_;
439 // Containers for bundle adjustment smoother
440 gtsam::NonlinearFactorGraph containerBatchSmootherFactors_;
442 // Result and keyTimestampMap
445 std::map<gtsam::Key, double> batchSmootherKeyTimestampMap_;
446 // Sub-graphs for marginal covariance computation
448 std::vector<gtsam::NonlinearFactorGraph> subGraphs_;
449 std::unordered_map<gtsam::Key, int> keyToSubGraphIndexMap_;
450 std::vector<gtsam::Marginals> marginalsForSubGraphs_;
451 // Container for ingredients of last optimization, e.g. for marginal covariance
452 gtsam::NonlinearFactorGraph graphLastOptimizedResult_;
455
456 // Indicator of current sub-graph
458};
459
460} // namespace holistic_fusion
461
462#endif // OPTIMIZER_LM_BATCH_HPP
#define COLOR_END
#define GREEN_START
#define YELLOW_START
std::shared_ptr< GraphConfig > graphConfigPtr_
const std::map< gtsam::Key, double > & getFullKeyTimestampMap() override
gtsam::Vector3 calculateEstimatedVelocity3(const gtsam::Key &key) override
gtsam::Point3 calculateEstimatedPoint3(const gtsam::Key &key) override
bool updateExistingValues(const gtsam::Values &newGraphValues) override
gtsam::Matrix calculateMarginalCovarianceMatrixAtKey(const gtsam::Key &valueKey) override
static std::vector< double > linspace(double start, double end, int num)
OptimizerLMBatch(const std::shared_ptr< GraphConfig > graphConfigPtr)
const gtsam::Values & getAllOptimizedStates() override
gtsam::imuBias::ConstantBias calculateEstimatedBias(const gtsam::Key &key) override
std::unordered_map< gtsam::Key, int > keyToSubGraphIndexMap_
std::map< gtsam::Key, double > batchSmootherKeyTimestampMap_
gtsam::KeyVector getAllOptimizedKeys() override
gtsam::NonlinearFactorGraph graphLastOptimizedResult_
std::shared_ptr< gtsam::LevenbergMarquardtOptimizer > batchSmootherPtr_
static gtsam::Marginals computeMarginals(gtsam::NonlinearFactorGraph &graph, gtsam::Values &values)
gtsam::Vector calculateEstimatedVector(const gtsam::Key &key) override
void optimize(int maxIterations) override
std::vector< gtsam::Marginals > marginalsForSubGraphs_
std::vector< gtsam::NonlinearFactorGraph > subGraphs_
bool update(const gtsam::NonlinearFactorGraph &newGraphFactors, const gtsam::Values &newGraphValues, const std::map< gtsam::Key, double > &newGraphKeysTimeStampMap, const int depth=0) override
gtsam::NonlinearFactorGraph containerBatchSmootherFactors_
static int filterFactorsConnectedToKey(gtsam::NonlinearFactorGraph &graph, const gtsam::Key &keyToFilter)
gtsam::Pose3 calculateEstimatedPose3(const gtsam::Key &key) override
const gtsam::NonlinearFactorGraph & getNonlinearFactorGraph() const override
gtsam::LevenbergMarquardtParams lmParams_
OptimizerLM(const std::shared_ptr< GraphConfig > graphConfigPtr)