9 #ifndef GRAPH_SLAM_LEVMARQ_H 10 #define GRAPH_SLAM_LEVMARQ_H 78 template <
class GRAPH_T>
81 const std::set<mrpt::utils::TNodeID>* in_nodes_to_optimize =
nullptr,
99 typename gst::Array_O array_O_zeros;
100 array_O_zeros.fill(0);
103 static const unsigned int DIMS_POSE = gst::SE_TYPE::VECTOR_SIZE;
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);
112 const double initial_lambda = extra_params.getWithDefaultVal(
113 "initial_lambda", 0);
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);
118 const double SCALE_HESSIAN =
119 extra_params.getWithDefaultVal(
"scale_hessian", 1);
122 profiler.
enter(
"optimize_graph_spa_levmarq (entire)");
126 profiler.
enter(
"optimize_graph_spa_levmarq.list_IDs");
127 const set<TNodeID>* nodes_to_optimize;
128 set<TNodeID> nodes_to_optimize_auxlist;
130 if (in_nodes_to_optimize)
132 nodes_to_optimize = in_nodes_to_optimize;
139 it != graph.nodes.end(); ++it)
140 if (it->first != graph.root)
141 nodes_to_optimize_auxlist.insert(
142 nodes_to_optimize_auxlist.end(),
145 nodes_to_optimize = &nodes_to_optimize_auxlist;
147 profiler.
leave(
"optimize_graph_spa_levmarq.list_IDs");
150 const size_t nFreeNodes = nodes_to_optimize->size();
156 <<
" nodes to optimize: ";
159 ostream_iterator<TNodeID> out_it(cout,
", ");
161 nodes_to_optimize->begin(), nodes_to_optimize->end(), out_it);
169 typedef typename gst::observation_info_t observation_info_t;
170 vector<observation_info_t> lstObservationData;
175 for (
typename gst::edge_const_iterator it = graph.edges.begin();
176 it != graph.edges.end(); ++it)
179 const typename gst::graph_t::edge_t& edge = it->second;
181 if (nodes_to_optimize->find(
ids.first) == nodes_to_optimize->end() &&
182 nodes_to_optimize->find(
ids.second) == nodes_to_optimize->end())
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'.")
197 const typename gst::graph_t::constraint_t::type_value& EDGE_POSE =
201 typename gst::observation_info_t new_entry;
203 new_entry.edge_mean = &EDGE_POSE;
204 new_entry.P1 = &itP1->second;
205 new_entry.P2 = &itP2->second;
207 lstObservationData.push_back(new_entry);
212 const size_t nObservations = lstObservationData.size();
217 typedef std::unique_ptr<CSparseMatrix::CholeskyDecomp>
218 SparseCholeskyDecompPtr;
219 SparseCholeskyDecompPtr ptrCh;
225 typename gst::map_pairIDs_pairJacobs_t lstJacobians;
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");
245 vector<pair<size_t, size_t>>
246 observationIndex_to_relatedFreeNodeIndex;
252 observationIndex_to_relatedFreeNodeIndex.reserve(nObservations);
253 ASSERTDEB_(lstJacobians.size() == nObservations)
255 lstJacobians.begin();
256 itJ != lstJacobians.end(); ++itJ)
258 const TNodeID id1 = itJ->first.first;
259 const TNodeID id2 = itJ->first.second;
260 observationIndex_to_relatedFreeNodeIndex.push_back(
270 TNodeID,
typename gst::matrix_VxV_t>::map_t map_ID2matrix_VxV_t;
271 vector<map_ID2matrix_VxV_t> H_map(nFreeNodes);
273 double lambda = initial_lambda;
275 bool have_to_recompute_H_and_grad =
true;
282 size_t last_iter = 0;
284 for (
size_t iter = 0; iter < max_iters; ++iter)
288 if (have_to_recompute_H_and_grad)
293 have_to_recompute_H_and_grad =
false;
303 profiler.
enter(
"optimize_graph_spa_levmarq.grad");
305 grad_parts(nFreeNodes, array_O_zeros);
309 ASSERT_EQUAL_(lstJacobians.size(), lstObservationData.size())
315 for (idx_obs = 0, itJ = lstJacobians.begin();
316 itJ != lstJacobians.end(); ++itJ, ++idx_obs)
320 lstObservationData[idx_obs].edge->first)
333 observationIndex_to_relatedFreeNodeIndex[idx_obs].first;
335 observationIndex_to_relatedFreeNodeIndex[idx_obs]
338 if (idx1 != string::npos)
342 lstObservationData[idx_obs].edge ,
347 if (idx2 != string::npos)
351 lstObservationData[idx_obs].edge ,
360 &grad[0], &grad_parts[0],
361 nFreeNodes * DIMS_POSE *
sizeof(grad[0]));
362 grad /= SCALE_HESSIAN;
363 profiler.
leave(
"optimize_graph_spa_levmarq.grad");
368 if (grad_norm_inf <= e1)
374 "End condition #1: math::norm_inf(g)<=e1 " 380 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:build map");
397 for (idxObs = 0, itJacobPair = lstJacobians.begin();
398 idxObs < nObservations; ++itJacobPair, ++idxObs)
402 const bool edge_straight =
403 itJacobPair->first.first < itJacobPair->first.second;
408 ? observationIndex_to_relatedFreeNodeIndex[idxObs]
410 : observationIndex_to_relatedFreeNodeIndex[idxObs]
414 ? observationIndex_to_relatedFreeNodeIndex[idxObs]
416 : observationIndex_to_relatedFreeNodeIndex[idxObs]
419 const bool is_i_free_node = idx_i != string::npos;
420 const bool is_j_free_node = idx_j != string::npos;
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;
436 typename gst::matrix_VxV_t JtJ(
440 J1, JtJ, lstObservationData[idxObs].edge);
441 H_map[idx_i][idx_i] += JtJ;
446 typename gst::matrix_VxV_t JtJ(
450 J2, JtJ, lstObservationData[idxObs].edge);
451 H_map[idx_j][idx_j] += JtJ;
454 if (is_i_free_node && is_j_free_node)
456 typename gst::matrix_VxV_t JtJ(
460 J1, J2, JtJ, lstObservationData[idxObs].edge);
461 H_map[idx_j][idx_i] += JtJ;
465 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:build map");
469 if (lambda <= 0 && iter == 0)
472 "optimize_graph_spa_levmarq.lambda_init");
473 double H_diagonal_max = 0;
474 for (
size_t i = 0; i < nFreeNodes; i++)
477 it != H_map[i].end(); ++it)
483 for (
size_t k = 0; k < DIMS_POSE; k++)
486 it->second.get_unsafe(k, k));
489 lambda = tau * H_diagonal_max;
492 "optimize_graph_spa_levmarq.lambda_init");
508 <<
" ,total sqr. err: " << total_sqr_err
509 <<
", avrg. err per edge: " 510 << std::sqrt(total_sqr_err / nObservations)
511 <<
" lambda: " << lambda << endl;
514 if (functor_feedback)
516 functor_feedback(graph, iter, max_iters, total_sqr_err);
519 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:build");
523 CSparseMatrix sp_H(nFreeNodes * DIMS_POSE, nFreeNodes * DIMS_POSE);
524 for (
size_t i = 0; i < nFreeNodes; i++)
526 const size_t i_offset = i * DIMS_POSE;
530 it != H_map[i].end(); ++it)
532 const size_t j = it->first;
533 const size_t j_offset = j * DIMS_POSE;
541 for (
size_t r = 0;
r < DIMS_POSE;
r++)
545 j_offset +
r, i_offset +
r,
546 it->second.get_unsafe(
r,
r) + lambda);
548 for (
size_t c =
r + 1;
c < DIMS_POSE;
c++)
550 j_offset +
r, i_offset +
c,
551 it->second.get_unsafe(
r,
c));
562 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:build");
572 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:chol");
574 ptrCh = SparseCholeskyDecompPtr(
577 ptrCh.get()->update(sp_H);
578 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:chol");
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");
589 <<
"] Got non-definite positive matrix, retrying with a " 590 "larger lambda...\n";
601 profiler.
enter(
"optimize_graph_spa_levmarq.delta_norm");
603 profiler.
leave(
"optimize_graph_spa_levmarq.delta_norm");
606 profiler.
enter(
"optimize_graph_spa_levmarq.x_norm");
610 it != nodes_to_optimize->end(); ++it)
613 graph.nodes.find(*it);
614 const typename gst::graph_t::constraint_t::type_value& P =
616 for (
size_t i = 0; i < DIMS_POSE; i++) x_norm +=
square(P[i]);
618 x_norm = std::sqrt(x_norm);
620 profiler.
leave(
"optimize_graph_spa_levmarq.x_norm");
623 const double thres_norm = e2 * (x_norm + e2);
624 if (delta_norm < thres_norm)
630 "End condition #2: %e < %e\n", delta_norm,
642 typename gst::graph_t::global_poses_t old_poses_backup;
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)
653 typename gst::graph_t::constraint_t::type_value
655 typename gst::Array_O exp_delta;
656 for (
size_t i = 0; i < DIMS_POSE; i++)
657 exp_delta[i] = -*delta_ptr++;
661 gst::SE_TYPE::exp(exp_delta, exp_delta_pose);
665 it_old_value = graph.nodes.find(*it);
666 old_poses_backup[*it] =
667 it_old_value->second;
668 it_old_value->second.composeFrom(
669 exp_delta_pose, it_old_value->second);
676 typename gst::map_pairIDs_pairJacobs_t new_lstJacobians;
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");
686 if (new_total_sqr_err < total_sqr_err)
689 new_lstJacobians.swap(lstJacobians);
691 std::swap(new_total_sqr_err, total_sqr_err);
694 have_to_recompute_H_and_grad =
true;
701 old_poses_backup.begin();
702 it != old_poses_backup.end(); ++it)
703 graph.nodes[it->first] = it->second;
707 <<
"] Got larger error=" << new_total_sqr_err
708 <<
", retrying with a larger lambda...\n";
718 profiler.
leave(
"optimize_graph_spa_levmarq (entire)");
#define ASSERT_EQUAL_(__A, __B)
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...
EIGEN_STRONG_INLINE iterator begin()
Column vector, like Eigen::MatrixX*, but automatically initialized to zeros since construction...
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.
void compressFromTriplet()
ONLY for TRIPLET matrices: convert the matrix in a column-compressed form.
const Scalar * const_iterator
Helper types for STL containers with Eigen memory allocators.
T square(const T x)
Inline function for the square of a number.
Used in mrpt::math::CSparseMatrix.
#define ASSERTDEBMSG_(f, __ERROR_MSG)
SLAM methods related to graphs of pose constraints.
This base provides a set of functions for maths stuff.
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
uint64_t TNodeID
The type for node IDs in graphs of different types.
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...
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)
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
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...
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.
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.
double leave(const char *func_name)
End of a named section.
#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.
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".