1 // This example illustrates how to use a dynamic vertex in a graph.
2 
3 // The goal is to fit a polynomial y(x)=p(x) to a set of data. The degree of the
4 // polynomial is user-defined. The amount of samples is user defined as well.
5 
6 // Each observation consists of the pair Z_i=(x_i,z_i) where
7 // z_i=y(x_i)+w_i, where w_i is additive white noise with information
8 // matrix Omega.
9 
10 #include <unsupported/Eigen/Polynomials>
11 
12 #include "g2o/stuff/sampler.h"
13 #include "g2o/core/sparse_optimizer.h"
14 #include "g2o/core/block_solver.h"
15 #include "g2o/core/optimization_algorithm_levenberg.h"
16 #include "g2o/core/base_dynamic_vertex.h"
17 #include "g2o/core/base_unary_edge.h"
18 #include "g2o/core/base_binary_edge.h"
19 #include "g2o/solvers/eigen/linear_solver_eigen.h"
20 
21 // Declare the custom types used in the graph
22 
23 // This vertex stores the coefficients of the polynomial. It is dynamic because
24 // we can change it at runtime.
25 
26 class PolynomialCoefficientVertex : public g2o::BaseDynamicVertex<Eigen::VectorXd> {
27 public:
28   EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
29 
30   // Create the vertex
PolynomialCoefficientVertex()31   PolynomialCoefficientVertex() {
32   }
33 
34   // Read the vertex
read(std::istream & is)35   virtual bool read(std::istream& is) {
36     // Read the dimension
37     int dimension;
38     is >> dimension;
39     if (is.good() == false) {
40       return false;
41     }
42 
43     // Set the dimension; we call the method here to ensure stuff like
44     // cache and the workspace is setup
45     setDimension(dimension);
46 
47     // Read the state
48     return g2o::internal::readVector(is, _estimate);
49   }
50 
51   // Write the vertex
write(std::ostream & os) const52   virtual bool write(std::ostream& os) const {
53     os << _estimate.size() << " ";
54     return g2o::internal::writeVector(os, _estimate);
55   }
56 
57   // Reset to zero
setToOriginImpl()58   virtual void setToOriginImpl() {
59     _estimate.setZero();
60   }
61 
62   // Direct linear add
oplusImpl(const double * update)63   virtual void oplusImpl(const double* update) {
64     Eigen::VectorXd::ConstMapType v(update, _dimension);
65     _estimate += v;
66   }
67 
68   // Resize the vertex state. In this case, we want to preserve as much of the
69   // state as we can. Therefore, we use conservativeResize and pad with zeros
70   // at the end if the state dimension has increased.
setDimensionImpl(int newDimension)71   virtual bool setDimensionImpl(int newDimension)
72   {
73     int oldDimension = dimension();
74 
75     // Handle the special case this is the first time
76     if (oldDimension == Eigen::Dynamic) {
77       _estimate.resize(newDimension);
78       _estimate.setZero();
79       return true;
80     }
81 
82     _estimate.conservativeResize(newDimension);
83 
84     // If the state has expanded, pad with zeros
85     if (oldDimension < newDimension)
86       _estimate.tail(newDimension-oldDimension).setZero();
87 
88     return true;
89   }
90 };
91 
92 // This edge provides an observation of the polynomial at a single value.
93 // The assumed model is z = p(x) + w,
94 // where w is the additive noise with covariance equal to inv(Omega).
95 
96 // Note that x is not a measurement so it has to be stored separately.
97 
98 class PolynomialSingleValueEdge : public g2o::BaseUnaryEdge<1, double, PolynomialCoefficientVertex> {
99 
100 public:
101   EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
102 
PolynomialSingleValueEdge(double x,double z,const PolynomialSingleValueEdge::InformationType & omega)103   PolynomialSingleValueEdge(double x, double z, const PolynomialSingleValueEdge::InformationType& omega) : _x(x) {
104     _x = x;
105     setMeasurement(z);
106     setInformation(omega);
107   }
108 
read(std::istream & is)109   virtual bool read(std::istream& is) {
110     double z;
111     is >> _x >> z;
112     setMeasurement(z);
113     return readInformationMatrix(is);
114   }
115 
write(std::ostream & os) const116   virtual bool write(std::ostream& os) const {
117     os << _x << " " << _measurement;
118     return writeInformationMatrix(os);
119   }
120 
121   // Compute the measurement from the eigen polynomial module
computeError()122   virtual void computeError() {
123     const PolynomialCoefficientVertex* vertex = static_cast<const PolynomialCoefficientVertex*> (_vertices[0]);
124     _error[0] = _measurement - Eigen::poly_eval(vertex->estimate(), _x);
125   }
126 
127 private:
128 
129   // The point that the polynomial is computed at
130   double _x;
131 };
132 
main(int argc,const char * argv[])133 int main(int argc, const char* argv[]) {
134 
135   // Number of dimensions of the polynomial; the default is 4
136   int polynomialDimension = 4;
137   if (argc > 1) {
138     polynomialDimension = atoi(argv[1]);
139   }
140 
141   // Create the coefficients for the polynomial (all drawn randomly)
142   Eigen::VectorXd p(polynomialDimension);
143   for (int i = 0; i < polynomialDimension; ++i) {
144     p[i] = g2o::sampleUniform(-1, 1);
145   }
146 
147   std::cout << "Ground truth vector=" << p.transpose() << std::endl;
148 
149   // The number of observations in the polynomial; the defautl is 6
150   int obs = 6;
151   if (argc > 2) {
152     obs = atoi(argv[2]);
153   }
154 
155   // Sample the observations; we don't do anything with them here, but
156   // they could be plotted
157   double sigmaZ = 0.1;
158   Eigen::VectorXd x(obs);
159   Eigen::VectorXd z(obs);
160 
161   for (int i = 0; i < obs; ++i) {
162     x[i] = g2o::sampleUniform(-5, 5);
163     z[i] = Eigen::poly_eval(p, x[i]) + sigmaZ * g2o::sampleGaussian();
164   }
165 
166   // Construct the graph and set up the solver and optimiser
167   std::unique_ptr<g2o::BlockSolverX::LinearSolverType> linearSolver =
168       g2o::make_unique<g2o::LinearSolverEigen<g2o::BlockSolverX::PoseMatrixType>>();
169 
170   // Set up the solver
171   std::unique_ptr<g2o::BlockSolverX> blockSolver =
172       g2o::make_unique<g2o::BlockSolverX>(move(linearSolver));
173 
174   // Set up the optimisation algorithm
175   g2o::OptimizationAlgorithm* optimisationAlgorithm =
176     new g2o::OptimizationAlgorithmLevenberg(move(blockSolver));
177 
178   // Create the graph and configure it
179   std::unique_ptr<g2o::SparseOptimizer> optimizer = g2o::make_unique<g2o::SparseOptimizer>();
180   optimizer->setVerbose(true);
181   optimizer->setAlgorithm(optimisationAlgorithm);
182 
183   // Create the vertex; note its dimension is currently is undefined
184   PolynomialCoefficientVertex* pv = new PolynomialCoefficientVertex();
185   pv->setId(0);
186   optimizer->addVertex(pv);
187 
188   // Create the information matrix
189   PolynomialSingleValueEdge::InformationType omega = PolynomialSingleValueEdge::InformationType::Zero();
190   omega(0, 0) = 1 / (sigmaZ * sigmaZ);
191 
192   // Create the observations and the edges
193   for (int i = 0; i < obs; ++i) {
194     PolynomialSingleValueEdge* pe = new PolynomialSingleValueEdge(x[i], z[i], omega);
195     pe->setVertex(0, pv);
196     optimizer->addEdge(pe);
197   }
198 
199   // Now run the same optimization problem for different choices of
200   // dimension of the polynomial vertex. This shows how we can
201   // dynamically change the vertex dimensions in an alreacy
202   // constructed graph. Note that you must call initializeOptimization
203   // before you can optimize after a state dimension has changed.
204   for (int testDimension = 1; testDimension <= polynomialDimension; ++testDimension) {
205     pv->setDimension(testDimension);
206     optimizer->initializeOptimization();
207     optimizer->optimize(10);
208     std::cout << "Computed parameters = " << pv->estimate().transpose() << std::endl;
209   }
210   for (int testDimension = polynomialDimension - 1; testDimension >= 1; --testDimension) {
211     pv->setDimension(testDimension);
212     optimizer->initializeOptimization();
213     optimizer->optimize(10);
214     std::cout << "Computed parameters = " << pv->estimate().transpose() << std::endl;
215   }
216 }
217