ConcurrentFilteringAndSmoothingExample.cpp
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 
26 // This example demonstrates the use of the Concurrent Filtering and Smoothing architecture in GTSAM unstable
29 
30 // We will compare the results to a similar Fixed-Lag Smoother
32 
33 // In GTSAM, measurement functions are represented as 'factors'. Several common factors
34 // have been provided with the library for solving robotics/SLAM/Bundle Adjustment problems.
35 // Here we will use Between factors for the relative motion described by odometry measurements.
36 // Also, we will initialize the robot at the origin using a Prior factor.
38 
39 // When the factors are created, we will add them to a Factor Graph. As the factors we are using
40 // are nonlinear factors, we will need a Nonlinear Factor Graph.
42 
43 // The nonlinear solvers within GTSAM are iterative solvers, meaning they linearize the
44 // nonlinear functions around an initial linearization point, then solve the linear system
45 // to update the linearization point. This happens repeatedly until the solver converges
46 // to a consistent set of variable values. This requires us to specify an initial guess
47 // for each variable, held in a Values container.
48 #include <gtsam/nonlinear/Values.h>
49 
50 // We will use simple integer Keys to uniquely identify each robot pose.
51 #include <gtsam/inference/Key.h>
52 
53 // We will use Pose2 variables (x, y, theta) to represent the robot positions
54 #include <gtsam/geometry/Pose2.h>
55 
56 #include <iomanip>
57 
58 using namespace std;
59 using namespace gtsam;
60 
61 
62 int main(int argc, char** argv) {
63 
64  // Define the smoother lag (in seconds)
65  double lag = 2.0;
66 
67  // Create a Concurrent Filter and Smoother
68  ConcurrentBatchFilter concurrentFilter;
69  ConcurrentBatchSmoother concurrentSmoother;
70 
71  // And a fixed lag smoother with a short lag
72  BatchFixedLagSmoother fixedlagSmoother(lag);
73 
74  // And a fixed lag smoother with very long lag (i.e. a full batch smoother)
75  BatchFixedLagSmoother batchSmoother(1000.0);
76 
77 
78  // Create containers to store the factors and linearization points that
79  // will be sent to the smoothers
80  NonlinearFactorGraph newFactors;
81  Values newValues;
83 
84  // Create a prior on the first pose, placing it at the origin
85  Pose2 priorMean(0.0, 0.0, 0.0); // prior at origin
86  auto priorNoise = noiseModel::Diagonal::Sigmas(Vector3(0.3, 0.3, 0.1));
87  Key priorKey = 0;
88  newFactors.addPrior(priorKey, priorMean, priorNoise);
89  newValues.insert(priorKey, priorMean); // Initialize the first pose at the mean of the prior
90  newTimestamps[priorKey] = 0.0; // Set the timestamp associated with this key to 0.0 seconds;
91 
92  // Now, loop through several time steps, creating factors from different "sensors"
93  // and adding them to the fixed-lag smoothers
94  double deltaT = 0.25;
95  for(double time = 0.0+deltaT; time <= 5.0; time += deltaT) {
96 
97  // Define the keys related to this timestamp
98  Key previousKey(1000 * (time-deltaT));
99  Key currentKey(1000 * (time));
100 
101  // Assign the current key to the current timestamp
102  newTimestamps[currentKey] = time;
103 
104  // Add a guess for this pose to the new values
105  // Since the robot moves forward at 2 m/s, then the position is simply: time[s]*2.0[m/s]
106  // {This is not a particularly good way to guess, but this is just an example}
107  Pose2 currentPose(time * 2.0, 0.0, 0.0);
108  newValues.insert(currentKey, currentPose);
109 
110  // Add odometry factors from two different sources with different error stats
111  Pose2 odometryMeasurement1 = Pose2(0.61, -0.08, 0.02);
112  auto odometryNoise1 = noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.05));
113  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement1, odometryNoise1));
114 
115  Pose2 odometryMeasurement2 = Pose2(0.47, 0.03, 0.01);
116  auto odometryNoise2 = noiseModel::Diagonal::Sigmas(Vector3(0.05, 0.05, 0.05));
117  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement2, odometryNoise2));
118 
119  // Unlike the fixed-lag versions, the concurrent filter implementation
120  // requires the user to supply the specify which keys to move to the smoother
121  FastList<Key> oldKeys;
122  if(time >= lag+deltaT) {
123  oldKeys.push_back(1000 * (time-lag-deltaT));
124  }
125 
126  // Update the various inference engines
127  concurrentFilter.update(newFactors, newValues, oldKeys);
128  fixedlagSmoother.update(newFactors, newValues, newTimestamps);
129  batchSmoother.update(newFactors, newValues, newTimestamps);
130 
131  // Manually synchronize the Concurrent Filter and Smoother every 1.0 s
132  if(fmod(time, 1.0) < 0.01) {
133  // Synchronize the Filter and Smoother
134  concurrentSmoother.update();
135  synchronize(concurrentFilter, concurrentSmoother);
136  }
137 
138  // Print the optimized current pose
139  cout << setprecision(5) << "Timestamp = " << time << endl;
140  concurrentFilter.calculateEstimate<Pose2>(currentKey).print("Concurrent Estimate: ");
141  fixedlagSmoother.calculateEstimate<Pose2>(currentKey).print("Fixed Lag Estimate: ");
142  batchSmoother.calculateEstimate<Pose2>(currentKey).print("Batch Estimate: ");
143  cout << endl;
144 
145  // Clear contains for the next iteration
146  newTimestamps.clear();
147  newValues.clear();
148  newFactors.resize(0);
149  }
150  cout << "******************************************************************" << endl;
151  cout << "All three versions should be identical." << endl;
152  cout << "Adding a loop closure factor to the Batch version only." << endl;
153  cout << "******************************************************************" << endl;
154  cout << endl;
155 
156  // At the moment, all three versions produce the same output.
157  // Now lets create a "loop closure" factor between the first pose and the current pose
158  Key loopKey1(1000 * (0.0));
159  Key loopKey2(1000 * (5.0));
160  Pose2 loopMeasurement = Pose2(9.5, 1.00, 0.00);
161  auto loopNoise = noiseModel::Diagonal::Sigmas(Vector3(0.5, 0.5, 0.25));
162  NonlinearFactor::shared_ptr loopFactor(new BetweenFactor<Pose2>(loopKey1, loopKey2, loopMeasurement, loopNoise));
163 
164  // This measurement cannot be added directly to the concurrent filter because it connects a filter state to a smoother state
165  // This measurement can never be added to the fixed-lag smoother, as one of the poses has been permanently marginalized out
166  // This measurement can be incorporated into the full batch version though
167  newFactors.push_back(loopFactor);
168  batchSmoother.update(newFactors, Values(), FixedLagSmoother::KeyTimestampMap());
169  newFactors.resize(0);
170 
171 
172 
173  // Continue adding odometry factors until the loop closure may be incorporated into the concurrent smoother
174  for(double time = 5.0+deltaT; time <= 8.0; time += deltaT) {
175 
176  // Define the keys related to this timestamp
177  Key previousKey(1000 * (time-deltaT));
178  Key currentKey(1000 * (time));
179 
180  // Assign the current key to the current timestamp
181  newTimestamps[currentKey] = time;
182 
183  // Add a guess for this pose to the new values
184  // Since the robot moves forward at 2 m/s, then the position is simply: time[s]*2.0[m/s]
185  // {This is not a particularly good way to guess, but this is just an example}
186  Pose2 currentPose(time * 2.0, 0.0, 0.0);
187  newValues.insert(currentKey, currentPose);
188 
189  // Add odometry factors from two different sources with different error stats
190  Pose2 odometryMeasurement1 = Pose2(0.61, -0.08, 0.02);
191  auto odometryNoise1 = noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.05));
192  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement1, odometryNoise1));
193 
194  Pose2 odometryMeasurement2 = Pose2(0.47, 0.03, 0.01);
195  auto odometryNoise2 = noiseModel::Diagonal::Sigmas(Vector3(0.05, 0.05, 0.05));
196  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement2, odometryNoise2));
197 
198  // Unlike the fixed-lag versions, the concurrent filter implementation
199  // requires the user to supply the specify which keys to marginalize
200  FastList<Key> oldKeys;
201  if(time >= lag+deltaT) {
202  oldKeys.push_back(1000 * (time-lag-deltaT));
203  }
204 
205  // Update the various inference engines
206  concurrentFilter.update(newFactors, newValues, oldKeys);
207  fixedlagSmoother.update(newFactors, newValues, newTimestamps);
208  batchSmoother.update(newFactors, newValues, newTimestamps);
209 
210  // Manually synchronize the Concurrent Filter and Smoother every 1.0 s
211  if(fmod(time, 1.0) < 0.01) {
212  // Synchronize the Filter and Smoother
213  concurrentSmoother.update();
214  synchronize(concurrentFilter, concurrentSmoother);
215  }
216 
217  // Print the optimized current pose
218  cout << setprecision(5) << "Timestamp = " << time << endl;
219  concurrentFilter.calculateEstimate<Pose2>(currentKey).print("Concurrent Estimate: ");
220  fixedlagSmoother.calculateEstimate<Pose2>(currentKey).print("Fixed Lag Estimate: ");
221  batchSmoother.calculateEstimate<Pose2>(currentKey).print("Batch Estimate: ");
222  cout << endl;
223 
224  // Clear contains for the next iteration
225  newTimestamps.clear();
226  newValues.clear();
227  newFactors.resize(0);
228  }
229  cout << "******************************************************************" << endl;
230  cout << "The Concurrent system and the Fixed-Lag Smoother should be " << endl;
231  cout << "the same, but the Batch version has a loop closure." << endl;
232  cout << "Adding the loop closure factor to the Concurrent version." << endl;
233  cout << "This will not update the Concurrent Filter until the next " << endl;
234  cout << "synchronization, but the Concurrent solution should be identical " << endl;
235  cout << "to the Batch solution afterwards." << endl;
236  cout << "******************************************************************" << endl;
237  cout << endl;
238 
239  // The state at 5.0s should have been transferred to the concurrent smoother at this point. Add the loop closure.
240  newFactors.push_back(loopFactor);
241  concurrentSmoother.update(newFactors, Values());
242  newFactors.resize(0);
243 
244 
245  // Now run for a few more seconds so the concurrent smoother and filter have to to re-sync
246  // Continue adding odometry factors until the loop closure may be incorporated into the concurrent smoother
247  for(double time = 8.0+deltaT; time <= 15.0; time += deltaT) {
248 
249  // Define the keys related to this timestamp
250  Key previousKey(1000 * (time-deltaT));
251  Key currentKey(1000 * (time));
252 
253  // Assign the current key to the current timestamp
254  newTimestamps[currentKey] = time;
255 
256  // Add a guess for this pose to the new values
257  // Since the robot moves forward at 2 m/s, then the position is simply: time[s]*2.0[m/s]
258  // {This is not a particularly good way to guess, but this is just an example}
259  Pose2 currentPose(time * 2.0, 0.0, 0.0);
260  newValues.insert(currentKey, currentPose);
261 
262  // Add odometry factors from two different sources with different error stats
263  Pose2 odometryMeasurement1 = Pose2(0.61, -0.08, 0.02);
264  auto odometryNoise1 = noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.05));
265  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement1, odometryNoise1));
266 
267  Pose2 odometryMeasurement2 = Pose2(0.47, 0.03, 0.01);
268  auto odometryNoise2 = noiseModel::Diagonal::Sigmas(Vector3(0.05, 0.05, 0.05));
269  newFactors.push_back(BetweenFactor<Pose2>(previousKey, currentKey, odometryMeasurement2, odometryNoise2));
270 
271  // Unlike the fixed-lag versions, the concurrent filter implementation
272  // requires the user to supply the specify which keys to marginalize
273  FastList<Key> oldKeys;
274  if(time >= lag+deltaT) {
275  oldKeys.push_back(1000 * (time-lag-deltaT));
276  }
277 
278  // Update the various inference engines
279  concurrentFilter.update(newFactors, newValues, oldKeys);
280  fixedlagSmoother.update(newFactors, newValues, newTimestamps);
281  batchSmoother.update(newFactors, newValues, newTimestamps);
282 
283  // Manually synchronize the Concurrent Filter and Smoother every 1.0 s
284  if(fmod(time, 1.0) < 0.01) {
285  // Synchronize the Filter and Smoother
286  concurrentSmoother.update();
287  synchronize(concurrentFilter, concurrentSmoother);
288  cout << "******************************************************************" << endl;
289  cout << "Syncing Concurrent Filter and Smoother." << endl;
290  cout << "******************************************************************" << endl;
291  cout << endl;
292  }
293 
294  // Print the optimized current pose
295  cout << setprecision(5) << "Timestamp = " << time << endl;
296  concurrentFilter.calculateEstimate<Pose2>(currentKey).print("Concurrent Estimate: ");
297  fixedlagSmoother.calculateEstimate<Pose2>(currentKey).print("Fixed Lag Estimate: ");
298  batchSmoother.calculateEstimate<Pose2>(currentKey).print("Batch Estimate: ");
299  cout << endl;
300 
301  // Clear contains for the next iteration
302  newTimestamps.clear();
303  newValues.clear();
304  newFactors.resize(0);
305  }
306 
307 
308  // And to demonstrate the fixed-lag aspect, print the keys contained in each smoother after 3.0 seconds
309  cout << "After 15.0 seconds, each version contains to the following keys:" << endl;
310  cout << " Concurrent Filter Keys: " << endl;
311  for(const auto key: concurrentFilter.getLinearizationPoint().keys()) {
312  cout << setprecision(5) << " Key: " << key << endl;
313  }
314  cout << " Concurrent Smoother Keys: " << endl;
315  for(const auto key: concurrentSmoother.getLinearizationPoint().keys()) {
316  cout << setprecision(5) << " Key: " << key << endl;
317  }
318  cout << " Fixed-Lag Smoother Keys: " << endl;
319  for(const auto& key_timestamp: fixedlagSmoother.timestamps()) {
320  cout << setprecision(5) << " Key: " << key_timestamp.first << endl;
321  }
322  cout << " Batch Smoother Keys: " << endl;
323  for(const auto& key_timestamp: batchSmoother.timestamps()) {
324  cout << setprecision(5) << " Key: " << key_timestamp.first << endl;
325  }
326 
327  return 0;
328 }
const gtsam::Symbol key('X', 0)
void clear()
Definition: Values.h:298
Result update(const NonlinearFactorGraph &newFactors=NonlinearFactorGraph(), const Values &newTheta=Values(), const KeyTimestampMap &timestamps=KeyTimestampMap(), const FactorIndices &factorsToRemove=FactorIndices()) override
A Levenberg-Marquardt Batch Filter that implements the Concurrent Filtering and Smoothing interface...
std::map< Key, double > KeyTimestampMap
Typedef for a Key-Timestamp map/database.
int main(int argc, char **argv)
Eigen::Vector3d Vector3
Definition: Vector.h:43
A non-templated config holding any types of Manifold-group elements.
Factor Graph consisting of non-linear factors.
IsDerived< DERIVEDFACTOR > push_back(std::shared_ptr< DERIVEDFACTOR > factor)
Add a factor directly using a shared_ptr.
Definition: FactorGraph.h:190
KeyVector keys() const
Definition: Values.cpp:218
Definition: BFloat16.h:88
virtual Result update(const NonlinearFactorGraph &newFactors=NonlinearFactorGraph(), const Values &newTheta=Values(), const std::optional< FastList< Key > > &keysToMove={}, const std::optional< std::vector< size_t > > &removeFactorIndices={})
EIGEN_STRONG_INLINE Packet4f print(const Packet4f &a)
const Values & getLinearizationPoint() const
void addPrior(Key key, const T &prior, const SharedNoiseModel &model=nullptr)
EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC bfloat16 fmod(const bfloat16 &a, const bfloat16 &b)
Definition: BFloat16.h:567
virtual Result update(const NonlinearFactorGraph &newFactors=NonlinearFactorGraph(), const Values &newTheta=Values(), const std::optional< std::vector< size_t > > &removeFactorIndices={})
static const double deltaT
Pose2 priorMean(0.0, 0.0, 0.0)
#define time
A Levenberg-Marquardt Batch Smoother that implements the Concurrent Filtering and Smoothing interface...
traits
Definition: chartTesting.h:28
auto priorNoise
std::vector< float > Values
const KeyTimestampMap & timestamps() const
virtual void resize(size_t size)
Definition: FactorGraph.h:389
void synchronize(ConcurrentFilter &filter, ConcurrentSmoother &smoother)
void insert(Key j, const Value &val)
Definition: Values.cpp:155
2D Pose
const Values & getLinearizationPoint() const
std::uint64_t Key
Integer nonlinear key type.
Definition: types.h:102


gtsam
Author(s):
autogenerated on Tue Jul 4 2023 02:34:03