Main MRPT website > C++ reference for MRPT 1.9.9
levmarq.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2017, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #ifndef GRAPH_SLAM_LEVMARQ_H
10 #define GRAPH_SLAM_LEVMARQ_H
11 
12 #include <mrpt/graphslam/types.h>
13 #include <mrpt/utils/TParameters.h>
14 #include <mrpt/utils/stl_containers_utils.h> // find_in_vector()
15 #include <mrpt/graphslam/levmarq_impl.h> // Aux classes
16 
17 #include <iterator> // ostream_iterator
18 
19 namespace mrpt
20 {
21 namespace graphslam
22 {
23 /** Optimize a graph of pose constraints using the Sparse Pose Adjustment (SPA)
24  *sparse representation and a Levenberg-Marquardt optimizer.
25  * This method works for all types of graphs derived from \a CNetworkOfPoses
26  *(see its reference mrpt::graphs::CNetworkOfPoses for the list).
27  * The input data are all the pose constraints in \a graph (graph.edges), and
28  *the gross first estimations of the "global" pose coordinates (in
29  *graph.nodes).
30  *
31  * Note that these first coordinates can be obtained with
32  *mrpt::graphs::CNetworkOfPoses::dijkstra_nodes_estimate().
33  *
34  * The method implemented in this file is based on this work:
35  * - "Efficient Sparse Pose Adjustment for 2D Mapping", Kurt Konolige et al.,
36  *2010.
37  * , but generalized for not only 2D but 2D and 3D poses, and using on-manifold
38  *optimization.
39  *
40  * \param[in,out] graph The input edges and output poses.
41  * \param[out] out_info Some basic output information on the process.
42  * \param[in] nodes_to_optimize The list of nodes whose global poses are to be
43  *optimized. If nullptr (default), all the node IDs are optimized (but that
44  *marked as \a root in the graph).
45  * \param[in] extra_params Optional parameters, see below.
46  * \param[in] functor_feedback Optional: a pointer to a user function can be
47  *set here to be called on each LM loop iteration (eg to refresh the current
48  *state and error, refresh a GUI, etc.)
49  *
50  * List of optional parameters by name in "extra_params":
51  * - "verbose": (default=0) If !=0, produce verbose ouput.
52  * - "max_iterations": (default=100) Maximum number of Lev-Marq.
53  *iterations.
54  * - "scale_hessian": (default=0.1) Multiplies the Hessian matrix by this
55  *scalar (may improve convergence speed).
56  * - "initial_lambda": (default=0) <=0 means auto guess, otherwise, initial
57  *lambda value for the lev-marq algorithm.
58  * - "tau": (default=1e-3) Initial tau value for the lev-marq algorithm.
59  * - "e1": (default=1e-6) Lev-marq algorithm iteration stopping criterion
60  *#1:
61  *|gradient| < e1
62  * - "e2": (default=1e-6) Lev-marq algorithm iteration stopping criterion
63  *#2:
64  *|delta_incr| < e2*(x_norm+e2)
65  *
66  * \note The following graph types are supported:
67  *mrpt::graphs::CNetworkOfPoses2D, mrpt::graphs::CNetworkOfPoses3D,
68  *mrpt::graphs::CNetworkOfPoses2DInf, mrpt::graphs::CNetworkOfPoses3DInf
69  *
70  * \tparam GRAPH_T Normally a
71  *mrpt::graphs::CNetworkOfPoses<EDGE_TYPE,MAPS_IMPLEMENTATION>. Users won't
72  *have to write this template argument by hand, since the compiler will
73  *auto-fit it depending on the type of the graph object.
74  * \sa The example "graph_slam_demo"
75  * \ingroup mrpt_graphslam_grp
76  * \note Implementation can be found in file \a levmarq_impl.h
77  */
78 template <class GRAPH_T>
80  GRAPH_T& graph, TResultInfoSpaLevMarq& out_info,
81  const std::set<mrpt::utils::TNodeID>* in_nodes_to_optimize = nullptr,
82  const mrpt::utils::TParametersDouble& extra_params =
84  typename graphslam_traits<GRAPH_T>::TFunctorFeedback functor_feedback =
86 {
87  using namespace mrpt;
88  using namespace mrpt::poses;
89  using namespace mrpt::graphslam;
90  using namespace mrpt::math;
91  using namespace mrpt::utils;
92  using namespace std;
93 
95 
96  // Typedefs to make life easier:
97  typedef graphslam_traits<GRAPH_T> gst;
98 
99  typename gst::Array_O array_O_zeros;
100  array_O_zeros.fill(0); // Auxiliary var with all zeros
101 
102  // The size of things here (because size matters...)
103  static const unsigned int DIMS_POSE = gst::SE_TYPE::VECTOR_SIZE;
104 
105  // Read extra params:
106  const bool verbose = 0 != extra_params.getWithDefaultVal("verbose", 0);
107  const size_t max_iters =
108  extra_params.getWithDefaultVal("max_iterations", 100);
109  const bool enable_profiler =
110  0 != extra_params.getWithDefaultVal("profiler", 0);
111  // LM params:
112  const double initial_lambda = extra_params.getWithDefaultVal(
113  "initial_lambda", 0); // <=0: means auto guess
114  const double tau = extra_params.getWithDefaultVal("tau", 1e-3);
115  const double e1 = extra_params.getWithDefaultVal("e1", 1e-6);
116  const double e2 = extra_params.getWithDefaultVal("e2", 1e-6);
117 
118  const double SCALE_HESSIAN =
119  extra_params.getWithDefaultVal("scale_hessian", 1);
120 
121  mrpt::utils::CTimeLogger profiler(enable_profiler);
122  profiler.enter("optimize_graph_spa_levmarq (entire)");
123 
124  // Make list of node IDs to optimize, since the user may want only a subset
125  // of them to be optimized:
126  profiler.enter("optimize_graph_spa_levmarq.list_IDs");
127  const set<TNodeID>* nodes_to_optimize;
128  set<TNodeID> nodes_to_optimize_auxlist; // Used only if
129  // in_nodes_to_optimize==nullptr
130  if (in_nodes_to_optimize)
131  {
132  nodes_to_optimize = in_nodes_to_optimize;
133  }
134  else
135  {
136  // We have to make the list of IDs:
138  graph.nodes.begin();
139  it != graph.nodes.end(); ++it)
140  if (it->first != graph.root) // Root node is fixed.
141  nodes_to_optimize_auxlist.insert(
142  nodes_to_optimize_auxlist.end(),
143  it->first); // Provide the "first guess" insert position
144  // for efficiency
145  nodes_to_optimize = &nodes_to_optimize_auxlist;
146  }
147  profiler.leave("optimize_graph_spa_levmarq.list_IDs"); // ---------------/
148 
149  // Number of nodes to optimize, or free variables:
150  const size_t nFreeNodes = nodes_to_optimize->size();
151  ASSERT_ABOVE_(nFreeNodes, 0)
152 
153  if (verbose)
154  {
155  cout << "[" << __CURRENT_FUNCTION_NAME__ << "] " << nFreeNodes
156  << " nodes to optimize: ";
157  if (nFreeNodes < 14)
158  {
159  ostream_iterator<TNodeID> out_it(cout, ", ");
160  std::copy(
161  nodes_to_optimize->begin(), nodes_to_optimize->end(), out_it);
162  }
163  cout << endl;
164  }
165 
166  // The list of those edges that will be considered in this optimization
167  // (many may be discarded
168  // if we are optimizing just a subset of all the nodes):
169  typedef typename gst::observation_info_t observation_info_t;
170  vector<observation_info_t> lstObservationData;
171 
172  // Note: We'll need those Jacobians{i->j} where at least one "i" or "j"
173  // is a free variable (i.e. it's in nodes_to_optimize)
174  // Now, build the list of all relevent "observations":
175  for (typename gst::edge_const_iterator it = graph.edges.begin();
176  it != graph.edges.end(); ++it)
177  {
178  const TPairNodeIDs& ids = it->first;
179  const typename gst::graph_t::edge_t& edge = it->second;
180 
181  if (nodes_to_optimize->find(ids.first) == nodes_to_optimize->end() &&
182  nodes_to_optimize->find(ids.second) == nodes_to_optimize->end())
183  continue; // Skip this edge, none of the IDs are free variables.
184 
185  // get the current global poses of both nodes in this constraint:
187  graph.nodes.find(ids.first);
189  graph.nodes.find(ids.second);
191  itP1 != graph.nodes.end(),
192  "Node1 in an edge does not have a global pose in 'graph.nodes'.")
194  itP2 != graph.nodes.end(),
195  "Node2 in an edge does not have a global pose in 'graph.nodes'.")
196 
197  const typename gst::graph_t::constraint_t::type_value& EDGE_POSE =
198  edge.getPoseMean();
199 
200  // Add all the data to the list of relevant observations:
201  typename gst::observation_info_t new_entry;
202  new_entry.edge = it;
203  new_entry.edge_mean = &EDGE_POSE;
204  new_entry.P1 = &itP1->second;
205  new_entry.P2 = &itP2->second;
206 
207  lstObservationData.push_back(new_entry);
208  }
209 
210  // The number of constraints, or observations actually implied in this
211  // problem:
212  const size_t nObservations = lstObservationData.size();
213  ASSERT_ABOVE_(nObservations, 0)
214 
215  // Cholesky object, as a pointer to reuse it between iterations:
216 
217  typedef std::unique_ptr<CSparseMatrix::CholeskyDecomp>
218  SparseCholeskyDecompPtr;
219  SparseCholeskyDecompPtr ptrCh;
220 
221  // The list of Jacobians: for each constraint i->j,
222  // we need the pair of Jacobians: { dh(xi,xj)_dxi, dh(xi,xj)_dxj },
223  // which are "first" and "second" in each pair.
224  // Index of the map are the node IDs {i,j} for each contraint.
225  typename gst::map_pairIDs_pairJacobs_t lstJacobians;
226  // The vector of errors: err_k = SE(2/3)::pseudo_Ln( P_i * EDGE_ij *
227  // inv(P_j) )
229  errs; // Separated vectors for each edge. i \in [0,nObservations-1], in
230  // same order than lstObservationData
231 
232  // ===================================
233  // Compute Jacobians & errors
234  // ===================================
235  profiler.enter("optimize_graph_spa_levmarq.Jacobians&err");
236  double total_sqr_err = computeJacobiansAndErrors<GRAPH_T>(
237  graph, lstObservationData, lstJacobians, errs);
238  profiler.leave("optimize_graph_spa_levmarq.Jacobians&err");
239 
240  // Only once (since this will be static along iterations), build a quick
241  // look-up table with the
242  // indices of the free nodes associated to the (first_id,second_id) of each
243  // Jacobian pair:
244  // ------------------------------------------------------------------------
245  vector<pair<size_t, size_t>>
246  observationIndex_to_relatedFreeNodeIndex; // "relatedFreeNodeIndex"
247  // means into
248  // [0,nFreeNodes-1], or "-1"
249  // if that node is fixed, as
250  // ordered in
251  // "nodes_to_optimize"
252  observationIndex_to_relatedFreeNodeIndex.reserve(nObservations);
253  ASSERTDEB_(lstJacobians.size() == nObservations)
255  lstJacobians.begin();
256  itJ != lstJacobians.end(); ++itJ)
257  {
258  const TNodeID id1 = itJ->first.first;
259  const TNodeID id2 = itJ->first.second;
260  observationIndex_to_relatedFreeNodeIndex.push_back(
261  std::make_pair(
262  mrpt::utils::find_in_vector(id1, *nodes_to_optimize),
263  mrpt::utils::find_in_vector(id2, *nodes_to_optimize)));
264  }
265 
266  // other important vars for the main loop:
267  CVectorDouble grad(nFreeNodes * DIMS_POSE);
268  grad.setZero();
269  typedef typename mrpt::aligned_containers<
270  TNodeID, typename gst::matrix_VxV_t>::map_t map_ID2matrix_VxV_t;
271  vector<map_ID2matrix_VxV_t> H_map(nFreeNodes);
272 
273  double lambda = initial_lambda; // Will be actually set on first iteration.
274  double v = 1; // was 2, changed since it's modified in the first pass.
275  bool have_to_recompute_H_and_grad = true;
276 
277  // -----------------------------------------------------------
278  // Main iterative loop of Levenberg-Marquardt algorithm
279  // For notation and overall algorithm overview, see:
280  // http://www.mrpt.org/Levenberg%E2%80%93Marquardt_algorithm
281  // -----------------------------------------------------------
282  size_t last_iter = 0;
283 
284  for (size_t iter = 0; iter < max_iters; ++iter)
285  {
286  last_iter = iter;
287 
288  if (have_to_recompute_H_and_grad) // This will be false only when the
289  // delta leads to a worst solution
290  // and only a change in lambda is
291  // needed.
292  {
293  have_to_recompute_H_and_grad = false;
294 
295  // ======================================================================
296  // Compute the gradient: grad = J^t * errs
297  // ======================================================================
298  // "grad" can be seen as composed of N independent arrays, each one
299  // being:
300  // grad_i = \sum_k J^t_{k->i} errs_k
301  // that is: g_i is the "dot-product" of the i'th (transposed)
302  // block-column of J and the vector of errors "errs"
303  profiler.enter("optimize_graph_spa_levmarq.grad");
305  grad_parts(nFreeNodes, array_O_zeros);
306 
307  // "lstJacobians" is sorted in the same order than
308  // "lstObservationData":
309  ASSERT_EQUAL_(lstJacobians.size(), lstObservationData.size())
310 
311  {
312  size_t idx_obs;
314 
315  for (idx_obs = 0, itJ = lstJacobians.begin();
316  itJ != lstJacobians.end(); ++itJ, ++idx_obs)
317  {
318  ASSERTDEB_(
319  itJ->first ==
320  lstObservationData[idx_obs].edge->first) // make sure
321  // they're in
322  // the
323  // expected
324  // order!
325 
326  // grad[k] += J^t_{i->k} * Inf.Matrix * errs_i
327  // k: [0,nFreeNodes-1] <-- IDs.first & IDs.second
328  // i: [0,nObservations-1] <--- idx_obs
329 
330  // Get the corresponding indices in the vector of "free
331  // variables" being optimized:
332  const size_t idx1 =
333  observationIndex_to_relatedFreeNodeIndex[idx_obs].first;
334  const size_t idx2 =
335  observationIndex_to_relatedFreeNodeIndex[idx_obs]
336  .second;
337 
338  if (idx1 != string::npos)
341  itJ->second.first /* J */,
342  lstObservationData[idx_obs].edge /* W */,
343  errs[idx_obs] /* err */,
344  grad_parts[idx1] /* out */
345  );
346 
347  if (idx2 != string::npos)
350  itJ->second.second /* J */,
351  lstObservationData[idx_obs].edge /* W */,
352  errs[idx_obs] /* err */,
353  grad_parts[idx2] /* out */
354  );
355  }
356  }
357 
358  // build the gradient as a single vector:
359  ::memcpy(
360  &grad[0], &grad_parts[0],
361  nFreeNodes * DIMS_POSE * sizeof(grad[0])); // Ohh yeahh!
362  grad /= SCALE_HESSIAN;
363  profiler.leave("optimize_graph_spa_levmarq.grad");
364 
365  // End condition #1
366  const double grad_norm_inf = math::norm_inf(
367  grad); // inf-norm (abs. maximum value) of the gradient
368  if (grad_norm_inf <= e1)
369  {
370  // Change is too small
371  if (verbose)
372  cout << "[" << __CURRENT_FUNCTION_NAME__ << "] "
373  << mrpt::format(
374  "End condition #1: math::norm_inf(g)<=e1 "
375  ":%f<=%f\n",
376  grad_norm_inf, e1);
377  break;
378  }
379 
380  profiler.enter("optimize_graph_spa_levmarq.sp_H:build map");
381  // ======================================================================
382  // Build sparse representation of the upper triangular part of
383  // the Hessian matrix H = J^t * J
384  //
385  // Sparse memory structure is a vector of maps, such as:
386  // - H_map[i]: corresponds to the i'th column of H.
387  // Here "i" corresponds to [0,N-1] indices of
388  // appearance in the map "*nodes_to_optimize".
389  // - H_map[i][j] is the entry for the j'th row, with "j" also in
390  // the range [0,N-1] as ordered in "*nodes_to_optimize".
391  // ======================================================================
392  {
393  size_t idxObs;
395  itJacobPair;
396 
397  for (idxObs = 0, itJacobPair = lstJacobians.begin();
398  idxObs < nObservations; ++itJacobPair, ++idxObs)
399  {
400  // We sort IDs such as "i" < "j" and we can build just the
401  // upper triangular part of the Hessian.
402  const bool edge_straight =
403  itJacobPair->first.first < itJacobPair->first.second;
404 
405  // Indices in the "H_map" vector:
406  const size_t idx_i =
407  edge_straight
408  ? observationIndex_to_relatedFreeNodeIndex[idxObs]
409  .first
410  : observationIndex_to_relatedFreeNodeIndex[idxObs]
411  .second;
412  const size_t idx_j =
413  edge_straight
414  ? observationIndex_to_relatedFreeNodeIndex[idxObs]
415  .second
416  : observationIndex_to_relatedFreeNodeIndex[idxObs]
417  .first;
418 
419  const bool is_i_free_node = idx_i != string::npos;
420  const bool is_j_free_node = idx_j != string::npos;
421 
422  // Take references to both Jacobians (wrt pose "i" and pose
423  // "j"), taking into account the possible
424  // switch in their order:
425 
426  const typename gst::matrix_VxV_t& J1 =
427  edge_straight ? itJacobPair->second.first
428  : itJacobPair->second.second;
429  const typename gst::matrix_VxV_t& J2 =
430  edge_straight ? itJacobPair->second.second
431  : itJacobPair->second.first;
432 
433  // Is "i" a free (to be optimized) node? -> Ji^t * Inf * Ji
434  if (is_i_free_node)
435  {
436  typename gst::matrix_VxV_t JtJ(
440  J1, JtJ, lstObservationData[idxObs].edge);
441  H_map[idx_i][idx_i] += JtJ;
442  }
443  // Is "j" a free (to be optimized) node? -> Jj^t * Inf * Jj
444  if (is_j_free_node)
445  {
446  typename gst::matrix_VxV_t JtJ(
450  J2, JtJ, lstObservationData[idxObs].edge);
451  H_map[idx_j][idx_j] += JtJ;
452  }
453  // Are both "i" and "j" free nodes? -> Ji^t * Inf * Jj
454  if (is_i_free_node && is_j_free_node)
455  {
456  typename gst::matrix_VxV_t JtJ(
460  J1, J2, JtJ, lstObservationData[idxObs].edge);
461  H_map[idx_j][idx_i] += JtJ;
462  }
463  }
464  }
465  profiler.leave("optimize_graph_spa_levmarq.sp_H:build map");
466 
467  // Just in the first iteration, we need to calculate an estimate for
468  // the first value of "lamdba":
469  if (lambda <= 0 && iter == 0)
470  {
471  profiler.enter(
472  "optimize_graph_spa_levmarq.lambda_init"); // ---\ .
473  double H_diagonal_max = 0;
474  for (size_t i = 0; i < nFreeNodes; i++)
475  for (typename map_ID2matrix_VxV_t::const_iterator it =
476  H_map[i].begin();
477  it != H_map[i].end(); ++it)
478  {
479  const size_t j =
480  it->first; // entry submatrix is for (i,j).
481  if (i == j)
482  {
483  for (size_t k = 0; k < DIMS_POSE; k++)
485  H_diagonal_max,
486  it->second.get_unsafe(k, k));
487  }
488  }
489  lambda = tau * H_diagonal_max;
490 
491  profiler.leave(
492  "optimize_graph_spa_levmarq.lambda_init"); // ---/
493  }
494  else
495  {
496  // After recomputing H and the grad, we update lambda:
497  lambda *= 0.1; // std::max(0.333, 1-pow(2*rho-1,3.0) );
498  }
499  utils::keep_max(lambda, 1e-200); // JL: Avoids underflow!
500  v = 2;
501 #if 0
502  { mrpt::math::CMatrixDouble H; sp_H.get_dense(H); H.saveToTextFile("d:\\H.txt"); }
503 #endif
504  } // end "have_to_recompute_H_and_grad"
505 
506  if (verbose)
507  cout << "[" << __CURRENT_FUNCTION_NAME__ << "] Iter: " << iter
508  << " ,total sqr. err: " << total_sqr_err
509  << ", avrg. err per edge: "
510  << std::sqrt(total_sqr_err / nObservations)
511  << " lambda: " << lambda << endl;
512 
513  // Feedback to the user:
514  if (functor_feedback)
515  {
516  functor_feedback(graph, iter, max_iters, total_sqr_err);
517  }
518 
519  profiler.enter("optimize_graph_spa_levmarq.sp_H:build");
520  // Now, build the actual sparse matrix H:
521  // Note: we only need to fill out the upper diagonal part, since
522  // Cholesky will later on ignore the other part.
523  CSparseMatrix sp_H(nFreeNodes * DIMS_POSE, nFreeNodes * DIMS_POSE);
524  for (size_t i = 0; i < nFreeNodes; i++)
525  {
526  const size_t i_offset = i * DIMS_POSE;
527 
528  for (typename map_ID2matrix_VxV_t::const_iterator it =
529  H_map[i].begin();
530  it != H_map[i].end(); ++it)
531  {
532  const size_t j = it->first; // entry submatrix is for (i,j).
533  const size_t j_offset = j * DIMS_POSE;
534 
535  // For i==j (diagonal blocks), it's different, since we only
536  // need to insert their
537  // upper-diagonal half and also we have to add the lambda*I to
538  // the diagonal from the Lev-Marq. algorithm:
539  if (i == j)
540  {
541  for (size_t r = 0; r < DIMS_POSE; r++)
542  {
543  // c=r: add lambda from LM
544  sp_H.insert_entry_fast(
545  j_offset + r, i_offset + r,
546  it->second.get_unsafe(r, r) + lambda);
547  // c>r:
548  for (size_t c = r + 1; c < DIMS_POSE; c++)
549  sp_H.insert_entry_fast(
550  j_offset + r, i_offset + c,
551  it->second.get_unsafe(r, c));
552  }
553  }
554  else
555  {
556  sp_H.insert_submatrix(j_offset, i_offset, it->second);
557  }
558  }
559  } // end for each free node, build sp_H
560 
561  sp_H.compressFromTriplet();
562  profiler.leave("optimize_graph_spa_levmarq.sp_H:build");
563 
564  // Use the cparse Cholesky decomposition to efficiently solve:
565  // (H+\lambda*I) \delta = -J^t * (f(x)-z)
566  // A x = b --> x = A^{-1} * b
567  //
568  CVectorDouble delta; // The (minus) increment to be added to the
569  // current solution in this step
570  try
571  {
572  profiler.enter("optimize_graph_spa_levmarq.sp_H:chol");
573  if (!ptrCh.get())
574  ptrCh = SparseCholeskyDecompPtr(
576  else
577  ptrCh.get()->update(sp_H);
578  profiler.leave("optimize_graph_spa_levmarq.sp_H:chol");
579 
580  profiler.enter("optimize_graph_spa_levmarq.sp_H:backsub");
581  ptrCh->backsub(grad, delta);
582  profiler.leave("optimize_graph_spa_levmarq.sp_H:backsub");
583  }
584  catch (CExceptionNotDefPos&)
585  {
586  // not positive definite so increase mu and try again
587  if (verbose)
588  cout << "[" << __CURRENT_FUNCTION_NAME__
589  << "] Got non-definite positive matrix, retrying with a "
590  "larger lambda...\n";
591  lambda *= v;
592  v *= 2;
593  if (lambda > 1e9)
594  { // enough!
595  break;
596  }
597  continue; // try again with this params
598  }
599 
600  // Compute norm of the increment vector:
601  profiler.enter("optimize_graph_spa_levmarq.delta_norm");
602  const double delta_norm = math::norm(delta);
603  profiler.leave("optimize_graph_spa_levmarq.delta_norm");
604 
605  // Compute norm of the current solution vector:
606  profiler.enter("optimize_graph_spa_levmarq.x_norm");
607  double x_norm = 0;
608  {
609  for (set<TNodeID>::const_iterator it = nodes_to_optimize->begin();
610  it != nodes_to_optimize->end(); ++it)
611  {
613  graph.nodes.find(*it);
614  const typename gst::graph_t::constraint_t::type_value& P =
615  itP->second;
616  for (size_t i = 0; i < DIMS_POSE; i++) x_norm += square(P[i]);
617  }
618  x_norm = std::sqrt(x_norm);
619  }
620  profiler.leave("optimize_graph_spa_levmarq.x_norm");
621 
622  // Test end condition #2:
623  const double thres_norm = e2 * (x_norm + e2);
624  if (delta_norm < thres_norm)
625  {
626  // The change is too small: we're done here...
627  if (verbose)
628  cout << "[" << __CURRENT_FUNCTION_NAME__ << "] "
629  << format(
630  "End condition #2: %e < %e\n", delta_norm,
631  thres_norm);
632  break;
633  }
634  else
635  {
636  // =====================================================================================
637  // Accept this delta? Try it and look at the increase/decrease of
638  // the error:
639  // new_x = old_x [+] (-delta) , with [+] being the "manifold
640  // exp()+add" operation.
641  // =====================================================================================
642  typename gst::graph_t::global_poses_t old_poses_backup;
643 
644  {
645  ASSERTDEB_(
646  delta.size() == nodes_to_optimize->size() * DIMS_POSE)
647  const double* delta_ptr = &delta[0];
649  nodes_to_optimize->begin();
650  it != nodes_to_optimize->end(); ++it)
651  {
652  // exp_delta_i = Exp_SE( delta_i )
653  typename gst::graph_t::constraint_t::type_value
654  exp_delta_pose(UNINITIALIZED_POSE);
655  typename gst::Array_O exp_delta;
656  for (size_t i = 0; i < DIMS_POSE; i++)
657  exp_delta[i] = -*delta_ptr++; // The "-" sign is for
658  // the missing "-"
659  // carried all this time
660  // from above
661  gst::SE_TYPE::exp(exp_delta, exp_delta_pose);
662 
663  // new_x_i = exp_delta_i (+) old_x_i
665  it_old_value = graph.nodes.find(*it);
666  old_poses_backup[*it] =
667  it_old_value->second; // back up the old pose as a copy
668  it_old_value->second.composeFrom(
669  exp_delta_pose, it_old_value->second);
670  }
671  }
672 
673  // =============================================================
674  // Compute Jacobians & errors with the new "graph.nodes" info:
675  // =============================================================
676  typename gst::map_pairIDs_pairJacobs_t new_lstJacobians;
678  new_errs;
679 
680  profiler.enter("optimize_graph_spa_levmarq.Jacobians&err");
681  double new_total_sqr_err = computeJacobiansAndErrors<GRAPH_T>(
682  graph, lstObservationData, new_lstJacobians, new_errs);
683  profiler.leave("optimize_graph_spa_levmarq.Jacobians&err");
684 
685  // Now, to decide whether to accept the change:
686  if (new_total_sqr_err < total_sqr_err) // rho>0)
687  {
688  // Accept the new point:
689  new_lstJacobians.swap(lstJacobians);
690  new_errs.swap(errs);
691  std::swap(new_total_sqr_err, total_sqr_err);
692 
693  // Instruct to recompute H and grad from the new Jacobians.
694  have_to_recompute_H_and_grad = true;
695  }
696  else
697  {
698  // Nope...
699  // We have to revert the "graph.nodes" to "old_poses_backup"
701  old_poses_backup.begin();
702  it != old_poses_backup.end(); ++it)
703  graph.nodes[it->first] = it->second;
704 
705  if (verbose)
706  cout << "[" << __CURRENT_FUNCTION_NAME__
707  << "] Got larger error=" << new_total_sqr_err
708  << ", retrying with a larger lambda...\n";
709  // Change params and try again:
710  lambda *= v;
711  v *= 2;
712  }
713 
714  } // end else end condition #2
715 
716  } // end for each iter
717 
718  profiler.leave("optimize_graph_spa_levmarq (entire)");
719 
720  // Fill out basic output data:
721  // ------------------------------
722  out_info.num_iters = last_iter;
723  out_info.final_total_sq_error = total_sqr_err;
724 
725  MRPT_END
726 } // end of optimize_graph_spa_levmarq()
727 
728 /** @} */ // end of grouping
729 
730 } // End of namespace
731 } // End of namespace
732 
733 #endif
#define ASSERT_EQUAL_(__A, __B)
GLuint * ids
Definition: glext.h:3906
Classes for serialization, sockets, ini-file manipulation, streams, list of properties-values, timewatch, extensions to STL.
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
#define ASSERT_ABOVE_(__A, __B)
void insert_entry_fast(const size_t row, const size_t col, const double val)
This was an optimized version, but is now equivalent to insert_entry() due to the need to be compatib...
Scalar * iterator
Definition: eigen_plugins.h:26
EIGEN_STRONG_INLINE iterator begin()
Definition: eigen_plugins.h:29
Column vector, like Eigen::MatrixX*, but automatically initialized to zeros since construction...
Definition: eigen_frwds.h:42
size_t num_iters
The number of LM iterations executed.
Auxiliary class to hold the results of a Cholesky factorization of a sparse matrix.
A sparse matrix structure, wrapping T.
STL namespace.
void compressFromTriplet()
ONLY for TRIPLET matrices: convert the matrix in a column-compressed form.
const Scalar * const_iterator
Definition: eigen_plugins.h:27
Helper types for STL containers with Eigen memory allocators.
T square(const T x)
Inline function for the square of a number.
Definition: bits.h:55
Used in mrpt::math::CSparseMatrix.
Definition: CSparseMatrix.h:40
#define ASSERTDEBMSG_(f, __ERROR_MSG)
SLAM methods related to graphs of pose constraints.
This base provides a set of functions for maths stuff.
Definition: CArrayNumeric.h:19
#define MRPT_END
const GLubyte * c
Definition: glext.h:6313
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
Definition: format.cpp:19
uint64_t TNodeID
The type for node IDs in graphs of different types.
Definition: types_simple.h:49
void keep_max(T &var, const K test_val)
If the second argument is above the first one, set the first argument to this higher value...
Definition: bits.h:227
Classes for 2D/3D geometry representation, both of single values and probability density distribution...
Definition: CPoint.h:17
Auxiliary traits template for use among graph-slam problems to make life easier with these complicate...
CONTAINER::Scalar norm_inf(const CONTAINER &v)
#define MRPT_START
const GLdouble * v
Definition: glext.h:3678
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
#define ASSERTDEB_(f)
Defines an assertion mechanism - only when compiled in debug.
GLdouble GLdouble GLdouble r
Definition: glext.h:3705
void optimize_graph_spa_levmarq(GRAPH_T &graph, TResultInfoSpaLevMarq &out_info, const std::set< mrpt::utils::TNodeID > *in_nodes_to_optimize=nullptr, const mrpt::utils::TParametersDouble &extra_params=mrpt::utils::TParametersDouble(), typename graphslam_traits< GRAPH_T >::TFunctorFeedback functor_feedback=typename graphslam_traits< GRAPH_T >::TFunctorFeedback())
Optimize a graph of pose constraints using the Sparse Pose Adjustment (SPA) sparse representation and...
Definition: levmarq.h:79
std::function< void(const GRAPH_T &graph, const size_t iter, const size_t max_iter, const double cur_sq_error)> TFunctorFeedback
std::pair< TNodeID, TNodeID > TPairNodeIDs
A pair of node IDs.
Definition: types_simple.h:51
void insert_submatrix(const size_t row, const size_t col, const MATRIX &M)
ONLY for TRIPLET matrices: insert a given matrix (in any of the MRPT formats) at a given location of ...
Output information for mrpt::graphslam::optimize_graph_spa_levmarq()
A versatile "profiler" that logs the time spent within each pair of calls to enter(X)-leave(X), among other stats.
Definition: CTimeLogger.h:45
double leave(const char *func_name)
End of a named section.
Definition: CTimeLogger.h:123
#define __CURRENT_FUNCTION_NAME__
A macro for obtaining the name of the current function:
size_t find_in_vector(const T &value, const CONTAINER &vect)
Returns the index of the value "T" in the container "vect" (std::vector,std::deque,etc), or string::npos if not found.
void enter(const char *func_name)
Start of a named section.
Definition: CTimeLogger.h:117
void keep_max(T &var, const K test_val)
If the second argument is above the first one, set the first argument to this higher value...
double final_total_sq_error
The sum of all the squared errors for every constraint involved in the problem.
std::vector< TYPE1, Eigen::aligned_allocator< TYPE1 > > vector_t
CONTAINER::Scalar norm(const CONTAINER &v)
void memcpy(void *dest, size_t destSize, const void *src, size_t copyCount) noexcept
An OS and compiler independent version of "memcpy".
Definition: os.cpp:355



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: ae4571287 Thu Nov 23 00:06:53 2017 +0100 at dom oct 27 23:51:55 CET 2019