testLago.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 
22 #include <gtsam/slam/lago.h>
24 #include <gtsam/slam/dataset.h>
26 #include <gtsam/geometry/Pose2.h>
27 #include <gtsam/inference/Symbol.h>
28 
30 
31 #include <cmath>
32 
33 using namespace std;
34 using namespace gtsam;
35 
36 static Symbol x0('x', 0), x1('x', 1), x2('x', 2), x3('x', 3);
37 static SharedNoiseModel model(noiseModel::Isotropic::Sigma(3, 0.1));
38 
39 namespace simpleLago {
40 // We consider a small graph:
41 // symbolic FG
42 // x2 0 1
43 // / | \ 1 2
44 // / | \ 2 3
45 // x3 | x1 2 0
46 // \ | / 0 3
47 // \ | /
48 // x0
49 //
50 
51 static Pose2 pose0 = Pose2(0.000000, 0.000000, 0.000000);
52 static Pose2 pose1 = Pose2(1.000000, 1.000000, 1.570796);
53 static Pose2 pose2 = Pose2(0.000000, 2.000000, 3.141593);
54 static Pose2 pose3 = Pose2(-1.000000, 1.000000, 4.712389);
55 
58  g.add(BetweenFactor<Pose2>(x0, x1, pose0.between(pose1), model));
59  g.add(BetweenFactor<Pose2>(x1, x2, pose1.between(pose2), model));
60  g.add(BetweenFactor<Pose2>(x2, x3, pose2.between(pose3), model));
61  g.add(BetweenFactor<Pose2>(x2, x0, pose2.between(pose0), model));
62  g.add(BetweenFactor<Pose2>(x0, x3, pose0.between(pose3), model));
63  g.addPrior(x0, pose0, model);
64  return g;
65 }
66 }
67 
68 /*******************************************************************************/
71  auto gPlus = initialize::buildPoseGraph<Pose2>(g);
73 
74  /* We should recover the following spanning tree:
75  x2
76  / \
77  / \
78  x3 x1
79  /
80  /
81  x0
82  |
83  a
84  */
87  EXPECT_LONGS_EQUAL(kAnchorKey, tree[x0]);
88  EXPECT_LONGS_EQUAL(x0, tree[x1]);
89  EXPECT_LONGS_EQUAL(x1, tree[x2]);
90  EXPECT_LONGS_EQUAL(x2, tree[x3]);
91 }
92 
93 /* *************************************************************************** */
94 TEST( Lago, checkSTandChords ) {
96  auto gPlus = initialize::buildPoseGraph<Pose2>(g);
98 
99  lago::key2doubleMap deltaThetaMap;
100  vector<size_t> spanningTreeIds; // ids of between factors forming the spanning tree T
101  vector<size_t> chordsIds; // ids of between factors corresponding to chordsIds wrt T
102  lago::getSymbolicGraph(spanningTreeIds, chordsIds, deltaThetaMap, tree, g);
103 
104  EXPECT_LONGS_EQUAL(0, spanningTreeIds[0]); // factor 0 is the first in the ST(0->1)
105  EXPECT_LONGS_EQUAL(1, spanningTreeIds[1]); // factor 1 is the second in the ST(1->2)
106  EXPECT_LONGS_EQUAL(2, spanningTreeIds[2]); // factor 2 is the third in the ST(2->3)
107 
108 }
109 
110 /* *************************************************************************** */
111 TEST(Lago, orientationsOverSpanningTree) {
113  auto gPlus = initialize::buildPoseGraph<Pose2>(g);
115 
116  // check the tree structure
118 
120  EXPECT_LONGS_EQUAL(x0, tree[x1]);
121  EXPECT_LONGS_EQUAL(x1, tree[x2]);
122  EXPECT_LONGS_EQUAL(x2, tree[x3]);
123 
125  expected[x0] = 0;
126  expected[x1] = M_PI / 2; // edges traversed: x0->x1
127  expected[x2] = M_PI; // edges traversed: x0->x1->x2
128  expected[x3] = 3 * M_PI / 2; // edges traversed: x0->x1->x2->x3
129 
130  lago::key2doubleMap deltaThetaMap;
131  vector<size_t>
132  spanningTreeIds; // ids of between factors forming the spanning tree T
133  vector<size_t>
134  chordsIds; // ids of between factors corresponding to chordsIds wrt T
135  lago::getSymbolicGraph(spanningTreeIds, chordsIds, deltaThetaMap, tree,
136  gPlus);
137 
138  lago::key2doubleMap actual;
139  actual = lago::computeThetasToRoot(deltaThetaMap, tree);
140 
141  DOUBLES_EQUAL(expected[x0], actual[x0], 1e-6);
142  DOUBLES_EQUAL(expected[x1], actual[x1], 1e-6);
143  DOUBLES_EQUAL(expected[x2], actual[x2], 1e-6);
144  DOUBLES_EQUAL(expected[x3], actual[x3], 1e-6);
145 }
146 
147 /* *************************************************************************** */
148 TEST( Lago, regularizedMeasurements ) {
150  auto gPlus = initialize::buildPoseGraph<Pose2>(g);
152 
153  lago::key2doubleMap deltaThetaMap;
154  vector<size_t> spanningTreeIds; // ids of between factors forming the spanning tree T
155  vector<size_t> chordsIds; // ids of between factors corresponding to chordsIds wrt T
156  lago::getSymbolicGraph(spanningTreeIds, chordsIds, deltaThetaMap, tree, gPlus);
157 
158  lago::key2doubleMap orientationsToRoot = lago::computeThetasToRoot(deltaThetaMap, tree);
159 
160  GaussianFactorGraph lagoGraph = lago::buildLinearOrientationGraph(spanningTreeIds, chordsIds, gPlus, orientationsToRoot, tree);
161  std::pair<Matrix,Vector> actualAb = lagoGraph.jacobian();
162  // jacobian corresponding to the orientation measurements (last entry is the prior on the anchor and is disregarded)
163  Vector actual = (Vector(5) << actualAb.second(0),actualAb.second(1),actualAb.second(2),actualAb.second(3),actualAb.second(4)).finished();
164  // this is the whitened error, so we multiply by the std to unwhiten
165  actual = 0.1 * actual;
166  // Expected regularized measurements (same for the spanning tree, corrected for the chordsIds)
167  Vector expected = (Vector(5) << M_PI/2, M_PI/2, M_PI/2, 0 , -M_PI).finished();
168 
169  EXPECT(assert_equal(expected, actual, 1e-6));
170 }
171 
172 /* *************************************************************************** */
173 TEST( Lago, smallGraphVectorValues ) {
174  bool useOdometricPath = false;
176 
177  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
178  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
179  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
180  EXPECT(assert_equal((Vector(1) << M_PI).finished(), initial.at(x2), 1e-6));
181  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI).finished(), initial.at(x3), 1e-6));
182 }
183 
184 /* *************************************************************************** */
185 TEST( Lago, smallGraphVectorValuesSP ) {
186 
188 
189  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
190  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
191  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
192  EXPECT(assert_equal((Vector(1) << M_PI ).finished(), initial.at(x2), 1e-6));
193  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI ).finished(), initial.at(x3), 1e-6));
194 }
195 
196 /* *************************************************************************** */
197 TEST( Lago, multiplePosePriors ) {
198  bool useOdometricPath = false;
201  VectorValues initial = lago::initializeOrientations(g, useOdometricPath);
202 
203  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
204  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
205  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
206  EXPECT(assert_equal((Vector(1) << M_PI).finished(), initial.at(x2), 1e-6));
207  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI).finished(), initial.at(x3), 1e-6));
208 }
209 
210 /* *************************************************************************** */
211 TEST( Lago, multiplePosePriorsSP ) {
215 
216  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
217  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
218  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
219  EXPECT(assert_equal((Vector(1) << M_PI ).finished(), initial.at(x2), 1e-6));
220  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI ).finished(), initial.at(x3), 1e-6));
221 }
222 
223 /* *************************************************************************** */
224 TEST( Lago, multiplePoseAndRotPriors ) {
225  bool useOdometricPath = false;
227  g.addPrior(x1, simpleLago::pose1.theta(), model);
228  VectorValues initial = lago::initializeOrientations(g, useOdometricPath);
229 
230  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
231  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
232  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
233  EXPECT(assert_equal((Vector(1) << M_PI).finished(), initial.at(x2), 1e-6));
234  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI).finished(), initial.at(x3), 1e-6));
235 }
236 
237 /* *************************************************************************** */
238 TEST( Lago, multiplePoseAndRotPriorsSP ) {
240  g.addPrior(x1, simpleLago::pose1.theta(), model);
242 
243  // comparison is up to M_PI, that's why we add some multiples of 2*M_PI
244  EXPECT(assert_equal((Vector(1) << 0.0).finished(), initial.at(x0), 1e-6));
245  EXPECT(assert_equal((Vector(1) << 0.5 * M_PI).finished(), initial.at(x1), 1e-6));
246  EXPECT(assert_equal((Vector(1) << M_PI ).finished(), initial.at(x2), 1e-6));
247  EXPECT(assert_equal((Vector(1) << 1.5 * M_PI ).finished(), initial.at(x3), 1e-6));
248 }
249 
250 /* *************************************************************************** */
251 TEST( Lago, smallGraphValues ) {
252 
253  // we set the orientations in the initial guess to zero
254  Values initialGuess;
255  initialGuess.insert(x0,Pose2(simpleLago::pose0.x(),simpleLago::pose0.y(),0.0));
256  initialGuess.insert(x1,Pose2(simpleLago::pose1.x(),simpleLago::pose1.y(),0.0));
257  initialGuess.insert(x2,Pose2(simpleLago::pose2.x(),simpleLago::pose2.y(),0.0));
258  initialGuess.insert(x3,Pose2(simpleLago::pose3.x(),simpleLago::pose3.y(),0.0));
259 
260  // lago does not touch the Cartesian part and only fixed the orientations
261  Values actual = lago::initialize(simpleLago::graph(), initialGuess);
262 
263  // we are in a noiseless case
265  expected.insert(x0,simpleLago::pose0);
266  expected.insert(x1,simpleLago::pose1);
267  expected.insert(x2,simpleLago::pose2);
268  expected.insert(x3,simpleLago::pose3);
269 
270  EXPECT(assert_equal(expected, actual, 1e-6));
271 }
272 
273 /* *************************************************************************** */
274 TEST( Lago, smallGraph2 ) {
275 
276  // lago does not touch the Cartesian part and only fixed the orientations
278 
279  // we are in a noiseless case
281  expected.insert(x0,simpleLago::pose0);
282  expected.insert(x1,simpleLago::pose1);
283  expected.insert(x2,simpleLago::pose2);
284  expected.insert(x3,simpleLago::pose3);
285 
286  EXPECT(assert_equal(expected, actual, 1e-6));
287 }
288 
289 /* *************************************************************************** */
290 TEST( Lago, largeGraphNoisy_orientations ) {
291 
292  string inputFile = findExampleDataFile("noisyToyGraph");
293  const auto [g, initial] = readG2o(inputFile);
294 
295  // Add prior on the pose having index (key) = 0
296  NonlinearFactorGraph graphWithPrior = *g;
297  noiseModel::Diagonal::shared_ptr priorModel = noiseModel::Diagonal::Variances(Vector3(1e-2, 1e-2, 1e-4));
298  graphWithPrior.addPrior(0, Pose2(), priorModel);
299 
300  VectorValues actualVV = lago::initializeOrientations(graphWithPrior);
301  Values actual;
302  Key keyAnc = symbol('Z',9999999);
303  for(VectorValues::const_iterator it = actualVV.begin(); it != actualVV.end(); ++it ){
304  Key key = it->first;
305  if (key != keyAnc){
306  Vector orientation = actualVV.at(key);
307  Pose2 poseLago = Pose2(0.0,0.0,orientation(0));
308  actual.insert(key, poseLago);
309  }
310  }
311  string matlabFile = findExampleDataFile("orientationsNoisyToyGraph");
312  const auto [gmatlab, expected] = readG2o(matlabFile);
313 
314  for(const auto& key_pose: expected->extract<Pose2>()){
315  const Key& k = key_pose.first;
316  const Pose2& pose = key_pose.second;
317  EXPECT(assert_equal(pose, actual.at<Pose2>(k), 1e-5));
318  }
319 }
320 
321 /* *************************************************************************** */
322 TEST( Lago, largeGraphNoisy ) {
323 
324  string inputFile = findExampleDataFile("noisyToyGraph");
325  const auto [g, initial] = readG2o(inputFile);
326 
327  // Add prior on the pose having index (key) = 0
328  NonlinearFactorGraph graphWithPrior = *g;
329  noiseModel::Diagonal::shared_ptr priorModel = noiseModel::Diagonal::Variances(Vector3(1e-2, 1e-2, 1e-4));
330  graphWithPrior.addPrior(0, Pose2(), priorModel);
331 
332  Values actual = lago::initialize(graphWithPrior);
333 
334  string matlabFile = findExampleDataFile("optimizedNoisyToyGraph");
335  const auto [gmatlab, expected] = readG2o(matlabFile);
336 
337  for(const auto& key_pose: expected->extract<Pose2>()){
338  const Key& k = key_pose.first;
339  const Pose2& pose = key_pose.second;
340  EXPECT(assert_equal(pose, actual.at<Pose2>(k), 1e-2));
341  }
342 }
343 
344 
345 /* ************************************************************************* */
346 int main() {
347  TestResult tr;
348  return TestRegistry::runAllTests(tr);
349 }
350 /* ************************************************************************* */
351 
static Pose2 pose3
Definition: testLago.cpp:54
const gtsam::Symbol key('X', 0)
PredecessorMap< KEY > findMinimumSpanningTree(const G &fg)
Definition: graph-inl.h:222
Scalar * y
static int runAllTests(TestResult &result)
Eigen::Vector3d Vector3
Definition: Vector.h:43
const ValueType at(Key j) const
Definition: Values-inl.h:204
static Symbol x0('x', 0)
Matrix expected
Definition: testMatrix.cpp:971
static Symbol x2('x', 2)
#define DOUBLES_EQUAL(expected, actual, threshold)
Definition: Test.h:141
key2doubleMap computeThetasToRoot(const key2doubleMap &deltaThetaMap, const PredecessorMap &tree)
Definition: lago.cpp:82
bool assert_equal(const Matrix &expected, const Matrix &actual, double tol)
Definition: Matrix.cpp:40
common code between lago.* (2D) and InitializePose3.* (3D)
#define M_PI
Definition: main.h:106
int main()
Definition: testLago.cpp:346
static Pose2 pose0
Definition: testLago.cpp:51
Values initial
Definition: BFloat16.h:88
iterator end()
Iterator over variables.
Definition: VectorValues.h:238
Vector & at(Key j)
Definition: VectorValues.h:139
void getSymbolicGraph(vector< size_t > &spanningTreeIds, vector< size_t > &chordsIds, key2doubleMap &deltaThetaMap, const PredecessorMap &tree, const NonlinearFactorGraph &g)
Definition: lago.cpp:101
void g(const string &key, int i)
Definition: testBTree.cpp:41
IsDerived< DERIVEDFACTOR > add(std::shared_ptr< DERIVEDFACTOR > factor)
add is a synonym for push_back.
Definition: FactorGraph.h:214
void addPrior(Key key, const T &prior, const SharedNoiseModel &model=nullptr)
std::map< Key, Key > PredecessorMap
Definition: lago.h:45
Initialize Pose2 in a factor graph using LAGO (Linear Approximation for Graph Optimization). see papers:
string inputFile
static Symbol x3('x', 3)
Eigen::VectorXd Vector
Definition: Vector.h:38
static Pose2 pose2
Definition: testLago.cpp:53
NonlinearFactorGraph graph()
Definition: testLago.cpp:56
#define EXPECT(condition)
Definition: Test.h:150
Array< double, 1, 3 > e(1./3., 0.5, 2.)
Key symbol(unsigned char c, std::uint64_t j)
VectorValues initializeOrientations(const NonlinearFactorGraph &graph, bool useOdometricPath)
Definition: lago.cpp:297
GraphAndValues readG2o(const std::string &g2oFile, const bool is3D, KernelFunctionType kernelFunctionType)
This function parses a g2o file and stores the measurements into a NonlinearFactorGraph and the initi...
Definition: dataset.cpp:621
traits
Definition: chartTesting.h:28
GTSAM_EXPORT std::string findExampleDataFile(const std::string &name)
Definition: dataset.cpp:70
TEST(Lago, findMinimumSpanningTree)
Definition: testLago.cpp:69
static const Pose3 pose(Rot3(Vector3(1, -1, -1).asDiagonal()), Point3(0, 0, 0.5))
static Pose2 pose1
Definition: testLago.cpp:52
#define EXPECT_LONGS_EQUAL(expected, actual)
Definition: Test.h:154
std::pair< Matrix, Vector > jacobian(const Ordering &ordering) const
GaussianFactorGraph buildLinearOrientationGraph(const vector< size_t > &spanningTreeIds, const vector< size_t > &chordsIds, const NonlinearFactorGraph &g, const key2doubleMap &orientationsToRoot, const PredecessorMap &tree)
Definition: lago.cpp:165
Class between(const Class &g) const
Definition: Lie.h:52
static SharedNoiseModel model(noiseModel::Isotropic::Sigma(3, 0.1))
static constexpr Key kAnchorKey
void insert(Key j, const Value &val)
Definition: Values.cpp:155
std::map< Key, double > key2doubleMap
Definition: lago.h:44
2D Pose
std::shared_ptr< Diagonal > shared_ptr
Definition: NoiseModel.h:307
set noclip points set clip one set noclip two set bar set border lt lw set xdata set ydata set zdata set x2data set y2data set boxwidth set dummy x
Eigen::Matrix< double, Eigen::Dynamic, 1 > Vector
iterator begin()
Iterator over variables.
Definition: VectorValues.h:236
Values initialize(const NonlinearFactorGraph &graph, bool useOdometricPath)
Definition: lago.cpp:375
utility functions for loading datasets
std::uint64_t Key
Integer nonlinear key type.
Definition: types.h:102
static Symbol x1('x', 1)
noiseModel::Base::shared_ptr SharedNoiseModel
Definition: NoiseModel.h:741


gtsam
Author(s):
autogenerated on Tue Jul 4 2023 02:38:35