SRBA: Sparser Relative Bundle Adjustment
srba/impl/optimize_edges.h
00001 /* +---------------------------------------------------------------------------+
00002    |                     Mobile Robot Programming Toolkit (MRPT)               |
00003    |                          http://www.mrpt.org/                             |
00004    |                                                                           |
00005    | Copyright (c) 2005-2015, Individual contributors, see AUTHORS file        |
00006    | See: http://www.mrpt.org/Authors - All rights reserved.                   |
00007    | Released under BSD License. See details in http://www.mrpt.org/License    |
00008    +---------------------------------------------------------------------------+ */
00009 
00010 #pragma once
00011 
00012 #include <mrpt/math/ops_containers.h> // norm_inf()
00013 
00014 namespace srba {
00015 
00016 #ifndef SRBA_DETAILED_TIME_PROFILING
00017 #   define SRBA_DETAILED_TIME_PROFILING       0          //  Enabling this has a measurable impact in performance, so use only for debugging.
00018 #endif
00019 
00020 // Macros:
00021 #if SRBA_DETAILED_TIME_PROFILING
00022 #   define DETAILED_PROFILING_ENTER(_STR) m_profiler.enter(_STR);
00023 #   define DETAILED_PROFILING_LEAVE(_STR) m_profiler.leave(_STR);
00024 #else
00025 #   define DETAILED_PROFILING_ENTER(_STR)
00026 #   define DETAILED_PROFILING_LEAVE(_STR)
00027 #endif
00028 
00029 namespace internal
00030 {
00032     template <bool USE_SCHUR, bool DENSE_CHOL,class RBA_ENGINE>
00033     struct solver_engine;
00034 
00035     // Implemented in lev-marq_solvers.h
00036 }
00037 
00038 
00039 // ------------------------------------------
00040 //         optimize_edges
00041 //          (See header for docs)
00042 // ------------------------------------------
00043 template <class KF2KF_POSE_TYPE,class LM_TYPE,class OBS_TYPE,class RBA_OPTIONS>
00044 void RbaEngine<KF2KF_POSE_TYPE,LM_TYPE,OBS_TYPE,RBA_OPTIONS>::optimize_edges(
00045     const std::vector<size_t> & run_k2k_edges_in,
00046     const std::vector<size_t> & run_feat_ids_in,
00047     TOptimizeExtraOutputInfo & out_info,
00048     const std::vector<size_t> & in_observation_indices_to_optimize
00049     )
00050 {
00051     using namespace std;
00052     // This method deals with many common tasks to any optimizer: update Jacobians, prepare Hessians, etc. 
00053     // The specific solver method details are implemented in "my_solver_t":
00054     typedef internal::solver_engine<RBA_OPTIONS::solver_t::USE_SCHUR,RBA_OPTIONS::solver_t::DENSE_CHOLESKY,rba_engine_t> my_solver_t;
00055     
00056     m_profiler.enter("opt");
00057 
00058     out_info.clear();
00059 
00060     // Problem dimensions:
00061     const size_t POSE_DIMS = kf2kf_pose_t::REL_POSE_DIMS;
00062     const size_t LM_DIMS   = landmark_t::LM_DIMS;
00063     const size_t OBS_DIMS  = obs_t::OBS_DIMS;
00064 
00065 
00066     // Filter out those unknowns which for sure do not have any observation,
00067     //  which can be checked by looking for empty columns in the sparse Jacobian.
00068     // -------------------------------------------------------------------------------
00069     DETAILED_PROFILING_ENTER("opt.filter_unknowns")
00070 
00071     std::vector<size_t> run_k2k_edges; run_k2k_edges.reserve(run_k2k_edges_in.size());
00072     std::vector<size_t> run_feat_ids; run_feat_ids.reserve(run_feat_ids_in.size());
00073 
00074     std::set<TKeyFrameID> touched_KFs; // Only for stats
00075     std::set<TLandmarkID> touched_LMs; // Only for stats
00076 
00077     for (size_t i=0;i<run_k2k_edges_in.size();i++)
00078     {
00079         const typename TSparseBlocksJacobians_dh_dAp::col_t & col_i = rba_state.lin_system.dh_dAp.getCol( run_k2k_edges_in[i] );
00080         if (!col_i.empty()) {
00081             run_k2k_edges.push_back( run_k2k_edges_in[i] );
00082             touched_KFs.insert( rba_state.k2k_edges[run_k2k_edges_in[i]].from );
00083             touched_KFs.insert( rba_state.k2k_edges[run_k2k_edges_in[i]].to );
00084         }
00085         else 
00086         {
00087             const TKeyFrameID from = rba_state.k2k_edges[run_k2k_edges_in[i]].from;
00088             const TKeyFrameID to   = rba_state.k2k_edges[run_k2k_edges_in[i]].to;
00089             mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/);
00090             std::cerr << "[RbaEngine::optimize_edges] *Warning*: Skipping optimization of k2k edge #"<<run_k2k_edges_in[i] << " (" <<from <<"->"<<to <<") since no observation depends on it.\n";
00091             mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/);
00092         }
00093     }
00094 
00095     const mrpt::utils::map_as_vector<size_t,size_t> & dh_df_remap = rba_state.lin_system.dh_df.getColInverseRemappedIndices();
00096     for (size_t i=0;i<run_feat_ids_in.size();i++)
00097     {
00098         const TLandmarkID feat_id = run_feat_ids_in[i];
00099         ASSERT_(feat_id<rba_state.all_lms.size())
00100 
00101         const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id];
00102         ASSERTMSG_(lm_e.rfp!=NULL, "Trying to optimize an unknown feature ID")
00103         ASSERTMSG_(!lm_e.has_known_pos,"Trying to optimize a feature with fixed (known) value")
00104 
00105         mrpt::utils::map_as_vector<size_t,size_t>::const_iterator it_remap = dh_df_remap.find(feat_id);   // O(1) with map_as_vector
00106         ASSERT_(it_remap != dh_df_remap.end())
00107 
00108         const typename TSparseBlocksJacobians_dh_df::col_t & col_i = rba_state.lin_system.dh_df.getCol( it_remap->second );
00109 
00110         if (!col_i.empty()) {
00111             run_feat_ids.push_back( feat_id );
00112             touched_LMs.insert( feat_id );
00113         }
00114         else {
00115             mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/);
00116             std::cerr << "[RbaEngine::optimize_edges] *Warning*: Skipping optimization of k2f edge #"<< feat_id << " since no observation depends on it.\n";
00117             mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/);
00118         }
00119     }
00120 
00121     DETAILED_PROFILING_LEAVE("opt.filter_unknowns")
00122 
00123     // Build list of unknowns, and their corresponding columns in the Sparse Jacobian:
00124     // -------------------------------------------------------------------------------
00125     const size_t nUnknowns_k2k = run_k2k_edges.size();
00126     const size_t nUnknowns_k2f = run_feat_ids.size();
00127     out_info.num_kf_optimized = touched_KFs.size();
00128     out_info.num_lm_optimized = touched_LMs.size();
00129 
00130     const size_t idx_start_f = POSE_DIMS*nUnknowns_k2k; // In the vector of unknowns, the 0-based first index of the first feature variable (before that, all are SE(3) edges)
00131     const size_t nUnknowns_scalars = POSE_DIMS*nUnknowns_k2k + LM_DIMS*nUnknowns_k2f;
00132     if (!nUnknowns_scalars)
00133     {
00134         mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED, true /*cerr*/);
00135         std::cerr << "[RbaEngine::optimize_edges] *Warning*: Skipping optimization since no observation depends on any of the given variables.\n";
00136         mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL, true /*cerr*/);
00137         return;
00138     }
00139 
00140     // k2k edges:
00141     std::vector<typename TSparseBlocksJacobians_dh_dAp::col_t*>  dh_dAp(nUnknowns_k2k);
00142     std::vector<k2k_edge_t *> k2k_edge_unknowns(nUnknowns_k2k);
00143     for (size_t i=0;i<nUnknowns_k2k;i++)
00144     {
00145         dh_dAp[i] = &rba_state.lin_system.dh_dAp.getCol( run_k2k_edges[i] );
00146         k2k_edge_unknowns[i] = 
00147             & rba_state.k2k_edges[run_k2k_edges[i]];
00148     }
00149 
00150     // k2f edges:
00151     std::vector<typename TSparseBlocksJacobians_dh_df::col_t*>  dh_df(nUnknowns_k2f);
00152     std::vector<TRelativeLandmarkPos*> k2f_edge_unknowns(nUnknowns_k2f);
00153     for (size_t i=0;i<nUnknowns_k2f;i++)
00154     {
00155         const TLandmarkID feat_id = run_feat_ids[i];
00156         const typename rba_problem_state_t::TLandmarkEntry &lm_e = rba_state.all_lms[feat_id];
00157 
00158         mrpt::utils::map_as_vector<size_t,size_t>::const_iterator it_remap = dh_df_remap.find(feat_id);  // O(1) with map_as_vector
00159         ASSERT_(it_remap != dh_df_remap.end())
00160 
00161         dh_df[i] = &rba_state.lin_system.dh_df.getCol( it_remap->second );
00162         k2f_edge_unknowns[i] = lm_e.rfp;
00163     }
00164 
00165     // Unless stated otherwise, take into account ALL the observations involved in each
00166     // unknown (i.e. don't discard any information).
00167     // -------------------------------------------------------------------------------
00168     DETAILED_PROFILING_ENTER("opt.build_obs_list")
00169 
00170     // Mapping betwwen obs. indices in the residuals vector & the global list of all the observations:
00171     std::map<size_t,size_t>   obs_global_idx2residual_idx;
00172 
00173     std::vector<TObsUsed>  involved_obs;  //  + obs. data
00174     if (in_observation_indices_to_optimize.empty())
00175     {
00176         // Observations for k2k edges Jacobians
00177         for (size_t i=0;i<nUnknowns_k2k;i++)
00178         {
00179             // For each column, process each nonzero block:
00180             typename TSparseBlocksJacobians_dh_dAp::col_t *col = dh_dAp[i];
00181 
00182             for (typename TSparseBlocksJacobians_dh_dAp::col_t::iterator it=col->begin();it!=col->end();++it)
00183             {
00184                 const size_t global_obs_idx = it->first;
00185                 const size_t obs_idx = involved_obs.size();
00186 
00187                 obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
00188 
00189                 involved_obs.push_back( TObsUsed (global_obs_idx, 
00190                 &rba_state.all_observations[global_obs_idx]
00191                  ) );
00192             }
00193         }
00194         // Observations for k2f edges Jacobians
00195         for (size_t i=0;i<nUnknowns_k2f;i++)
00196         {
00197             // For each column, process each nonzero block:
00198             typename TSparseBlocksJacobians_dh_df::col_t *col = dh_df[i];
00199 
00200             for (typename TSparseBlocksJacobians_dh_df::col_t::iterator it=col->begin();it!=col->end();++it)
00201             {
00202                 const size_t global_obs_idx = it->first;
00203                 // Only add if not already added before:
00204                 std::map<size_t,size_t>::const_iterator it_o = obs_global_idx2residual_idx.find(global_obs_idx);
00205                 /*  TSizeFlag & sf = obs_global_idx2residual_idx[global_obs_idx];*/
00206                 if (it_o == obs_global_idx2residual_idx.end())
00207                 {
00208                     const size_t obs_idx = involved_obs.size();
00209 
00210                     obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
00211 
00212                     involved_obs.push_back( TObsUsed( global_obs_idx,  
00213                         &rba_state.all_observations[global_obs_idx] 
00214                     ) );
00215                 }
00216             }
00217         }
00218     }
00219     else
00220     {
00221         // use only those observations in "in_observation_indices_to_optimize":
00222         const size_t nInObs = in_observation_indices_to_optimize.size();
00223         for (size_t i=0;i<nInObs;i++)
00224         {
00225             const size_t global_obs_idx = in_observation_indices_to_optimize[i];
00226             const size_t obs_idx = involved_obs.size();
00227 
00228             obs_global_idx2residual_idx[global_obs_idx] = obs_idx;
00229 
00230             involved_obs.push_back(TObsUsed(global_obs_idx, 
00231                 &rba_state.all_observations[global_obs_idx] 
00232             ) );
00233         }
00234     }
00235 
00236     DETAILED_PROFILING_LEAVE("opt.build_obs_list")
00237 
00238     const size_t nObs = involved_obs.size();
00239 
00240 
00241     // Make a list of Keyframe IDs whose numeric spanning trees must be updated (so we only update them!)
00242     // -------------------------------------------------------------------------------
00243     DETAILED_PROFILING_ENTER("opt.prep_list_num_tree_roots")
00244 
00245     std::set<TKeyFrameID>  kfs_num_spantrees_to_update;
00246     prepare_Jacobians_required_tree_roots(kfs_num_spantrees_to_update, dh_dAp, dh_df);
00247 
00248     DETAILED_PROFILING_LEAVE("opt.prep_list_num_tree_roots")
00249 
00250     VERBOSE_LEVEL(2) << "[OPT] KF roots whose spantrees need numeric-updates: " << mrpt::system::sprintf_container("%u", kfs_num_spantrees_to_update) <<std::endl;
00251 
00252 
00253     // Spanning tree: Update numerically only those entries which we really need:
00254     // -------------------------------------------------------------------------------
00255     DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num")
00256     const size_t count_span_tree_num_update = rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update, false /* don't skip those marked as updated, so update all */);
00257     DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num")
00258 
00259 
00260     // Re-evaluate all Jacobians numerically:
00261     // -------------------------------------------------------------------------------
00262     std::vector<const pose_flag_t*>    list_of_required_num_poses; // Filled-in by Jacobian evaluation upon first call.
00263 
00264 
00265     DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity")
00266     // Before evaluating Jacobians we must reset as "valid" all the involved observations.
00267     //  If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components
00268     //  for one observation leads to an error.
00269     for (size_t i=0;i<nObs;i++)
00270         rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
00271 
00272     DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity")
00273 
00274 
00275     DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians")
00276     const size_t count_jacobians = recompute_all_Jacobians(dh_dAp, dh_df, &list_of_required_num_poses );
00277     DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians")
00278 
00279     // Mark all required spanning-tree numeric entries as outdated, so an exception will reveal us if
00280     //  next time Jacobians are required they haven't been updated as they should:
00281     // -------------------------------------------------------------------------------
00282     for (size_t i=0;i<list_of_required_num_poses.size();i++)
00283         list_of_required_num_poses[i]->mark_outdated();
00284 
00285 #if 0  // Save a sparse block representation of the Jacobian.
00286     {
00287         static unsigned int dbg_idx = 0;
00288      mrpt::math::CMatrixDouble Jbin;
00289         rba_state.lin_system.dh_dAp.getBinaryBlocksRepresentation(Jbin);
00290         Jbin.saveToTextFile(mrpt::format("sparse_jacobs_blocks_%05u.txt",dbg_idx), mrpt::math::MATRIX_FORMAT_INT );
00291         rba_state.lin_system.dh_dAp.saveToTextFileAsDense(mrpt::format("sparse_jacobs_%05u.txt",dbg_idx));
00292         ++dbg_idx;
00293         mrpt::system::pause();
00294     }
00295 #endif
00296 
00297     // ----------------------------------------------------------------------
00298     //  Compute H = J^t * J , exploting the sparse, block representation of J:
00299     //
00300     //   Don't add the LevMarq's "\lambda*I"  here so "H" needs not to be
00301     //    modified between different LevMarq. trials with different lambda values.
00302     // ----------------------------------------------------------------------
00303     typename hessian_traits_t::TSparseBlocksHessian_Ap  HAp;
00304     typename hessian_traits_t::TSparseBlocksHessian_f   Hf;
00305     typename hessian_traits_t::TSparseBlocksHessian_Apf HApf;
00306 
00307     // This symbolic constructions must be done only ONCE:
00308     DETAILED_PROFILING_ENTER("opt.sparse_hessian_build_symbolic")
00309     sparse_hessian_build_symbolic(
00310         HAp,Hf,HApf,
00311         dh_dAp,dh_df
00312         );
00313     DETAILED_PROFILING_LEAVE("opt.sparse_hessian_build_symbolic")
00314 
00315     if (parameters.srba.compute_sparsity_stats)
00316     {
00317         DETAILED_PROFILING_ENTER("opt.sparsity_stats")
00318         TSparseBlocksJacobians_dh_dAp::getSparsityStats(dh_dAp, out_info.sparsity_dh_dAp_max_size, out_info.sparsity_dh_dAp_nnz );
00319         TSparseBlocksJacobians_dh_df::getSparsityStats (dh_df , out_info.sparsity_dh_df_max_size, out_info.sparsity_dh_df_nnz );
00320         HAp.getSparsityStats( out_info.sparsity_HAp_max_size, out_info.sparsity_HAp_nnz );
00321         Hf.getSparsityStats( out_info.sparsity_Hf_max_size, out_info.sparsity_Hf_nnz );
00322         HApf.getSparsityStats( out_info.sparsity_HApf_max_size, out_info.sparsity_HApf_nnz );
00323         DETAILED_PROFILING_LEAVE("opt.sparsity_stats")
00324     }
00325 
00326     // and then we only have to do a numeric evaluation upon changes:
00327     size_t nInvalidJacobs = 0;
00328     DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric")
00329     nInvalidJacobs += sparse_hessian_update_numeric(HAp);
00330     nInvalidJacobs += sparse_hessian_update_numeric(Hf);
00331     nInvalidJacobs += sparse_hessian_update_numeric(HApf);
00332     DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric")
00333 
00334     if (nInvalidJacobs) {
00335         mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED);
00336         VERBOSE_LEVEL(1) << "[OPT] " << nInvalidJacobs << " Jacobian blocks ignored for 'invalid'.\n";
00337         mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL);
00338     }
00339 
00340     VERBOSE_LEVEL(2) << "[OPT] Individual Jacobs: " << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << std::endl;
00341     VERBOSE_LEVEL(2) << "[OPT] k2k_edges to optimize: " << mrpt::system::sprintf_container("% u",run_k2k_edges) << std::endl;
00342     VERBOSE_LEVEL(2) << "[OPT] k2f_edges to optimize: " << mrpt::system::sprintf_container("% u",run_feat_ids) << std::endl;
00343     // Extra verbose: display initial value of each optimized pose:
00344     if (m_verbose_level>=2 && !run_k2k_edges.empty())
00345     {
00346         std::cout << "[OPT] k2k_edges to optimize, initial value(s):\n";
00347         ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size())
00348         for (size_t i=0;i<run_k2k_edges.size();i++)
00349             std::cout << " k2k_edge: " <<k2k_edge_unknowns[i]->from << "=>" << k2k_edge_unknowns[i]->to << ",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl;
00350     }
00351 
00352     // VERY IMPORTANT: For J^t*J to be invertible, we need a full rank Hessian:
00353     //    nObs*OBS_DIMS >= nUnknowns_k2k*POSE_DIMS+nUnknowns_k2f*LM_DIMS
00354     //
00355     ASSERT_ABOVEEQ_(OBS_DIMS*nObs,POSE_DIMS*nUnknowns_k2k+LM_DIMS*nUnknowns_k2f)
00356 
00357     // ----------------------------------------------------------------
00358     //         Iterative Levenberg Marquardt (LM) algorithm
00359     // ----------------------------------------------------------------
00360     // LevMar parameters:
00361     double nu = 2;
00362     const double max_gradient_to_stop = 1e-15;  // Stop if the infinity norm of the gradient is smaller than this
00363     double lambda = -1;  // initial value. <0 = auto.
00364 
00365     // Automatic guess of "lambda" = tau * max(diag(Hessian))   (Hessian=H here)
00366     if (lambda<0)
00367     {
00368         DETAILED_PROFILING_ENTER("opt.guess lambda")
00369 
00370         double Hess_diag_max = 0;
00371         for (size_t i=0;i<nUnknowns_k2k;i++)
00372         {
00373             ASSERTDEB_(HAp.getCol(i).find(i)!=HAp.getCol(i).end())
00374 
00375             const double Hii_max = HAp.getCol(i)[i].num.diagonal().maxCoeff();
00376             mrpt::utils::keep_max(Hess_diag_max, Hii_max);
00377         }
00378         for (size_t i=0;i<nUnknowns_k2f;i++)
00379         {
00380             ASSERTDEB_(Hf.getCol(i).find(i)!=Hf.getCol(i).end())
00381 
00382             const double Hii_max = Hf.getCol(i)[i].num.diagonal().maxCoeff();
00383             mrpt::utils::keep_max(Hess_diag_max, Hii_max);
00384         }
00385 
00386         const double tau = 1e-3;
00387         lambda = tau * Hess_diag_max;
00388 
00389         DETAILED_PROFILING_LEAVE("opt.guess lambda")
00390     }
00391 
00392     // Compute the reprojection errors:
00393     //  residuals = "h(x)-z" (a vector of 2-vectors).
00394     // ---------------------------------------------------------------------------------
00395     vector_residuals_t  residuals(nObs);
00396 
00397     DETAILED_PROFILING_ENTER("opt.reprojection_residuals")
00398     double total_proj_error = reprojection_residuals(
00399         residuals, // Out
00400         involved_obs // In
00401         );
00402     DETAILED_PROFILING_LEAVE("opt.reprojection_residuals")
00403 
00404     double RMSE = std::sqrt(total_proj_error/nObs);
00405 
00406     out_info.num_observations     = nObs;
00407     out_info.num_jacobians        = count_jacobians;
00408     out_info.num_kf2kf_edges_optimized = run_k2k_edges.size();
00409     out_info.num_kf2lm_edges_optimized = run_feat_ids.size();
00410     out_info.num_total_scalar_optimized = nUnknowns_scalars;
00411     out_info.num_span_tree_numeric_updates = count_span_tree_num_update;
00412     out_info.total_sqr_error_init = total_proj_error;
00413 
00414 
00415     VERBOSE_LEVEL(1) << "[OPT] LM: Initial RMSE=" <<  RMSE << " #Jcbs=" << count_jacobians << " #k2k_edges=" << nUnknowns_k2k << " #k2f_edges=" << nUnknowns_k2f << " #obs=" << nObs << std::endl;
00416 
00417     if (parameters.srba.feedback_user_iteration)
00418         (*parameters.srba.feedback_user_iteration)(0,total_proj_error,RMSE);
00419 
00420     // Compute the gradient: "grad = J^t * (h(x)-z)"
00421     // ---------------------------------------------------------------------------------
00422     Eigen::VectorXd  minus_grad; // The negative of the gradient.
00423 
00424     DETAILED_PROFILING_ENTER("opt.compute_minus_gradient")
00425     compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
00426     DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient")
00427 
00428 
00429     // Build symbolic structures for Schur complement:
00430     // ---------------------------------------------------------------------------------
00431     my_solver_t my_solver(
00432         m_verbose_level, m_profiler, 
00433         HAp,Hf,HApf, // The different symbolic/numeric Hessian
00434         minus_grad,  // minus gradient of the Ap part
00435         nUnknowns_k2k,
00436         nUnknowns_k2f);
00437     // Notice: At this point, the constructor of "my_solver_t" might have already built the Schur-complement 
00438     // of HAp-HApf into HAp: it's overwritten there (Only if RBA_OPTIONS::solver_t::USE_SCHUR=true).
00439 
00440     const double MAX_LAMBDA = this->parameters.srba.max_lambda;
00441 
00442     // These are defined here to avoid allocatin/deallocating memory with each iteration:
00443     vector<k2k_edge_t>            old_k2k_edge_unknowns;
00444     vector<pose_flag_t>      old_span_tree; // In the same order than "list_of_required_num_poses"
00445     vector<TRelativeLandmarkPos>  old_k2f_edge_unknowns;
00446 
00447 #if SRBA_DETAILED_TIME_PROFILING
00448     const std::string sLabelProfilerLM_iter = mrpt::format("opt.lm_iteration_k2k=%03u_k2f=%03u", static_cast<unsigned int>(nUnknowns_k2k), static_cast<unsigned int>(nUnknowns_k2f) );
00449 #endif
00450 
00451     // LevMar iterations -------------------------------------
00452     size_t iter; // Declared here so we can read the final # of iterations out of the "for" loop.
00453     bool   stop = false;
00454     for (iter=0; iter<this->parameters.srba.max_iters && !stop; iter++)
00455     {
00456         DETAILED_PROFILING_ENTER(sLabelProfilerLM_iter.c_str())
00457 
00458         // Try with different rho's until a better solution is found:
00459         double rho = 0;
00460         if (lambda>=MAX_LAMBDA)
00461         {
00462             stop=true;
00463             VERBOSE_LEVEL(2) << "[OPT] LM end criterion: lambda too large. " << lambda << ">=" <<MAX_LAMBDA<<endl;
00464         }
00465         if (RMSE < this->parameters.srba.max_error_per_obs_to_stop)
00466         {
00467             stop=true;
00468             VERBOSE_LEVEL(2) << "[OPT] LM end criterion: error too small. " << RMSE << "<" <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
00469         }
00470 
00471         while(rho<=0 && !stop)
00472         {
00473             // -------------------------------------------------------------------------
00474             //  Build the matrix (Hessian+ \lambda I) and decompose it with Cholesky:
00475             // -------------------------------------------------------------------------
00476             if ( !my_solver.solve(lambda) )
00477             {
00478                 // not positive definite so increase lambda and try again
00479                 lambda *= nu;
00480                 nu *= 2.;
00481                 stop = (lambda>MAX_LAMBDA);
00482 
00483                 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " NotDefPos in Cholesky. Retrying with lambda=" << lambda << std::endl;
00484                 continue;
00485             }
00486 
00487             // Make a copy of the old edge values, just in case we need to restore them back...
00488             // ----------------------------------------------------------------------------------
00489             DETAILED_PROFILING_ENTER("opt.make_backup_copy_edges")
00490 
00491             old_k2k_edge_unknowns.resize(nUnknowns_k2k);
00492             for (size_t i=0;i<nUnknowns_k2k;i++)
00493             {
00494                 old_k2k_edge_unknowns[i] = *k2k_edge_unknowns[i];
00495             }
00496 
00497             old_k2f_edge_unknowns.resize(nUnknowns_k2f);
00498             for (size_t i=0;i<nUnknowns_k2f;i++)
00499             {
00500                 old_k2f_edge_unknowns[i] = *k2f_edge_unknowns[i];
00501             }
00502 
00503             DETAILED_PROFILING_LEAVE("opt.make_backup_copy_edges")
00504 
00505             // Add SE(2/3) deltas to the k2k edges:
00506             // ------------------------------------
00507             DETAILED_PROFILING_ENTER("opt.add_se3_deltas_to_frames")
00508             for (size_t i=0;i<nUnknowns_k2k;i++)
00509             {
00510                 // edges_to_optimize:
00511                 const pose_t &old_pose = k2k_edge_unknowns[i]->inv_pose;
00512                 pose_t new_pose(mrpt::poses::UNINITIALIZED_POSE);
00513 
00514                 // Use the Lie Algebra methods for the increment:
00515                 const mrpt::math::CArrayDouble<POSE_DIMS> incr( & my_solver.delta_eps[POSE_DIMS*i] );
00516                 pose_t  incrPose(mrpt::poses::UNINITIALIZED_POSE);
00517                 se_traits_t::pseudo_exp(incr,incrPose);   // incrPose = exp(incr) (Lie algebra pseudo-exponential map)
00518 
00519                 //new_pose =  old_pose  [+] delta
00520                 //         = exp(delta) (+) old_pose
00521                 new_pose.composeFrom(incrPose, old_pose);
00522 
00523                 VERBOSE_LEVEL(3) << "[OPT] Update k2k_edge[" <<i<< "]: eps=" << incr.transpose() << "\n" << " such that: " << old_pose << " => " << new_pose << "\n";
00524 
00525                 //  Overwrite problem graph:
00526                 k2k_edge_unknowns[i]->inv_pose = new_pose;
00527             }
00528             DETAILED_PROFILING_LEAVE("opt.add_se3_deltas_to_frames")
00529 
00530 
00531             // Add R^3 deltas to the k2f edges:
00532             // ------------------------------------
00533             DETAILED_PROFILING_ENTER("opt.add_deltas_to_feats")
00534             for (size_t i=0;i<nUnknowns_k2f;i++)
00535             {
00536                 const double *delta_feat = &my_solver.delta_eps[idx_start_f+LM_DIMS*i];
00537                 for (size_t k=0;k<LM_DIMS;k++)
00538                     k2f_edge_unknowns[i]->pos[k] += delta_feat[k];
00539             }
00540             DETAILED_PROFILING_LEAVE("opt.add_deltas_to_feats")
00541 
00542 
00543             // Update the Spanning tree, making a back-up copy:
00544             // ------------------------------------------------------
00545 
00546 
00547             DETAILED_PROFILING_ENTER("opt.make_backup_copy_spntree_num")
00548             // DON'T: old_span_tree = rba_state.spanning_tree.num;  // This works but runs in O(n) with the size of the map!!
00549             // Instead: copy just the required entries:
00550             {
00551                 const size_t nReqNumPoses = list_of_required_num_poses.size();
00552                 if (old_span_tree.size()!=nReqNumPoses) old_span_tree.resize(nReqNumPoses);
00553                 for (size_t i=0;i<nReqNumPoses;i++) 
00554                 {
00555                     old_span_tree[i].pose = list_of_required_num_poses[i]->pose;
00556                 }
00557             }
00558             DETAILED_PROFILING_LEAVE("opt.make_backup_copy_spntree_num")
00559 
00560 
00561             DETAILED_PROFILING_ENTER("opt.update_spanning_tree_num")
00562             for (size_t i=0;i<list_of_required_num_poses.size();i++)
00563                 list_of_required_num_poses[i]->mark_outdated();
00564 
00565             rba_state.spanning_tree.update_numeric(kfs_num_spantrees_to_update, true /* Only those marked as outdated above */);
00566             DETAILED_PROFILING_LEAVE("opt.update_spanning_tree_num")
00567 
00568             // Compute new reprojection errors:
00569             // ----------------------------------
00570             vector_residuals_t  new_residuals;
00571 
00572             DETAILED_PROFILING_ENTER("opt.reprojection_residuals")
00573             double new_total_proj_error = reprojection_residuals(
00574                 new_residuals, // Out
00575                 involved_obs // In
00576                 );
00577             DETAILED_PROFILING_LEAVE("opt.reprojection_residuals")
00578 
00579             const double new_RMSE = std::sqrt(new_total_proj_error/nObs);
00580 
00581             const double error_reduction_ratio = total_proj_error>0 ? (total_proj_error - new_total_proj_error)/total_proj_error : 0;
00582 
00583             // is this better or worse?
00584             // -----------------------------
00585             rho = (total_proj_error - new_total_proj_error)/ (my_solver.delta_eps.array()*(lambda*my_solver.delta_eps + minus_grad).array() ).sum();
00586 
00587             if(rho>0)
00588             {
00589                 // Good: Accept new values
00590 
00591                 // Recalculate Jacobians?
00592                 bool do_relinearize = (error_reduction_ratio<0 || error_reduction_ratio> this->parameters.srba.min_error_reduction_ratio_to_relinearize );
00593 
00594                 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " RMSE: " << RMSE << " -> " << new_RMSE <<  ", rho=" << rho << ", Err.reduc.ratio="<< error_reduction_ratio << " => Relinearize?:" << (do_relinearize ? "YES":"NO") << std::endl;
00595                 if (parameters.srba.feedback_user_iteration)
00596                     (*parameters.srba.feedback_user_iteration)(iter,new_total_proj_error,new_RMSE);
00597 
00598                 // Switch variables to the temptative ones, which are now accepted:
00599                 //  (swap where possible, since it's faster)
00600                 // ---------------------------------------------------------------------
00601                 residuals.swap( new_residuals );
00602 
00603                 total_proj_error = new_total_proj_error;
00604                 RMSE = new_RMSE;
00605 
00606                 if (do_relinearize)
00607                 {
00608                     DETAILED_PROFILING_ENTER("opt.reset_Jacobs_validity")
00609                     // Before evaluating Jacobians we must reset as "valid" all the involved observations.
00610                     //  If needed, they'll be marked as invalid by the Jacobian evaluator if just one of the components
00611                     //  for one observation leads to an error.
00612                     for (size_t i=0;i<nObs;i++)
00613                         rba_state.all_observations_Jacob_validity[ involved_obs[i].obs_idx ] = 1;
00614 
00615                     DETAILED_PROFILING_LEAVE("opt.reset_Jacobs_validity")
00616 
00617                     DETAILED_PROFILING_ENTER("opt.recompute_all_Jacobians")
00618                     recompute_all_Jacobians(dh_dAp, dh_df);
00619                     DETAILED_PROFILING_LEAVE("opt.recompute_all_Jacobians")
00620 
00621                     // Recalculate Hessian:
00622                     DETAILED_PROFILING_ENTER("opt.sparse_hessian_update_numeric")
00623                     sparse_hessian_update_numeric(HAp);
00624                     sparse_hessian_update_numeric(Hf);
00625                     sparse_hessian_update_numeric(HApf);
00626                     DETAILED_PROFILING_LEAVE("opt.sparse_hessian_update_numeric")
00627 
00628                     my_solver.realize_relinearized();
00629                 }
00630 
00631                 // Update gradient:
00632                 DETAILED_PROFILING_ENTER("opt.compute_minus_gradient")
00633                 compute_minus_gradient(/* Out: */ minus_grad, /* In: */ dh_dAp, dh_df, residuals, obs_global_idx2residual_idx);
00634                 DETAILED_PROFILING_LEAVE("opt.compute_minus_gradient")
00635 
00636                 const double norm_inf_min_grad = mrpt::math::norm_inf(minus_grad);
00637                 if (norm_inf_min_grad<=max_gradient_to_stop)
00638                 {
00639                     VERBOSE_LEVEL(2) << "[OPT] LM end criterion: norm_inf(minus_grad) below threshold: " << norm_inf_min_grad << " <= " <<max_gradient_to_stop<<endl;
00640                     stop = true;
00641                 }
00642                 if (RMSE<this->parameters.srba.max_error_per_obs_to_stop)
00643                 {
00644                     VERBOSE_LEVEL(2) << "[OPT] LM end criterion: RMSE below threshold: " << RMSE << " < " <<this->parameters.srba.max_error_per_obs_to_stop<<endl;
00645                     stop = true;
00646                 }
00647                 if (rho>this->parameters.srba.max_rho)
00648                 {
00649                     VERBOSE_LEVEL(2) << "[OPT] LM end criterion: rho above threshold: " << rho << " > " <<this->parameters.srba.max_rho<<endl;
00650                     stop = true;
00651                 }
00652                 // Reset other vars:
00653                 lambda *= 1.0/3.0; //std::max(1.0/3.0, 1-std::pow(2*rho-1,3.0) );
00654                 nu = 2.0;
00655 
00656                 my_solver.realize_lambda_changed();
00657             }
00658             else
00659             {
00660                 DETAILED_PROFILING_ENTER("opt.failedstep_restore_backup")
00661 
00662                 // Restore old values and retry again with a different lambda:
00663                 //DON'T: rba_state.spanning_tree.num = old_span_tree; // NO! Don't do this, since existing pointers will break -> Copy elements one by one:
00664                 {
00665                     const size_t nReqNumPoses = list_of_required_num_poses.size();
00666                     for (size_t i=0;i<nReqNumPoses;i++) 
00667                     {
00668                         const_cast<pose_flag_t*>(list_of_required_num_poses[i])->pose = old_span_tree[i].pose;
00669                     }
00670                 }
00671 
00672                 // Restore old edge values:
00673                 for (size_t i=0;i<nUnknowns_k2k;i++)
00674                 {
00675                     *k2k_edge_unknowns[i] = old_k2k_edge_unknowns[i];
00676                 }
00677                 for (size_t i=0;i<nUnknowns_k2f;i++)
00678                 {
00679                     *k2f_edge_unknowns[i] = old_k2f_edge_unknowns[i];
00680                 }
00681 
00682                 DETAILED_PROFILING_LEAVE("opt.failedstep_restore_backup")
00683 
00684                 VERBOSE_LEVEL(2) << "[OPT] LM iter #"<< iter << " no update,errs: " << sqrt(total_proj_error/nObs) << " < " << sqrt(new_total_proj_error/nObs) << " lambda=" << lambda <<endl;
00685                 lambda *= nu;
00686                 nu *= 2.0;
00687                 stop = (lambda>MAX_LAMBDA);
00688 
00689                 my_solver.realize_lambda_changed();
00690             }
00691 
00692         }; // end while rho
00693 
00694         DETAILED_PROFILING_LEAVE(sLabelProfilerLM_iter.c_str())
00695 
00696     } // end for LM "iter"
00697 
00698 #if 0
00699     std::cout << "residuals" << std::endl;
00700     for( size_t r = 0; r < residuals.size(); ++r ) 
00701     {
00702         std::cout << involved_obs[r].k2f->obs.obs.feat_id << ","
00703              << residuals[r][0] << "," 
00704              << residuals[r][1] << "," 
00705              << residuals[r][2] << "," 
00706              << residuals[r][3];
00707 
00708         const double totalres = residuals[r][0]*residuals[r][0]+
00709             residuals[r][1]*residuals[r][1]+
00710             residuals[r][2]*residuals[r][2]+
00711             residuals[r][3]*residuals[r][3];
00712 
00713         if( totalres > 20 )
00714             std::cout << " <-- spurious( " << totalres << ")";
00715         
00716         std::cout << std::endl;
00717     }
00718     cout << "done" << std::endl;
00719 #endif 
00720 
00721     // Final output info:
00722     out_info.total_sqr_error_final = total_proj_error;
00723 
00724     // Recover information on covariances?
00725     // ----------------------------------------------
00726     DETAILED_PROFILING_ENTER("opt.cov_recovery")
00727     rba_state.unknown_lms_inf_matrices.clear();
00728     switch (parameters.srba.cov_recovery)
00729     {
00730         case crpNone:
00731             break;
00732         case crpLandmarksApprox:
00733         {
00734             for (size_t i=0;i<nUnknowns_k2f;i++)
00735             {
00736                 if (!my_solver.was_ith_feature_invertible(i))
00737                     continue;
00738 
00739                 const typename hessian_traits_t::TSparseBlocksHessian_f::col_t & col_i = Hf.getCol(i);
00740                 ASSERT_(col_i.rbegin()->first==i)  // Make sure the last block matrix is the diagonal term of the upper-triangular matrix.
00741 
00742                 const typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_src = col_i.rbegin()->second.num;
00743                 typename hessian_traits_t::TSparseBlocksHessian_f::matrix_t & inf_mat_dst = rba_state.unknown_lms_inf_matrices[ run_feat_ids[i] ];
00744                 inf_mat_dst = inf_mat_src;
00745             }
00746         }
00747         break;
00748         default: 
00749             throw std::runtime_error("Unknown value found for 'parameters.srba.cov_recovery'");
00750     }
00751     DETAILED_PROFILING_LEAVE("opt.cov_recovery")
00752 
00753     if (parameters.srba.compute_condition_number)
00754     {
00755         DETAILED_PROFILING_ENTER("opt.condition_number")
00756 
00757         // Evaluate conditioning number of hessians:
00758         mrpt::math::CMatrixDouble dense_HAp;
00759         HAp.getAsDense(dense_HAp, true /* recover both triangular parts */);
00760 
00761         const Eigen::JacobiSVD<mrpt::math::CMatrixDouble::Base> H_svd = dense_HAp.jacobiSvd();
00762         out_info.HAp_condition_number = H_svd.singularValues().maxCoeff()/H_svd.singularValues().minCoeff();
00763 
00764         DETAILED_PROFILING_LEAVE("opt.condition_number")
00765     }
00766 
00767     // Fill in any other extra info from the solver:
00768     DETAILED_PROFILING_ENTER("opt.get_extra_results")
00769     my_solver.get_extra_results(out_info.extra_results);
00770     DETAILED_PROFILING_LEAVE("opt.get_extra_results")
00771 
00772     // Extra verbose: display final value of each optimized pose:
00773     if (m_verbose_level>=2 && !run_k2k_edges.empty())
00774     {
00775         std::cout << "[OPT] k2k_edges to optimize, final value(s):\n";
00776         ASSERT_(k2k_edge_unknowns.size()==run_k2k_edges.size())
00777         for (size_t i=0;i<run_k2k_edges.size();i++)
00778             std::cout << " k2k_edge: " <<k2k_edge_unknowns[i]->from << "=>" << k2k_edge_unknowns[i]->to << ",inv_pose=" << k2k_edge_unknowns[i]->inv_pose << std::endl;
00779     }
00780 
00781     // Save (quick swap) the list of unknowns to the output structure, 
00782     //  now that these vectors are not needed anymore:
00783     out_info.optimized_k2k_edge_indices.swap(run_k2k_edges);
00784     out_info.optimized_landmark_indices.swap(run_feat_ids);
00785 
00786     m_profiler.leave("opt");
00787     out_info.obs_rmse = RMSE;
00788 
00789     const bool rmse_too_high = (RMSE>parameters.srba.max_rmse_show_red_warning);
00790     if (rmse_too_high && m_verbose_level>=1) mrpt::system::setConsoleColor(mrpt::system::CONCOL_RED);
00791     VERBOSE_LEVEL(1) << "[OPT] Final RMSE=" <<  RMSE << " #iters=" << iter << "\n";
00792     if (rmse_too_high && m_verbose_level>=1) mrpt::system::setConsoleColor(mrpt::system::CONCOL_NORMAL);
00793 }
00794 
00795 } // End of namespaces
00796 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends