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



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: 7d5e6d718 Fri Aug 24 01:51:28 2018 +0200 at lun nov 2 08:35:50 CET 2020