Main MRPT website > C++ reference
MRPT logo
schur.h
Go to the documentation of this file.
1 /* +---------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2015, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +---------------------------------------------------------------------------+ */
9 
10 #pragma once
11 
12 namespace mrpt { namespace srba {
13 
14  /** A generic symbolic and numeric Schur-complement handler for builing reduced systems of equations.
15  */
16  template <class HESS_Ap, class HESS_f, class HESS_Apf>
18  {
19  public:
20 
21  /** Constructor: builds the symbolic representations
22  * Note: HApf must be in row-compressed form; HAp & Hf in column-compressed form.
23  * \param[in] _minus_grad_f Can be NULL if there're no observations of landmarks with unknown positions (may still be of LMs with known ones).
24  */
25  SchurComplement(HESS_Ap &_HAp, HESS_f & _Hf, HESS_Apf & _HApf, double * _minus_grad_Ap, double * _minus_grad_f)
26  : HAp(_HAp), Hf(_Hf), HApf(_HApf),
27  minus_grad_Ap(_minus_grad_Ap),
28  minus_grad_f(_minus_grad_f),
29  // Problem dims:
33  {
34  if (!nUnknowns_f || !nUnknowns_Ap) return;
35 
36  // 0) Make copy of the original numerical values of HAp:
37  // -----------------------------------------------------------------
38  HAp_original.copyNumericalValuesFrom( HAp );
39 
40  // 1) List of all diagonal blocks in Hf which have to be inverted
41  // -----------------------------------------------------------------
43  for (size_t i=0;i<nUnknowns_f;i++)
44  {
45  const typename HESS_f::col_t & col_i = Hf.getCol(i);
46  ASSERT_(!col_i.empty() && col_i.rbegin()->first==i)
47 
48  m_Hf_blocks_info[i].sym_Hf_diag_blocks = &col_i.rbegin()->second.num;
49  }
50 
51  // 2) Build instructions to reduce H_Ap and grad_Ap
52  // ----------------------------------------------------
53  m_sym_HAp_reduce.clear();
55 
56  for (size_t i=0;i<nUnknowns_Ap;i++)
57  { // Only upper-half triangle:
58  typename HESS_Ap::col_t & HAp_col_i = HAp.getCol(i);
59 
60  // Go thru all the "j" (row) indices in j \in [0,i]
61  // even if there's not an entry in the column map "HAp_col_i" (yet).
62  // If the block (i,j) has any feature in common, then we'll add
63  // a block HAp_{i,j} right here and add the corresponding instructions for building the numeric Schur.
64  for (size_t j=0;j<=i;j++)
65  {
66  // We are at the HAp block (i,j):
67  // Make a list of all the:
68  // \Sum H_{pi,lk} * H_{lk}^{-1} * H_{pj,lk}^t
69  //
70  // This amounts to looking for the "intersecting set" of the two rows "i" & "j" in HApf:
71  THApSymbolicEntry sym_ij;
72 
73  // (i==j) -> take advantage of repeated terms and build instructions for the gradient:
74  TGradApSymbolicEntry *grad_entries = (i==j) ? &m_sym_GradAp_reduce[i] : NULL;
75 
76 
77  // get Rows i & j from HAp_f:
78  typename HESS_Apf::col_t & row_i = HApf.getCol(i);
79  typename HESS_Apf::col_t & row_j = HApf.getCol(j);
80 
81  // code below based on "std::set_intersection"
82  typename HESS_Apf::col_t::const_iterator it_i = row_i.begin();
83  typename HESS_Apf::col_t::const_iterator it_j = row_j.begin();
84  const typename HESS_Apf::col_t::const_iterator it_i_end = row_i.end();
85  const typename HESS_Apf::col_t::const_iterator it_j_end = row_j.end();
86 
87  while (it_i!=it_i_end && it_j!=it_j_end)
88  {
89  if ( it_i->first < it_j->first ) ++it_i;
90  else if ( it_j->first < it_i->first ) ++it_j;
91  else
92  {
93  // match between: it_i->first == it_j->first
94 
95  // Add a new triplet:
96  // * const typename HESS_Apf::matrix_t * Hpi_lk;
97  // * const typename HESS_f::matrix_t * inv_Hf_lk;
98  // * const typename HESS_Apf::matrix_t * Hpj_lk;
99 
100  const size_t idx_feat = it_j->first;
101  ASSERT_(idx_feat<nUnknowns_f)
102 
103  // Gradient (only if we're at i==j)
104  typename HESS_Apf::matrix_t *out_temporary_result = NULL;
105  if (grad_entries)
106  {
107  grad_entries->lst_terms_to_subtract.resize( grad_entries->lst_terms_to_subtract.size()+1 );
108  typename TGradApSymbolicEntry::TEntry &ent = *grad_entries->lst_terms_to_subtract.rbegin();
109  ent.feat_idx = idx_feat;
110  out_temporary_result = &ent.Hpi_lk_times_inv_Hf_lk;
111  }
112 
113  // Hessian:
114 #if 0
115  std::cout << "SymSchur.HAp("<<i<<","<<j<< "): HApf_" << it_i->first << " * HApf_" << it_j->first << std::endl;
116 #endif
117  sym_ij.lst_terms_to_add.push_back( typename THApSymbolicEntry::TEntry(
118  &it_j->second.num, //&it_i->second.num,
119  &m_Hf_blocks_info[idx_feat],
120  &it_i->second.num, //&it_j->second.num,
121  out_temporary_result ) );
122 
123  // Move:
124  ++it_i; ++it_j;
125  }
126  } // end while (find intersect)
127 
128  // Only append if not empty:
129  if (!sym_ij.lst_terms_to_add.empty())
130  {
131  // There are common observations between Api & Apj:
132  // 1) If there was already an HAp_ij block, take a reference to it.
133  // 2) Otherwise, insert it now.
134  typename HESS_Ap::col_t::iterator itExistingRowEntry;
135  if ( ((i==j) || (i!=j && HAp_col_i.size()!=1)) // If size==1 don't even waste time: it's a diagonal block.
136  &&
137  (itExistingRowEntry=HAp_col_i.find(j))!= HAp_col_i.end())
138  {
139  // Add reference to target matrix for numeric Schur:
140  sym_ij.HAp_ij = & itExistingRowEntry ->second.num;
141  }
142  else
143  {
144  ASSERT_(i!=j) // We should only reach here for off-diagonal blocks!
145  // Create & add reference to target matrix for numeric Schur:
146  sym_ij.HAp_ij = & HAp_col_i[j].num;
147  // Clear initial contents of the Hessian to all zeros:
148  sym_ij.HAp_ij->setZero();
149  }
150 
151  // Fast move into the deque:
152  m_sym_HAp_reduce.resize( m_sym_HAp_reduce.size()+1 );
153  sym_ij.swap( *m_sym_HAp_reduce.rbegin() );
154  }
155 
156  } // end for j (row in HAp)
157  } // end for i (col in HAp)
158 
159  } // end of ctor.
160 
161  /** Must be called after the numerical values of the Hessian HAp change, typically after an optimization update
162  * which led to re-evaluation of the Jacobians around a new linearization point. This method latches the current
163  * value of HAp for reseting it again if we later need to recompute the reduced Schur system for different values of lambda.
164  */
166  {
167  HAp_original.copyNumericalValuesFrom( HAp );
168  }
169 
170  /** After calling numeric_build_reduced_system() one can get the stats on how many features are actually estimable */
171  size_t getNumFeatures() const { return nUnknowns_f; }
173 
174 
175  /** Replace the HAp matrix with its Schur reduced version.
176  * The lambda value is used to sum it to the diagonal of Hf (features), but it's NOT added
177  * to the output HAp. This is intentional, so the caller can add different lamdba values
178  * as needed in different trials if an ill conditioned matrix is found.
179  */
180  void numeric_build_reduced_system(const double lambda)
181  {
182  if (!nUnknowns_f || !nUnknowns_Ap) return;
183 
184  // 0) Restore the original state of the Hessian (since all changes are defined as sums / substractions on it)
185  // and this operation can be invoked several times with the same original HAp but diferent lambda values
186  // (which forces a total recomputation due to the inv(Hf+\lambda*I) terms).
187  // --------------------------------------------------------------------------------
188  HAp.copyNumericalValuesFrom( HAp_original );
189 
190  // 1) Invert diagonal blocks in Hf:
191  // ---------------------------------
193  for (size_t i=0;i<nUnknowns_f;i++)
194  {
195  // LU decomposition is rank-revealing (not like LLt)
196  typename HESS_f::matrix_t Hfi = *m_Hf_blocks_info[i].sym_Hf_diag_blocks;
197  for (int k=0;k<Hfi.cols();k++)
198  Hfi.coeffRef(k,k)+=lambda;
199 
200  const Eigen::FullPivLU<typename HESS_f::matrix_t> lu( Hfi );
201 
202  // Badly conditioned matrix?
203  if (true== (m_Hf_blocks_info[i].num_Hf_diag_blocks_invertible = lu.isInvertible() ))
204  {
206  m_Hf_blocks_info[i].num_Hf_diag_blocks_inverses = lu.inverse();
207  }
208  }
209 
210  // 2) H_Ap of the reduced system:
211  // ---------------------------------
212  typename HESS_Apf::matrix_t aux_Hpi_lk_times_inv_Hf_lk;
214  {
215  const THApSymbolicEntry &sym_entry = *it;
216 
217  typename HESS_Ap::matrix_t & HAp_ij = *sym_entry.HAp_ij;
218 
219  const size_t N = sym_entry.lst_terms_to_add.size();
220 
221  //std::cout << "before:\n" << HAp_ij << std::endl;
222  for (size_t i=0;i<N;i++)
223  {
224  const typename THApSymbolicEntry::TEntry &entry = sym_entry.lst_terms_to_add[i];
225 
226  if (entry.inv_Hf_lk->num_Hf_diag_blocks_invertible)
227  {
228  //cout << "Hfinv:\n" << entry.inv_Hf_lk->num_Hf_diag_blocks_inverses << endl;
229  // \bar{Hp} -= Hpi_lk * inv(Hf_lk) * Hpj_lk^t
230 
231  // Store this term for reuse with the gradient update, or use temporary local storage:
232  typename HESS_Apf::matrix_t * Hpi_lk_times_inv_Hf_lk =
233  entry.out_Hpi_lk_times_inv_Hf_lk!=NULL
234  ?
235  entry.out_Hpi_lk_times_inv_Hf_lk : &aux_Hpi_lk_times_inv_Hf_lk;
236 
237  Hpi_lk_times_inv_Hf_lk->noalias() = (*entry.Hpi_lk) * entry.inv_Hf_lk->num_Hf_diag_blocks_inverses;
238 
239  //std::cout << "product #" << i << ":\nHpi_lk:\n" << (*entry.Hpi_lk) << "\nHfi^-1:\n" << entry.inv_Hf_lk->num_Hf_diag_blocks_inverses << "\nHpj_lk^t:\n" << entry.Hpj_lk->transpose() << "\n\n";
240  HAp_ij.noalias() -= (*Hpi_lk_times_inv_Hf_lk) * (*entry.Hpj_lk).transpose();
241  }
242  }
243  //std::cout << "after:\n" << HAp_ij<< std::endl;
244  }
245 
246  // 3) g_Ap of the reduced system:
247  // ---------------------------------
248  double * modified_grad_Ap = minus_grad_Ap;
249  for (size_t i=0;i<nUnknowns_Ap;i++)
250  {
251  vector_Ap_t grad_Ap = vector_Ap_t(modified_grad_Ap); // A map which wraps the pointer
252  for (typename TGradApSymbolicEntry::lst_terms_t::const_iterator it=m_sym_GradAp_reduce[i].lst_terms_to_subtract.begin();it!=m_sym_GradAp_reduce[i].lst_terms_to_subtract.end();++it)
253  {
254  if (m_Hf_blocks_info[it->feat_idx].num_Hf_diag_blocks_invertible)
255  {
256  double *grad_df = this->minus_grad_f + it->feat_idx * HESS_f::matrix_t::RowsAtCompileTime;
257  // g -= \Sum Hpi_lk * inv(Hf_lk) * grad_lk
258  //
259  grad_Ap.noalias() -= it->Hpi_lk_times_inv_Hf_lk * vector_f_t(grad_df);
260  }
261  }
262 
263  // Advance to next part of gradient:
264  modified_grad_Ap+= HESS_Ap::matrix_t::RowsAtCompileTime;
265  }
266 
267 
268  } // end of numeric_build_reduced_system
269 
270 
272  double *in_deltas_Ap,
273  double *out_deltas_feats
274  )
275  {
276  // 1/2: Go thru all the Hessian blocks of HApl and subtracts the corresponding term to
277  // the grad_feats:
278  for (size_t idx_Ap=0;idx_Ap<nUnknowns_Ap;idx_Ap++)
279  {
280  // get Row i from HAp_f:
281  typename HESS_Apf::col_t & row_i = HApf.getCol(idx_Ap);
282 
283  const vector_Ap_t delta_idx_Ap = vector_Ap_t(in_deltas_Ap + idx_Ap * HESS_Ap::matrix_t::RowsAtCompileTime );
284 
285  for (typename HESS_Apf::col_t::const_iterator itCol=row_i.begin();itCol!=row_i.end();++itCol)
286  {
287  const size_t idx_feat = itCol->first;
288  if (!was_ith_feature_invertible(idx_feat))
289  continue;
290 
291  double *grad_df = this->minus_grad_f + idx_feat * HESS_f::matrix_t::RowsAtCompileTime;
292 
293  // g_reduced = -g_l - \Sum H^t_pi_lk * delta_Ap_i
294  vector_f_t(grad_df).noalias() -= itCol->second.num.transpose() * delta_idx_Ap;
295  }
296  }
297 
298  // 2/2: Solve each individual feature increment:
299  for (size_t idx_feat=0;idx_feat<nUnknowns_f;idx_feat++)
300  {
301  if (!was_ith_feature_invertible(idx_feat))
302  continue;
303 
304  vector_f_t delta_feat = vector_f_t( out_deltas_feats + idx_feat * HESS_f::matrix_t::RowsAtCompileTime );
305  vector_f_t grad_df = vector_f_t(this->minus_grad_f + idx_feat * HESS_f::matrix_t::RowsAtCompileTime );
306  //std::cout << grad_df.transpose() << std::endl << m_Hf_blocks_info[idx_feat].num_Hf_diag_blocks_inverses << std::endl << std::endl;
307 
308  delta_feat = (m_Hf_blocks_info[idx_feat].num_Hf_diag_blocks_inverses * grad_df);
309  }
310 
311  } // end of numeric_solve_for_features
312 
313 
314  bool was_ith_feature_invertible(const size_t i) const { return m_Hf_blocks_info[i].num_Hf_diag_blocks_invertible; }
315 
316  private:
317  // ----------- Input data ------------------
318  HESS_Ap HAp_original; // (Copy of the numerical values of HAp)
319  HESS_Ap & HAp; // Column compressed
320  HESS_f & Hf; // Column compressed
321  HESS_Apf & HApf; // *ROW* compressed
322  double * minus_grad_Ap;
323  double * minus_grad_f; // Should be changed to "const" if used a Eigen::"ConstMap"...
324 
325  const size_t nUnknowns_Ap;
326  const size_t nUnknowns_f;
327  size_t nHf_invertible_blocks; //!< for stats, the number of Hf diagonal blocks which are not rank-deficient.
328  // -----------------------------------------
329  typedef typename Eigen::Map<Eigen::Matrix<double,HESS_Ap::matrix_t::RowsAtCompileTime,1> > vector_Ap_t;
330  typedef typename Eigen::Map<Eigen::Matrix<double,HESS_f::matrix_t::RowsAtCompileTime,1> > vector_f_t;
331 
332 
334  {
335  const typename HESS_f::matrix_t * sym_Hf_diag_blocks;
336  typename HESS_f::matrix_t num_Hf_diag_blocks_inverses;
337  bool num_Hf_diag_blocks_invertible; //!< Whether \a num_Hf_diag_blocks_inverses could be generated
338 
339  TInfoPerHfBlock() : sym_Hf_diag_blocks(NULL), num_Hf_diag_blocks_invertible(false) { }
340 
342  };
343 
345 
346  TInfoPerHfBlock_vector_t m_Hf_blocks_info;
347 
348  /** Info for each block in HAp */
350  {
351  struct TEntry
352  {
354  const typename HESS_Apf::matrix_t * _Hpi_lk,
355  const TInfoPerHfBlock * _inv_Hf_lk,
356  const typename HESS_Apf::matrix_t * _Hpj_lk,
357  typename HESS_Apf::matrix_t * _out_Hpi_lk_times_inv_Hf_lk
358  )
359  :
360  Hpi_lk(_Hpi_lk),
361  inv_Hf_lk(_inv_Hf_lk),
362  Hpj_lk(_Hpj_lk),
363  out_Hpi_lk_times_inv_Hf_lk(_out_Hpi_lk_times_inv_Hf_lk)
364  {
365  }
366 
367  const typename HESS_Apf::matrix_t * Hpi_lk;
369  const typename HESS_Apf::matrix_t * Hpj_lk;
370  typename HESS_Apf::matrix_t * out_Hpi_lk_times_inv_Hf_lk; //!< If NULL=use local storage.
371  };
372 
373  typename HESS_Ap::matrix_t * HAp_ij;
374  std::deque<TEntry> lst_terms_to_add;
375 
377  {
378  std::swap(HAp_ij,o.HAp_ij);
379  lst_terms_to_add.swap(o.lst_terms_to_add);
380  }
381  };
382 
383  std::deque<THApSymbolicEntry> m_sym_HAp_reduce; //!< All the required operations needed to update HAp into its reduced system.
384 
385 
387  {
388  struct TEntry
389  {
390  size_t feat_idx;
391  // Used as a temporary container for the product, but also because this term reappears in the gradient update:
392  typename HESS_Apf::matrix_t Hpi_lk_times_inv_Hf_lk;
393 
395  };
396 
397 // BUG ALERT in Eigen/Deque/MSVC 2008 32bit: http://eigen.tuxfamily.org/bz/show_bug.cgi?id=83
398 // This workaround will lose performance but at least allow compiling. Remove this if someday a solution
399 // to the bug above is found:
400 #if defined(_MSC_VER) && (_MSC_VER < 1600) // if we use MSVC older than 2010:
401  typedef typename mrpt::aligned_containers<TEntry>::list_t lst_terms_t; // slower than deque, but works...
402 #else
403  // (Warning: Don't replace the STL container with "vector" or any other that invalidate
404  // pointers, which is assumed in the implementation. That's why I use "std::deque")
406 #endif
407  // The list itself:
409  };
410  std::vector<TGradApSymbolicEntry> m_sym_GradAp_reduce; //!< All the required operations needed to update Gradient of Ap into its reduced system.
411 
412  }; // end of class SchurComplement
413 
414 } } // end of namespaces
415 
Eigen::Map< Eigen::Matrix< double, HESS_f::matrix_t::RowsAtCompileTime, 1 > > vector_f_t
Definition: schur.h:330
std::deque< THApSymbolicEntry > m_sym_HAp_reduce
All the required operations needed to update HAp into its reduced system.
Definition: schur.h:383
mrpt::aligned_containers< TEntry >::deque_t lst_terms_t
Definition: schur.h:405
std::deque< TEntry > lst_terms_to_add
Definition: schur.h:374
#define MRPT_MAKE_ALIGNED_OPERATOR_NEW
Definition: memory.h:112
std::vector< TGradApSymbolicEntry > m_sym_GradAp_reduce
All the required operations needed to update Gradient of Ap into its reduced system.
Definition: schur.h:410
Eigen::Map< Eigen::Matrix< double, typename hessian_traits_t::TSparseBlocksHessian_Ap::matrix_t::RowsAtCompileTime, 1 > > vector_Ap_t
Definition: schur.h:329
Scalar * iterator
Definition: eigen_plugins.h:23
Definition: schur.h:351
const size_t nUnknowns_Ap
Definition: schur.h:325
const Scalar * const_iterator
Definition: eigen_plugins.h:24
std::deque< TYPE1, Eigen::aligned_allocator< TYPE1 > > deque_t
size_t feat_idx
Definition: schur.h:390
bool num_Hf_diag_blocks_invertible
Whether num_Hf_diag_blocks_inverses could be generated.
Definition: schur.h:337
size_t getNumFeatures() const
After calling numeric_build_reduced_system() one can get the stats on how many features are actually ...
Definition: schur.h:171
size_t getNumFeaturesFullRank() const
Definition: schur.h:172
HESS_Ap::matrix_t * HAp_ij
Definition: schur.h:373
const HESS_Apf::matrix_t * Hpi_lk
Definition: schur.h:367
SchurComplement(HESS_Ap &_HAp, HESS_f &_Hf, HESS_Apf &_HApf, double *_minus_grad_Ap, double *_minus_grad_f)
Constructor: builds the symbolic representations Note: HApf must be in row-compressed form; HAp & Hf ...
Definition: schur.h:25
HESS_f::matrix_t num_Hf_diag_blocks_inverses
Definition: schur.h:336
EIGEN_STRONG_INLINE size_t getColCount() const
Get number of columns.
Definition: eigen_plugins.h:48
Definition: schur.h:386
void numeric_build_reduced_system(const double lambda)
Replace the HAp matrix with its Schur reduced version.
Definition: schur.h:180
bool was_ith_feature_invertible(const size_t i) const
Definition: schur.h:314
TEntry(const typename HESS_Apf::matrix_t *_Hpi_lk, const TInfoPerHfBlock *_inv_Hf_lk, const typename HESS_Apf::matrix_t *_Hpj_lk, typename HESS_Apf::matrix_t *_out_Hpi_lk_times_inv_Hf_lk)
Definition: schur.h:353
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
const size_t nUnknowns_f
Definition: schur.h:326
TInfoPerHfBlock_vector_t m_Hf_blocks_info
Definition: schur.h:346
const TInfoPerHfBlock * inv_Hf_lk
Definition: schur.h:368
void numeric_solve_for_features(double *in_deltas_Ap, double *out_deltas_feats)
Definition: schur.h:271
HESS_Apf::matrix_t * out_Hpi_lk_times_inv_Hf_lk
If NULL=use local storage.
Definition: schur.h:370
void swap(THApSymbolicEntry &o)
Definition: schur.h:376
A generic symbolic and numeric Schur-complement handler for builing reduced systems of equations...
Definition: schur.h:17
#define ASSERT_(f)
const HESS_Apf::matrix_t * Hpj_lk
Definition: schur.h:369
const HESS_f::matrix_t * sym_Hf_diag_blocks
Definition: schur.h:335
lst_terms_t lst_terms_to_subtract
Definition: schur.h:408
Info for each block in HAp.
Definition: schur.h:349
void realize_HAp_changed()
Must be called after the numerical values of the Hessian HAp change, typically after an optimization ...
Definition: schur.h:165
std::vector< TYPE1, Eigen::aligned_allocator< TYPE1 > > vector_t
HESS_Apf::matrix_t Hpi_lk_times_inv_Hf_lk
Definition: schur.h:392
mrpt::aligned_containers< TInfoPerHfBlock >::vector_t TInfoPerHfBlock_vector_t
Definition: schur.h:344
std::list< TYPE1, Eigen::aligned_allocator< TYPE1 > > list_t
Definition: schur.h:388
size_t nHf_invertible_blocks
for stats, the number of Hf diagonal blocks which are not rank-deficient.
Definition: schur.h:327



Page generated by Doxygen 1.8.9.1 for MRPT 1.3.0 SVN: at Sun Sep 13 03:55:12 UTC 2015