9 #ifndef GRAPH_SLAM_LEVMARQ_H 10 #define GRAPH_SLAM_LEVMARQ_H 75 template <
class GRAPH_T>
78 const std::set<mrpt::graphs::TNodeID>* in_nodes_to_optimize =
nullptr,
96 typename gst::Array_O array_O_zeros;
97 array_O_zeros.fill(0);
100 constexpr
auto DIMS_POSE = gst::SE_TYPE::VECTOR_SIZE;
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);
109 const double initial_lambda = extra_params.getWithDefaultVal(
110 "initial_lambda", 0);
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);
116 profiler.
enter(
"optimize_graph_spa_levmarq (entire)");
120 profiler.
enter(
"optimize_graph_spa_levmarq.list_IDs");
121 const set<TNodeID>* nodes_to_optimize;
122 set<TNodeID> nodes_to_optimize_auxlist;
124 if (in_nodes_to_optimize)
126 nodes_to_optimize = in_nodes_to_optimize;
131 for (
const auto&
n : graph.nodes)
132 if (
n.first != graph.root)
133 nodes_to_optimize_auxlist.insert(
134 nodes_to_optimize_auxlist.end(),
137 nodes_to_optimize = &nodes_to_optimize_auxlist;
139 profiler.
leave(
"optimize_graph_spa_levmarq.list_IDs");
142 const size_t nFreeNodes = nodes_to_optimize->size();
147 <<
" nodes to optimize: ";
150 ostream_iterator<TNodeID> out_it(cout,
", ");
152 nodes_to_optimize->begin(), nodes_to_optimize->end(), out_it);
160 using observation_info_t =
typename gst::observation_info_t;
161 vector<observation_info_t> lstObservationData;
166 for (
const auto& e : graph.edges)
168 const auto&
ids = e.first;
169 const auto& edge = e.second;
171 if (nodes_to_optimize->find(
ids.first) == nodes_to_optimize->end() &&
172 nodes_to_optimize->find(
ids.second) == nodes_to_optimize->end())
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");
181 const auto& EDGE_POSE = edge.getPoseMean();
184 typename gst::observation_info_t new_entry;
186 new_entry.edge_mean = &EDGE_POSE;
187 new_entry.P1 = &itP1->second;
188 new_entry.P2 = &itP2->second;
190 lstObservationData.push_back(new_entry);
195 const size_t nObservations = lstObservationData.size();
199 using SparseCholeskyDecompPtr =
200 std::unique_ptr<CSparseMatrix::CholeskyDecomp>;
201 SparseCholeskyDecompPtr ptrCh;
207 typename gst::map_pairIDs_pairJacobs_t lstJacobians;
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");
227 vector<pair<size_t, size_t>> obsIdx2fnIdx;
230 obsIdx2fnIdx.reserve(nObservations);
231 ASSERTDEB_(lstJacobians.size() == nObservations);
233 lstJacobians.begin();
234 itJ != lstJacobians.end(); ++itJ)
236 const TNodeID id1 = itJ->first.first;
237 const TNodeID id2 = itJ->first.second;
238 obsIdx2fnIdx.push_back(std::make_pair(
246 using map_ID2matrix_VxV_t =
249 double lambda = initial_lambda;
251 bool have_to_recompute_H_and_grad =
true;
258 size_t last_iter = 0;
260 for (
size_t iter = 0; iter < max_iters; ++iter)
262 vector<map_ID2matrix_VxV_t> H_map(nFreeNodes);
267 if (have_to_recompute_H_and_grad)
269 have_to_recompute_H_and_grad =
false;
278 profiler.
enter(
"optimize_graph_spa_levmarq.grad");
280 nFreeNodes, array_O_zeros);
289 for (idx_obs = 0, itJ = lstJacobians.begin();
290 itJ != lstJacobians.end(); ++itJ, ++idx_obs)
294 itJ->first == lstObservationData[idx_obs].edge->first);
302 const size_t idx1 = obsIdx2fnIdx[idx_obs].first;
303 const size_t idx2 = obsIdx2fnIdx[idx_obs].second;
305 if (idx1 != string::npos)
309 lstObservationData[idx_obs].edge ,
314 if (idx2 != string::npos)
318 lstObservationData[idx_obs].edge ,
327 &grad[0], &grad_parts[0],
328 nFreeNodes * DIMS_POSE *
sizeof(grad[0]));
329 profiler.
leave(
"optimize_graph_spa_levmarq.grad");
334 if (grad_norm_inf <= e1)
340 "End condition #1: math::norm_inf(g)<=e1 " 346 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:build map");
363 for (idxObs = 0, itJacobPair = lstJacobians.begin();
364 idxObs < nObservations; ++itJacobPair, ++idxObs)
366 const bool Hij_upper_triang =
367 itJacobPair->first.first < itJacobPair->first.second;
370 const size_t idx_i = obsIdx2fnIdx[idxObs].first;
371 const size_t idx_j = obsIdx2fnIdx[idxObs].second;
373 const bool is_i_free_node = idx_i != string::npos;
374 const bool is_j_free_node = idx_j != string::npos;
379 const typename gst::matrix_VxV_t& J1 = itJacobPair->second.first;
380 const typename gst::matrix_VxV_t& J2 = itJacobPair->second.second;
385 typename gst::matrix_VxV_t JtJ(
389 J1, JtJ, lstObservationData[idxObs].edge);
390 H_map[idx_i][idx_i] += JtJ;
395 typename gst::matrix_VxV_t JtJ(
399 J2, JtJ, lstObservationData[idxObs].edge);
400 H_map[idx_j][idx_j] += JtJ;
403 if (is_i_free_node && is_j_free_node)
405 typename gst::matrix_VxV_t JtJ(
409 J1, J2, JtJ, lstObservationData[idxObs].edge);
412 if (Hij_upper_triang)
413 H_map[idx_j][idx_i] += JtJ;
415 H_map[idx_i][idx_j] += JtJ.transpose();
419 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:build map");
423 if (lambda <= 0 && iter == 0)
426 "optimize_graph_spa_levmarq.lambda_init");
427 double H_diagonal_max = 0;
428 for (
size_t i = 0; i < nFreeNodes; i++)
431 it != H_map[i].end(); ++it)
437 for (
size_t k = 0; k < DIMS_POSE; k++)
440 it->second.get_unsafe(k, k));
443 lambda = tau * H_diagonal_max;
446 "optimize_graph_spa_levmarq.lambda_init");
459 <<
" ,total sqr. err: " << total_sqr_err
460 <<
", avrg. err per edge: " 461 << std::sqrt(total_sqr_err / nObservations)
462 <<
" lambda: " << lambda << endl;
465 if (functor_feedback)
467 functor_feedback(graph, iter, max_iters, total_sqr_err);
470 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:build");
474 CSparseMatrix sp_H(nFreeNodes * DIMS_POSE, nFreeNodes * DIMS_POSE);
475 for (
size_t i = 0; i < nFreeNodes; i++)
477 const size_t i_offset = i * DIMS_POSE;
481 it != H_map[i].end(); ++it)
483 const size_t j = it->first;
484 const size_t j_offset = j * DIMS_POSE;
492 for (
size_t r = 0;
r < DIMS_POSE;
r++)
496 j_offset +
r, i_offset +
r,
497 it->second.get_unsafe(
r,
r) + lambda);
499 for (
size_t c =
r + 1;
c < DIMS_POSE;
c++)
502 j_offset +
r, i_offset +
c,
503 it->second.get_unsafe(
r,
c));
515 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:build");
525 profiler.
enter(
"optimize_graph_spa_levmarq.sp_H:chol");
527 ptrCh = SparseCholeskyDecompPtr(
530 ptrCh.get()->update(sp_H);
531 profiler.
leave(
"optimize_graph_spa_levmarq.sp_H:chol");
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");
542 <<
"] Got non-definite positive matrix, retrying with a " 543 "larger lambda...\n";
554 profiler.
enter(
"optimize_graph_spa_levmarq.delta_norm");
556 profiler.
leave(
"optimize_graph_spa_levmarq.delta_norm");
559 profiler.
enter(
"optimize_graph_spa_levmarq.x_norm");
563 it != nodes_to_optimize->end(); ++it)
566 graph.nodes.find(*it);
567 const typename gst::graph_t::constraint_t::type_value& P =
569 for (
size_t i = 0; i < DIMS_POSE; i++) x_norm +=
square(P[i]);
571 x_norm = std::sqrt(x_norm);
573 profiler.
leave(
"optimize_graph_spa_levmarq.x_norm");
576 const double thres_norm = e2 * (x_norm + e2);
577 if (delta_norm < thres_norm)
583 "End condition #2: %e < %e\n", delta_norm,
595 typename gst::graph_t::global_poses_t old_poses_backup;
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)
605 typename gst::Array_O exp_delta;
606 for (
size_t i = 0; i < DIMS_POSE; i++)
607 exp_delta[i] = -*delta_ptr++;
613 it_old_value = graph.nodes.find(*it);
614 old_poses_backup[*it] =
615 it_old_value->second;
617 it_old_value->second, exp_delta);
624 typename gst::map_pairIDs_pairJacobs_t new_lstJacobians;
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");
633 if (new_total_sqr_err < total_sqr_err)
636 new_lstJacobians.swap(lstJacobians);
638 std::swap(new_total_sqr_err, total_sqr_err);
641 have_to_recompute_H_and_grad =
true;
648 old_poses_backup.begin();
649 it != old_poses_backup.end(); ++it)
650 graph.nodes[it->first] = it->second;
654 <<
"] Got larger error=" << new_total_sqr_err
655 <<
", retrying with a larger lambda...\n";
665 profiler.
leave(
"optimize_graph_spa_levmarq (entire)");
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...
Used in mrpt::math::CSparseMatrix.
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.
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)
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...
#define ASSERTDEB_EQUAL_(__A, __B)
#define ASSERTMSG_(f, __ERROR_MSG)
Defines an assertion mechanism.
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)
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
GLdouble GLdouble GLdouble r
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.
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
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.
#define __CURRENT_FUNCTION_NAME__
A macro for obtaining the name of the current function:
const Scalar * const_iterator
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".