Main MRPT website > C++ reference
MRPT logo
levmarq.h
Go to the documentation of this file.
00001 /* +---------------------------------------------------------------------------+
00002    |          The Mobile Robot Programming Toolkit (MRPT) C++ library          |
00003    |                                                                           |
00004    |                       http://www.mrpt.org/                                |
00005    |                                                                           |
00006    |   Copyright (C) 2005-2011  University of Malaga                           |
00007    |                                                                           |
00008    |    This software was written by the Machine Perception and Intelligent    |
00009    |      Robotics Lab, University of Malaga (Spain).                          |
00010    |    Contact: Jose-Luis Blanco  <jlblanco@ctima.uma.es>                     |
00011    |                                                                           |
00012    |  This file is part of the MRPT project.                                   |
00013    |                                                                           |
00014    |     MRPT is free software: you can redistribute it and/or modify          |
00015    |     it under the terms of the GNU General Public License as published by  |
00016    |     the Free Software Foundation, either version 3 of the License, or     |
00017    |     (at your option) any later version.                                   |
00018    |                                                                           |
00019    |   MRPT is distributed in the hope that it will be useful,                 |
00020    |     but WITHOUT ANY WARRANTY; without even the implied warranty of        |
00021    |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         |
00022    |     GNU General Public License for more details.                          |
00023    |                                                                           |
00024    |     You should have received a copy of the GNU General Public License     |
00025    |     along with MRPT.  If not, see <http://www.gnu.org/licenses/>.         |
00026    |                                                                           |
00027    +---------------------------------------------------------------------------+ */
00028 #ifndef GRAPH_SLAM_LEVMARQ_H
00029 #define GRAPH_SLAM_LEVMARQ_H
00030 
00031 #include <mrpt/graphslam/types.h>
00032 #include <mrpt/utils/TParameters.h>
00033 
00034 #include <mrpt/graphslam/levmarq_impl.h> // Aux classes
00035 
00036 namespace mrpt
00037 {
00038         namespace graphslam
00039         {
00040                 /** Optimize a graph of pose constraints using the Sparse Pose Adjustment (SPA) sparse representation and a Levenberg-Marquartd optimizer.
00041                   *  This method works for all types of graphs derived from \a CNetworkOfPoses (see its reference mrpt::graphs::CNetworkOfPoses for the list).
00042                   *  The input data are all the pose constraints in \a graph (graph.edges), and the gross first estimations of the "global" pose coordinates (in graph.nodes).
00043                   *
00044                   *  Note that these first coordinates can be obtained with mrpt::graphs::CNetworkOfPoses::dijkstra_nodes_estimate().
00045                   *
00046                   * The method implemented in this file is based on this work:
00047                   *  - "Efficient Sparse Pose Adjustment for 2D Mapping", Kurt Konolige et al., 2010.
00048                   * , but generalized for not only 2D but 2D and 3D poses, and using on-manifold optimization.
00049                   *
00050                   * \param[in,out] graph The input edges and output poses.
00051                   * \param[out] out_info Some basic output information on the process.
00052                   * \param[in] nodes_to_optimize The list of nodes whose global poses are to be optimized. If NULL (default), all the node IDs are optimized (but that marked as \a root in the graph).
00053                   * \param[in] extra_params Optional parameters, see below.
00054                   * \param[in] functor_feedback Optional: a pointer to a user function can be set here to be called on each LM loop iteration (eg to refresh the current state and error, refresh a GUI, etc.)
00055                   *
00056                   * List of optional parameters by name in "extra_params":
00057                   *             - "verbose": (default=0) If !=0, produce verbose ouput.
00058                   *             - "max_iterations": (default=100) Maximum number of Lev-Marq. iterations.
00059                   *             - "scale_hessian": (default=0.1) Multiplies the Hessian matrix by this scalar (may improve convergence speed).
00060                   *             - "initial_lambda": (default=0) <=0 means auto guess, otherwise, initial lambda value for the lev-marq algorithm.
00061                   *             - "tau": (default=1e-3) Initial tau value for the lev-marq algorithm.
00062                   *             - "e1": (default=1e-6) Lev-marq algorithm iteration stopping criterion #1: |gradient| < e1
00063                   *             - "e2": (default=1e-6) Lev-marq algorithm iteration stopping criterion #2: |delta_incr| < e2*(x_norm+e2)
00064                   *
00065                   * \note The following graph types are supported: mrpt::graphs::CNetworkOfPoses2D, mrpt::graphs::CNetworkOfPoses3D, mrpt::graphs::CNetworkOfPoses2DInf, mrpt::graphs::CNetworkOfPoses3DInf
00066                   *
00067                   * \tparam GRAPH_T Normally a mrpt::graphs::CNetworkOfPoses<EDGE_TYPE,MAPS_IMPLEMENTATION>. Users won't have to write this template argument by hand, since the compiler will auto-fit it depending on the type of the graph object.
00068                   * \sa The example "graph_slam_demo"
00069                   * \ingroup mrpt_graphslam_grp
00070                   * \note Implementation can be found in file \a levmarq_impl.h
00071                   */
00072                 template <class GRAPH_T>
00073                 void optimize_graph_spa_levmarq(
00074                         GRAPH_T & graph,
00075                         TResultInfoSpaLevMarq                                        & out_info,
00076                         const std::set<mrpt::utils::TNodeID>            * in_nodes_to_optimize = NULL,
00077                         const mrpt::utils::TParametersDouble            & extra_params = mrpt::utils::TParametersDouble(),
00078                         typename graphslam_traits<GRAPH_T>::TFunctorFeedback  functor_feedback = NULL
00079                         )
00080                 {
00081                         using namespace mrpt;
00082                         using namespace mrpt::poses;
00083                         using namespace mrpt::graphslam;
00084                         using namespace mrpt::math;
00085                         using namespace mrpt::utils;
00086                         using namespace std;
00087 
00088                         MRPT_START
00089                 #define VERBOSE_PREFIX "[optimize_graph_spa_levmarq] "
00090 
00091                         // Typedefs to make life easier:
00092                         typedef graphslam_traits<GRAPH_T> gst;
00093 
00094                         typename gst::Array_O array_O_zeros; array_O_zeros.fill(0); // Auxiliary var with all zeros
00095 
00096                         // The size of things here (because size matters...)
00097                         static const unsigned int DIMS_POSE = gst::SE_TYPE::VECTOR_SIZE;
00098 
00099                         // Read extra params:
00100                         const bool verbose            = 0!=extra_params.getWithDefaultVal("verbose",0);
00101                         const size_t max_iters        = extra_params.getWithDefaultVal("max_iterations",100);
00102                         const bool   enable_profiler  = 0!=extra_params.getWithDefaultVal("profiler",0);
00103                         // LM params:
00104                         const double initial_lambda = extra_params.getWithDefaultVal("initial_lambda", 0);  // <=0: means auto guess
00105                         const double tau = extra_params.getWithDefaultVal("tau", 1e-3);
00106                         const double e1 = extra_params.getWithDefaultVal("e1",1e-6);
00107                         const double e2 = extra_params.getWithDefaultVal("e2",1e-6);
00108 
00109                         const double MIN_LAMBDA = extra_params.getWithDefaultVal("min_lambda",1e-290);
00110                         const double SCALE_HESSIAN = extra_params.getWithDefaultVal("scale_hessian",1);
00111 
00112 
00113                         mrpt::utils::CTimeLogger  profiler(enable_profiler);
00114                         profiler.enter("optimize_graph_spa_levmarq (entire)");
00115 
00116                         // Make list of node IDs to optimize, since the user may want only a subset of them to be optimized:
00117                         profiler.enter("optimize_graph_spa_levmarq.list_IDs"); // ---------------\  .
00118                         const set<TNodeID> * nodes_to_optimize;
00119                         set<TNodeID> nodes_to_optimize_auxlist;  // Used only if in_nodes_to_optimize==NULL
00120                         if (in_nodes_to_optimize)
00121                         {
00122                                 nodes_to_optimize = in_nodes_to_optimize;
00123                         }
00124                         else
00125                         {
00126                                 // We have to make the list of IDs:
00127                                 for (typename gst::graph_t::global_poses_t::const_iterator it=graph.nodes.begin();it!=graph.nodes.end();++it)
00128                                         if (it->first!=graph.root) // Root node is fixed.
00129                                                 nodes_to_optimize_auxlist.insert(nodes_to_optimize_auxlist.end(), it->first ); // Provide the "first guess" insert position for efficiency
00130                                 nodes_to_optimize = &nodes_to_optimize_auxlist;
00131                         }
00132                         profiler.leave("optimize_graph_spa_levmarq.list_IDs"); // ---------------/
00133 
00134                         // Number of nodes to optimize, or free variables:
00135                         const size_t nFreeNodes = nodes_to_optimize->size();
00136                         ASSERT_ABOVE_(nFreeNodes,0)
00137 
00138                         if (verbose)
00139                         {
00140                                 cout << VERBOSE_PREFIX << nFreeNodes << " nodes to optimize: ";
00141                                 if (nFreeNodes<14)
00142                                 {
00143                                         ostream_iterator<TNodeID> out_it (cout,", ");
00144                                         std::copy(nodes_to_optimize->begin(), nodes_to_optimize->end(), out_it );
00145                                 }
00146                                 cout << endl;
00147                         }
00148 
00149                         // The list of those edges that will be considered in this optimization (many may be discarded
00150                         //  if we are optimizing just a subset of all the nodes):
00151                         typedef typename gst::observation_info_t observation_info_t;
00152                         vector<observation_info_t>  lstObservationData;
00153 
00154                         // Note: We'll need those Jacobians{i->j} where at least one "i" or "j"
00155                         //        is a free variable (i.e. it's in nodes_to_optimize)
00156                         // Now, build the list of all relevent "observations":
00157                         for (typename gst::edge_const_iterator it=graph.edges.begin();it!=graph.edges.end();++it)
00158                         {
00159                                 const TPairNodeIDs                   &ids  = it->first;
00160                                 const typename gst::graph_t::edge_t  &edge = it->second;
00161 
00162                                 if (nodes_to_optimize->find(ids.first)==nodes_to_optimize->end() &&
00163                                         nodes_to_optimize->find(ids.second)==nodes_to_optimize->end())
00164                                                 continue; // Skip this edge, none of the IDs are free variables.
00165 
00166                                 // get the current global poses of both nodes in this constraint:
00167                                 typename gst::graph_t::global_poses_t::iterator itP1 = graph.nodes.find(ids.first);
00168                                 typename gst::graph_t::global_poses_t::iterator itP2 = graph.nodes.find(ids.second);
00169                                 ASSERTDEBMSG_(itP1!=graph.nodes.end(),"Node1 in an edge does not have a global pose in 'graph.nodes'.")
00170                                 ASSERTDEBMSG_(itP2!=graph.nodes.end(),"Node2 in an edge does not have a global pose in 'graph.nodes'.")
00171 
00172                                 const typename gst::graph_t::constraint_t::type_value &EDGE_POSE  = edge.getPoseMean();
00173 
00174                                 // Add all the data to the list of relevant observations:
00175                                 typename gst::observation_info_t new_entry;
00176                                 new_entry.edge = it;
00177                                 new_entry.edge_mean = &EDGE_POSE;
00178                                 new_entry.P1 = &itP1->second;
00179                                 new_entry.P2 = &itP2->second;
00180 
00181                                 lstObservationData.push_back(new_entry);
00182                         }
00183 
00184                         // The number of constraints, or observations actually implied in this problem:
00185                         const size_t nObservations = lstObservationData.size();
00186                         ASSERT_ABOVE_(nObservations,0)
00187 
00188                         // Cholesky object, as a pointer to reuse it between iterations:
00189                         std::auto_ptr<CSparseMatrix::CholeskyDecomp>  ptrCh;
00190 
00191                         // The list of Jacobians: for each constraint i->j,
00192                         //  we need the pair of Jacobians: { dh(xi,xj)_dxi, dh(xi,xj)_dxj },
00193                         //  which are "first" and "second" in each pair.
00194                         // Index of the map are the node IDs {i,j} for each contraint.
00195                         typename gst::map_pairIDs_pairJacobs_t   lstJacobians;
00196                         // The vector of errors: err_k = SE(2/3)::pseudo_Ln( P_i * EDGE_ij * inv(P_j) )
00197                         typename mrpt::aligned_containers<typename gst::Array_O>::vector_t  errs; // Separated vectors for each edge. i \in [0,nObservations-1], in same order than lstObservationData
00198 
00199                         // ===================================
00200                         // Compute Jacobians & errors
00201                         // ===================================
00202                         profiler.enter("optimize_graph_spa_levmarq.Jacobians&err");// ------------------------------\  .
00203                         double total_sqr_err = computeJacobiansAndErrors<GRAPH_T>(
00204                                 graph, lstObservationData,
00205                                 lstJacobians, errs);
00206                         profiler.leave("optimize_graph_spa_levmarq.Jacobians&err");  // ------------------------------/
00207 
00208 
00209                         // Only once (since this will be static along iterations), build a quick look-up table with the
00210                         //  indices of the free nodes associated to the (first_id,second_id) of each Jacobian pair:
00211                         // -----------------------------------------------------------------------------------------------
00212                         vector<pair<size_t,size_t> >  observationIndex_to_relatedFreeNodeIndex; // "relatedFreeNodeIndex" means into [0,nFreeNodes-1], or "-1" if that node is fixed, as ordered in "nodes_to_optimize"
00213                         observationIndex_to_relatedFreeNodeIndex.reserve(nObservations);
00214                         ASSERTDEB_(lstJacobians.size()==nObservations)
00215                         for (typename gst::map_pairIDs_pairJacobs_t::const_iterator itJ=lstJacobians.begin();itJ!=lstJacobians.end();++itJ)
00216                         {
00217                                 const TNodeID id1 = itJ->first.first;
00218                                 const TNodeID id2 = itJ->first.second;
00219                                 observationIndex_to_relatedFreeNodeIndex.push_back(
00220                                         std::make_pair(
00221                                                 mrpt::utils::find_in_vector(id1,*nodes_to_optimize),
00222                                                 mrpt::utils::find_in_vector(id2,*nodes_to_optimize) ));
00223                         }
00224 
00225                         // other important vars for the main loop:
00226                         vector_double grad(nFreeNodes*DIMS_POSE);
00227                         typedef typename mrpt::aligned_containers<TNodeID,typename gst::matrix_VxV_t>::map_t  map_ID2matrix_VxV_t;
00228                         vector<map_ID2matrix_VxV_t>  H_map(nFreeNodes);
00229 
00230                         double  lambda = initial_lambda; // Will be actually set on first iteration.
00231                         double  rho = 0.5; // value such as lambda is not modified in the first pass
00232                         double  v = 1; // was 2, changed since it's modified in the first pass.
00233                         bool    have_to_recompute_H_and_grad = true;
00234 
00235                         // -----------------------------------------------------------
00236                         // Main iterative loop of Levenberg-Marquardt algorithm
00237                         //  For notation and overall algorithm overview, see:
00238                         //  http://www.mrpt.org/Levenberg%E2%80%93Marquardt_algorithm
00239                         // -----------------------------------------------------------
00240                         size_t last_iter = 0;
00241 
00242                         for (size_t iter=0;iter<max_iters;++iter)
00243                         {
00244                                 last_iter = iter;
00245 
00246                                 if (have_to_recompute_H_and_grad)  // This will be false only when the delta leads to a worst solution and only a change in lambda is needed.
00247                                 {
00248                                         have_to_recompute_H_and_grad = false;
00249 
00250                                         // ========================================================================
00251                                         // Compute the gradient: grad = J^t * errs
00252                                         // ========================================================================
00253                                         //  "grad" can be seen as composed of N independent arrays, each one being:
00254                                         //   grad_i = \sum_k J^t_{k->i} errs_k
00255                                         // that is: g_i is the "dot-product" of the i'th (transposed) block-column of J and the vector of errors "errs"
00256                                         profiler.enter("optimize_graph_spa_levmarq.grad"); // ------------------------------\  .
00257                                         typename mrpt::aligned_containers<typename gst::Array_O>::vector_t  grad_parts(nFreeNodes, array_O_zeros);
00258 
00259                                         // "lstJacobians" is sorted in the same order than "lstObservationData":
00260                                         ASSERT_EQUAL_(lstJacobians.size(),lstObservationData.size())
00261 
00262                                         {
00263                                                 size_t idx_obs;
00264                                                 typename gst::map_pairIDs_pairJacobs_t::const_iterator itJ;
00265 
00266                                                 for (idx_obs=0, itJ=lstJacobians.begin();
00267                                                         itJ!=lstJacobians.end();
00268                                                         ++itJ,++idx_obs)
00269                                                 {
00270                                                         ASSERTDEB_(itJ->first==lstObservationData[idx_obs].edge->first) // make sure they're in the expected order!
00271 
00272                                                         //  grad[k] += J^t_{i->k} * Inf.Matrix * errs_i
00273                                                         //    k: [0,nFreeNodes-1]     <-- IDs.first & IDs.second
00274                                                         //    i: [0,nObservations-1]  <--- idx_obs
00275 
00276                                                         // Get the corresponding indices in the vector of "free variables" being optimized:
00277                                                         const size_t idx1 = observationIndex_to_relatedFreeNodeIndex[idx_obs].first;
00278                                                         const size_t idx2 = observationIndex_to_relatedFreeNodeIndex[idx_obs].second;
00279 
00280                                                         if (idx1!=string::npos)
00281                                                                 detail::AuxErrorEval<typename gst::edge_t,gst>::multiply_Jt_W_err(
00282                                                                         itJ->second.first /* J */,
00283                                                                         lstObservationData[idx_obs].edge /* W */,
00284                                                                         errs[idx_obs] /* err */,
00285                                                                         grad_parts[idx1] /* out */
00286                                                                         );
00287 
00288                                                         if (idx2!=string::npos)
00289                                                                 detail::AuxErrorEval<typename gst::edge_t,gst>::multiply_Jt_W_err(
00290                                                                         itJ->second.second /* J */,
00291                                                                         lstObservationData[idx_obs].edge /* W */,
00292                                                                         errs[idx_obs] /* err */,
00293                                                                         grad_parts[idx2] /* out */
00294                                                                         );
00295                                                 }
00296                                         }
00297 
00298                                         // build the gradient as a single vector:
00299                                         ::memcpy(&grad[0],&grad_parts[0], nFreeNodes*DIMS_POSE*sizeof(grad[0]));  // Ohh yeahh!
00300                                         profiler.leave("optimize_graph_spa_levmarq.grad"); // ------------------------------/
00301 
00302                                         // End condition #1
00303                                         const double grad_norm_inf = math::norm_inf(grad); // inf-norm (abs. maximum value) of the gradient
00304                                         if (grad_norm_inf<=e1)
00305                                         {
00306                                                 // Change is too small
00307                                                 if (verbose) printf(VERBOSE_PREFIX "End condition #1: math::norm_inf(g)<=e1 :%f<=%f\n",grad_norm_inf,e1);
00308                                                 break;
00309                                         }
00310 
00311 
00312                                         profiler.enter("optimize_graph_spa_levmarq.sp_H:build map"); // ------------------------------\  .
00313                                         // ======================================================================
00314                                         // Build sparse representation of the upper triangular part of
00315                                         //  the Hessian matrix H = J^t * J
00316                                         //
00317                                         // Sparse memory structure is a vector of maps, such as:
00318                                         //  - H_map[i]: corresponds to the i'th column of H.
00319                                         //              Here "i" corresponds to [0,N-1] indices of appearance in the map "*nodes_to_optimize".
00320                                         //  - H_map[i][j] is the entry for the j'th row, with "j" also in the range [0,N-1] as ordered in "*nodes_to_optimize".
00321                                         // ======================================================================
00322                                         {
00323                                                 size_t idxObs;
00324                                                 typename gst::map_pairIDs_pairJacobs_t::const_iterator itJacobPair;
00325 
00326                                                 for (idxObs=0, itJacobPair=lstJacobians.begin();
00327                                                          idxObs<nObservations;
00328                                                          ++itJacobPair,++idxObs)
00329                                                 {
00330                                                         // We sort IDs such as "i" < "j" and we can build just the upper triangular part of the Hessian.
00331                                                         const bool edge_straight = itJacobPair->first.first < itJacobPair->first.second;
00332 
00333                                                         // Indices in the "H_map" vector:
00334                                                         const size_t idx_i = edge_straight ? observationIndex_to_relatedFreeNodeIndex[idxObs].first  : observationIndex_to_relatedFreeNodeIndex[idxObs].second;
00335                                                         const size_t idx_j = edge_straight ? observationIndex_to_relatedFreeNodeIndex[idxObs].second : observationIndex_to_relatedFreeNodeIndex[idxObs].first;
00336 
00337                                                         const bool is_i_free_node = idx_i!=string::npos;
00338                                                         const bool is_j_free_node = idx_j!=string::npos;
00339 
00340                                                         // Take references to both Jacobians (wrt pose "i" and pose "j"), taking into account the possible
00341                                                         // switch in their order:
00342 
00343                                                         const typename gst::matrix_VxV_t &J1 = edge_straight ? itJacobPair->second.first : itJacobPair->second.second;
00344                                                         const typename gst::matrix_VxV_t &J2 = edge_straight ? itJacobPair->second.second : itJacobPair->second.first;
00345 
00346                                                         // Is "i" a free (to be optimized) node? -> Ji^t * Inf *  Ji
00347                                                         if (is_i_free_node)
00348                                                         {
00349                                                                 typename gst::matrix_VxV_t JtJ(UNINITIALIZED_MATRIX);
00350                                                                 detail::AuxErrorEval<typename gst::edge_t,gst>::multiplyJtLambdaJ(J1,JtJ,lstObservationData[idxObs].edge);
00351                                                                 H_map[idx_i][idx_i] += JtJ * SCALE_HESSIAN;
00352                                                         }
00353                                                         // Is "j" a free (to be optimized) node? -> Jj^t * Inf *  Jj
00354                                                         if (is_j_free_node)
00355                                                         {
00356                                                                 typename gst::matrix_VxV_t JtJ(UNINITIALIZED_MATRIX);
00357                                                                 detail::AuxErrorEval<typename gst::edge_t,gst>::multiplyJtLambdaJ(J2,JtJ,lstObservationData[idxObs].edge);
00358                                                                 H_map[idx_j][idx_j] += JtJ * SCALE_HESSIAN;
00359                                                         }
00360                                                         // Are both "i" and "j" free nodes? -> Ji^t * Inf *  Jj
00361                                                         if (is_i_free_node && is_j_free_node)
00362                                                         {
00363                                                                 typename gst::matrix_VxV_t JtJ(UNINITIALIZED_MATRIX);
00364                                                                 detail::AuxErrorEval<typename gst::edge_t,gst>::multiplyJ1tLambdaJ2(J1,J2,JtJ,lstObservationData[idxObs].edge);
00365                                                                 H_map[idx_j][idx_i] += JtJ * SCALE_HESSIAN;
00366                                                         }
00367                                                 }
00368                                         }
00369                                         profiler.leave("optimize_graph_spa_levmarq.sp_H:build map");  // ------------------------------/
00370 
00371                                         // Just in the first iteration, we need to calculate an estimate for the first value of "lamdba":
00372                                         if (lambda<=0 && iter==0)
00373                                         {
00374                                                 profiler.enter("optimize_graph_spa_levmarq.lambda_init");  // ---\  .
00375                                                 double H_diagonal_max = 0;
00376                                                 for (size_t i=0;i<nFreeNodes;i++)
00377                                                         for (typename map_ID2matrix_VxV_t::const_iterator it=H_map[i].begin();it!=H_map[i].end();++it)
00378                                                         {
00379                                                                 const size_t j = it->first; // entry submatrix is for (i,j).
00380                                                                 if (i==j)
00381                                                                 {
00382                                                                         for (size_t k=0;k<DIMS_POSE;k++)
00383                                                                                 mrpt::utils::keep_max(H_diagonal_max, it->second.get_unsafe(k,k) );
00384                                                                 }
00385                                                         }
00386                                                 lambda = tau * H_diagonal_max;
00387 
00388                                                 profiler.leave("optimize_graph_spa_levmarq.lambda_init");  // ---/
00389                                         }
00390                 #if 0
00391                                         { CMatrixDouble H; sp_H.get_dense(H); H.saveToTextFile("d:\\H.txt"); }
00392                 #endif
00393 
00394 
00395                                         // After recomputing H and the grad, we update lambda:
00396                                         lambda *= std::max(0.333, 1-pow(2*rho-1,3.0) );
00397                                         utils::keep_max(lambda, 1e-200);  // JL: Avoids underflow!
00398                                         v = 2;
00399 
00400                                         // JL: Additional ending condition: extremely small lambda:
00401                                         if (lambda<MIN_LAMBDA)
00402                                         {
00403                                                 if (verbose ) cout << VERBOSE_PREFIX "End condition #3: lambda < " << MIN_LAMBDA << "\n";
00404                                                 break;
00405                                         }
00406 
00407                                 } // end "have_to_recompute_H_and_grad"
00408 
00409                                 if (verbose )
00410                                         cout << VERBOSE_PREFIX "Iter: " << iter << " ,total sqr. err: " << total_sqr_err  << ", avrg. err per edge: " << std::sqrt(total_sqr_err/nObservations) << " lambda: " << lambda << endl;
00411 
00412                                 // Feedback to the user:
00413                                 if (functor_feedback)
00414                                 {
00415                                         (*functor_feedback)(graph,iter,max_iters,total_sqr_err);
00416                                 }
00417 
00418 
00419                                 profiler.enter("optimize_graph_spa_levmarq.sp_H:build"); // ------------------------------\  .
00420                                 // Now, build the actual sparse matrix H:
00421                                 // Note: we only need to fill out the upper diagonal part, since Cholesky will later on ignore the other part.
00422                                 CSparseMatrix  sp_H(nFreeNodes*DIMS_POSE,nFreeNodes*DIMS_POSE);
00423                                 for (size_t i=0;i<nFreeNodes;i++)
00424                                 {
00425                                         const size_t i_offset = i*DIMS_POSE;
00426 
00427                                         for (typename map_ID2matrix_VxV_t::const_iterator it=H_map[i].begin();it!=H_map[i].end();++it)
00428                                         {
00429                                                 const size_t j = it->first; // entry submatrix is for (i,j).
00430                                                 const size_t j_offset = j*DIMS_POSE;
00431 
00432                                                 // For i==j (diagonal blocks), it's different, since we only need to insert their
00433                                                 // upper-diagonal half and also we have to add the lambda*I to the diagonal from the Lev-Marq. algorithm:
00434                                                 if (i==j)
00435                                                 {
00436                                                         for (size_t r=0;r<DIMS_POSE;r++)
00437                                                         {
00438                                                                 // c=r: add lambda from LM
00439                                                                 sp_H.insert_entry_fast(j_offset+r,i_offset+r, it->second.get_unsafe(r,r) + lambda );
00440                                                                 // c>r:
00441                                                                 for (size_t c=r+1;c<DIMS_POSE;c++)
00442                                                                         sp_H.insert_entry_fast(j_offset+r,i_offset+c, it->second.get_unsafe(r,c));
00443                                                         }
00444                                                 }
00445                                                 else
00446                                                 {
00447                                                         sp_H.insert_submatrix(j_offset,i_offset, it->second);
00448                                                 }
00449                                         }
00450                                 } // end for each free node, build sp_H
00451 
00452                                 sp_H.compressFromTriplet();
00453                                 profiler.leave("optimize_graph_spa_levmarq.sp_H:build"); // ------------------------------/
00454 
00455                                 // Use the cparse Cholesky decomposition to efficiently solve:
00456                                 //   (H+\lambda*I) \delta = -J^t * (f(x)-z)
00457                                 //          A         x   =  b         -->       x = A^{-1} * b
00458                                 //
00459                                 vector_double  delta; // The (minus) increment to be added to the current solution in this step
00460                                 try
00461                                 {
00462                                         profiler.enter("optimize_graph_spa_levmarq.sp_H:chol");
00463                                         if (!ptrCh.get())
00464                                                         ptrCh = std::auto_ptr<CSparseMatrix::CholeskyDecomp>(new CSparseMatrix::CholeskyDecomp(sp_H) );
00465                                         else ptrCh.get()->update(sp_H);
00466                                         profiler.leave("optimize_graph_spa_levmarq.sp_H:chol");
00467 
00468                                         profiler.enter("optimize_graph_spa_levmarq.sp_H:backsub");
00469                                         ptrCh->backsub(grad,delta);
00470                                         profiler.leave("optimize_graph_spa_levmarq.sp_H:backsub");
00471                                 }
00472                                 catch (CExceptionNotDefPos &)
00473                                 {
00474                                         // not positive definite so increase mu and try again
00475                                         if (verbose ) cout << VERBOSE_PREFIX "Got non-definite positive matrix, retrying with a larger lambda...\n";
00476                                         lambda *= v;
00477                                         v*= 2;
00478                                         if (lambda>1e9)
00479                                         {       // enough!
00480                                                 break;
00481                                         }
00482                                         continue; // try again with this params
00483                                 }
00484 
00485                                 // Compute norm of the increment vector:
00486                                 profiler.enter("optimize_graph_spa_levmarq.delta_norm");
00487                                 const double delta_norm = math::norm(delta);
00488                                 profiler.leave("optimize_graph_spa_levmarq.delta_norm");
00489 
00490                                 // Compute norm of the current solution vector:
00491                                 profiler.enter("optimize_graph_spa_levmarq.x_norm" );
00492                                 double x_norm = 0;
00493                                 {
00494                                         for (set<TNodeID>::const_iterator it=nodes_to_optimize->begin();it!=nodes_to_optimize->end();++it)
00495                                         {
00496                                                 typename gst::graph_t::global_poses_t::const_iterator itP = graph.nodes.find(*it);
00497                                                 const typename gst::graph_t::constraint_t::type_value &P = itP->second;
00498                                                 for (size_t i=0;i<DIMS_POSE;i++)
00499                                                         x_norm+=square(P[i]);
00500                                         }
00501                                         x_norm=std::sqrt(x_norm);
00502                                 }
00503                                 profiler.leave("optimize_graph_spa_levmarq.x_norm" );
00504 
00505                                 // Test end condition #2:
00506                                 const double thres_norm = e2*(x_norm+e2);
00507                                 if (delta_norm<thres_norm)
00508                                 {
00509                                         // The change is too small: we're done here...
00510                                         if (verbose ) cout << VERBOSE_PREFIX << format("End condition #2: %e < %e\n", delta_norm, thres_norm );
00511                                         break;
00512                                 }
00513                                 else
00514                                 {
00515                                         // =====================================================================================
00516                                         // Accept this delta? Try it and look at the increase/decrease of the error:
00517                                         //  new_x = old_x [+] (-delta)    , with [+] being the "manifold exp()+add" operation.
00518                                         // =====================================================================================
00519                                         typename gst::graph_t::global_poses_t  old_poses_backup;
00520 
00521                                         {
00522                                                 ASSERTDEB_(delta.size()==nodes_to_optimize->size()*DIMS_POSE)
00523                                                 const double *delta_ptr = &delta[0];
00524                                                 for (set<TNodeID>::const_iterator it=nodes_to_optimize->begin();it!=nodes_to_optimize->end();++it)
00525                                                 {
00526                                                         // exp_delta_i = Exp_SE( delta_i )
00527                                                         typename gst::graph_t::constraint_t::type_value exp_delta_pose(UNINITIALIZED_POSE);
00528                                                         typename gst::Array_O exp_delta;
00529                                                         for (size_t i=0;i<DIMS_POSE;i++)
00530                                                                 exp_delta[i]= - *delta_ptr++;  // The "-" sign is for the missing "-" carried all this time from above
00531                                                         gst::SE_TYPE::exp(exp_delta,exp_delta_pose);
00532 
00533                                                         // new_x_i =  exp_delta_i (+) old_x_i
00534                                                         typename gst::graph_t::global_poses_t::iterator it_old_value = graph.nodes.find(*it);
00535                                                         old_poses_backup[*it] = it_old_value->second; // back up the old pose as a copy
00536                                                         it_old_value->second.composeFrom(exp_delta_pose, it_old_value->second);
00537                                                 }
00538                                         }
00539 
00540                                         // =============================================================
00541                                         // Compute Jacobians & errors with the new "graph.nodes" info:
00542                                         // =============================================================
00543                                         typename gst::map_pairIDs_pairJacobs_t  new_lstJacobians;
00544                                         typename mrpt::aligned_containers<typename gst::Array_O>::vector_t   new_errs;
00545 
00546                                         profiler.enter("optimize_graph_spa_levmarq.Jacobians&err");// ------------------------------\  .
00547                                         double new_total_sqr_err = computeJacobiansAndErrors<GRAPH_T>(
00548                                                 graph, lstObservationData,
00549                                                 new_lstJacobians, new_errs);
00550                                         profiler.leave("optimize_graph_spa_levmarq.Jacobians&err");// ------------------------------/
00551 
00552                                         // Now, to decide whether to accept the change, compute the quantity:
00553                                         //  l =  (old_error - new_error) / denom;
00554                                         //  denom = delta^t * ( \lambda * delta - grad )
00555                                         double denom;
00556                                         {
00557                                                 vector_double aux = delta;
00558                                                 aux*=lambda;
00559                                                 aux+=grad;  // -= (-grad), read the why of the extra "-" sign above.
00560                                                 denom = mrpt::math::dotProduct<vector_double,vector_double>(delta,aux);
00561                                         }
00562                                         rho = (total_sqr_err - new_total_sqr_err) / denom;
00563 
00564                                         if (rho>0)
00565                                         {
00566                                                 // Accept the new point:
00567                                                 new_lstJacobians.swap(lstJacobians);
00568                                                 new_errs.swap(errs);
00569                                                 std::swap( new_total_sqr_err, total_sqr_err);
00570 
00571                                                 // Instruct to recompute H and grad from the new Jacobians.
00572                                                 have_to_recompute_H_and_grad = true;
00573                                         }
00574                                         else
00575                                         {
00576                                                 // Nope...
00577                                                 // We have to revert the "graph.nodes" to "old_poses_backup"
00578                                                 for (typename gst::graph_t::global_poses_t::const_iterator it=old_poses_backup.begin();it!=old_poses_backup.end();++it)
00579                                                         graph.nodes[it->first] = it->second;
00580 
00581                                                 if (verbose ) cout << VERBOSE_PREFIX "Got larger error=" << new_total_sqr_err << ", retrying with a larger lambda...\n";
00582                                                 // Change params and try again:
00583                                                 lambda *= v;
00584                                                 v*= 2;
00585                                         }
00586 
00587                                 } // end else end condition #2
00588 
00589                         } // end for each iter
00590 
00591                         profiler.leave("optimize_graph_spa_levmarq (entire)");
00592 
00593 
00594                         // Fill out basic output data:
00595                         // ------------------------------
00596                         out_info.num_iters = last_iter;
00597                         out_info.final_total_sq_error = total_sqr_err;
00598 
00599                         MRPT_END
00600                 } // end of optimize_graph_spa_levmarq()
00601 
00602 
00603         /**  @} */  // end of grouping
00604 
00605         } // End of namespace
00606 } // End of namespace
00607 
00608 #endif



Page generated by Doxygen 1.7.5 for MRPT 0.9.5 SVN: at Thu Oct 13 21:25:36 UTC 2011