mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
rheiv.h
Go to the documentation of this file.
1 // clang: MatousFormat
7 #ifndef HEIV_H
8 #define HEIV_H
9 
10 #include <Eigen/Dense>
11 #include <iostream>
12 #include <chrono>
13 
14 namespace mrs_lib
15 {
16  /* eigenvector_exception //{ */
17 
24  struct eigenvector_exception : public std::exception
25  {
31  virtual const char* what() const throw() override
32  {
33  return "Could not compute matrix eigenvectors.";
34  }
35  };
36 
37  //}
38 
39  /* class RHEIV //{ */
60  template <int n_states, int n_params>
61  class RHEIV
62  {
63  static_assert(n_states <= n_params, "Sorry, n_states must be smaller or equal to n_params");
64  public:
65  /* RHEIV definitions (typedefs, constants etc) //{ */
66 
67  static const int k = n_states;
68  static const int l = n_params;
69  static const int lr = l - 1;
71  using x_t = Eigen::Matrix<double, k, 1>;
72  using xs_t = Eigen::Matrix<double, k, -1>;
73  using u_t = Eigen::Matrix<double, l, 1>;
74  using P_t = Eigen::Matrix<double, k, k>;
75  using Ps_t = std::vector<P_t>;
77  using z_t = Eigen::Matrix<double, lr, 1>;
78  using zs_t = Eigen::Matrix<double, lr, -1>;
79  using f_z_t = typename std::function<zs_t(const xs_t&)>;
81  using dzdx_t = Eigen::Matrix<double, lr, k>;
82  using dzdxs_t = std::vector<dzdx_t>;
83  using f_dzdx_t = typename std::function<dzdx_t(const xs_t&)>;
85  using theta_t = Eigen::Matrix<double, l, 1>;
86  using eta_t = z_t;
88  private:
89  using A_t = Eigen::Matrix<double, lr, lr>;
90  using B_t = A_t;
91  using Bs_t = std::vector<B_t>;
92  using M_t = A_t;
93  using N_t = A_t;
94  using betas_t = Eigen::Matrix<double, 1, -1>;
95  using ms_t = std::chrono::milliseconds;
96 
97  //}
98 
99  public:
100  /* constructor //{ */
108  :
109  m_initialized(false),
110  m_f_z(),
111  m_f_dzdx(),
112  m_min_dtheta(),
113  m_max_its(),
114  m_timeout(),
115  m_debug_nth_it(),
116  m_ALS_theta_set(false),
117  m_last_theta_set(false)
118  {};
119 
135  RHEIV(const f_z_t& f_z, const f_dzdx_t& f_dzdx, const double min_dtheta = 1e-15, const unsigned max_its = 100)
136  :
137  m_initialized(true),
138  m_f_z(f_z),
139  m_f_dzdx(f_dzdx),
140  m_min_dtheta(min_dtheta),
141  m_max_its(max_its),
142  m_timeout(ms_t::zero()),
143  m_debug_nth_it(-1),
144  m_ALS_theta_set(false),
145  m_last_theta_set(false)
146  {};
147 
165  template <typename time_t>
166  RHEIV(const f_z_t& f_z, const f_dzdx_t& f_dzdx, const double min_dtheta = 1e-15, const unsigned max_its = 100, const time_t& timeout = std::chrono::duration_cast<time_t>(ms_t::zero()), const int debug_nth_it = -1)
167  :
168  m_initialized(true),
169  m_f_z(f_z),
170  m_f_dzdx(f_dzdx),
171  m_min_dtheta(min_dtheta),
172  m_max_its(max_its),
173  m_timeout(std::chrono::duration_cast<ms_t>(timeout)),
174  m_debug_nth_it(debug_nth_it),
175  m_ALS_theta_set(false),
176  m_last_theta_set(false)
177  {}
178 
189  RHEIV(const f_z_t& f_z, const dzdx_t& dzdx, const double min_dtheta = 1e-15, const unsigned max_its = 100)
190  :
191  m_initialized(true),
192  m_f_z(f_z),
193  // define a helper function which just returns the constant dzdx
194  m_f_dzdx(
195  {
196  [dzdx](const x_t&)
197  {
198  return dzdx;
199  }
200  }),
201  m_min_dtheta(min_dtheta),
202  m_max_its(max_its),
203  m_timeout(ms_t::zero()),
204  m_debug_nth_it(-1),
205  m_ALS_theta_set(false),
206  m_last_theta_set(false)
207  {};
208 
222  template <typename time_t>
223  RHEIV(const f_z_t& f_z, const dzdx_t& dzdx, const double min_dtheta = 1e-15, const unsigned max_its = 100, const time_t& timeout = std::chrono::duration_cast<time_t>(ms_t::zero()), const int debug_nth_it = -1)
224  :
225  m_initialized(true),
226  m_f_z(f_z),
227  // define a helper function which just returns the constant dzdx
228  m_f_dzdx(
229  {
230  [dzdx](const x_t&)
231  {
232  return dzdx;
233  }
234  }),
235  m_min_dtheta(min_dtheta),
236  m_max_its(max_its),
237  m_timeout(std::chrono::duration_cast<ms_t>(timeout)),
238  m_debug_nth_it(debug_nth_it),
239  m_ALS_theta_set(false),
240  m_last_theta_set(false)
241  {}
242  //}
243 
244  /* fit() method //{ */
258  theta_t fit(const xs_t& xs, const Ps_t& Ps)
259  {
260  assert(m_initialized);
261  assert((size_t)xs.cols() == Ps.size());
262  const std::chrono::system_clock::time_point fit_start = std::chrono::system_clock::now();
263 
264  const zs_t zs = m_f_z(xs);
265  const dzdxs_t dzdxs = precalculate_dxdzs(xs, m_f_dzdx);
266 
267  // Find initial conditions through ALS
268  m_ALS_theta = fit_ALS_impl(zs);
269  m_last_theta = m_ALS_theta;
270  m_ALS_theta_set = m_last_theta_set = true;
271  eta_t eta = m_ALS_theta.template block<lr, 1>(0, 0);
272 
273  for (unsigned it = 0; it < m_max_its; it++)
274  {
275  const std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
276  const auto fit_dur = now - fit_start;
277  if (m_timeout > ms_t::zero() && fit_dur > m_timeout)
278  {
279  if (m_debug_nth_it > 0)
280  std::cerr << "[RHEIV]: Ending at iteration " << it << " (max " << m_max_its << ") - timed out." << std::endl;
281  break;
282  }
283  const theta_t prev_theta = m_last_theta;
284  const auto [M, N, zc] = calc_MN(eta, zs, Ps, dzdxs);
285  eta = calc_min_eigvec(M, N);
286  m_last_theta = calc_theta(eta, zc);
287  const double dtheta = calc_dtheta(prev_theta, m_last_theta);
288  if (m_debug_nth_it > 0 && it % m_debug_nth_it == 0)
289  std::cout << "[RHEIV]: iteration " << it << " (max " << m_max_its << "), dtheta: " << dtheta << " (min " << m_min_dtheta << ") " << std::endl;
290  if (dtheta < m_min_dtheta)
291  break;
292  }
293  return m_last_theta;
294  }
295 
312  template <class T_it1,
313  class T_it2>
314  theta_t fit(const T_it1& xs_begin, const T_it1& xs_end, const T_it2& Ps_begin, const T_it2& Ps_end)
315  {
316  const xs_t xs = cont_to_eigen(xs_begin, xs_end);
317  const Ps_t Ps = cont_to_vector(Ps_begin, Ps_end);
318  const theta_t ret = fit(xs, Ps);
319  return ret;
320  }
321  //}
322 
323  /* fit_ALS() method //{ */
335  theta_t fit_ALS(const xs_t& xs)
336  {
337  assert(m_initialized);
338 
339  const zs_t zs = m_f_z(xs);
340  m_ALS_theta = fit_ALS_impl(zs);
341  m_ALS_theta_set = true;
342  return m_ALS_theta;
343  }
344  //}
345 
346  /* get_last_estimate() method //{ */
359  {
360  assert(m_last_theta_set);
361  return m_last_theta;
362  }
363  //}
364 
365  /* get_ALS_estimate() method //{ */
378  {
379  assert(m_ALS_theta_set);
380  return m_ALS_theta;
381  }
382  //}
383 
384  private:
385  bool m_initialized;
386 
387  private:
388  f_z_t m_f_z;
389  f_dzdx_t m_f_dzdx;
390  double m_min_dtheta;
391  unsigned m_max_its;
392  ms_t m_timeout;
393  int m_debug_nth_it;
394 
395  private:
396  bool m_ALS_theta_set;
397  theta_t m_ALS_theta;
398  bool m_last_theta_set;
399  theta_t m_last_theta;
400 
401  private:
402  /* calc_MN() method //{ */
403  std::tuple<M_t, N_t, z_t> calc_MN(const eta_t& eta, const zs_t& zs, const Ps_t& Ps, const dzdxs_t& dzdxs) const
404  {
405  const int n = zs.cols();
406 
407  const Bs_t Bs = calc_Bs(Ps, dzdxs);
408  const betas_t betas = calc_betas(eta, Bs);
409  const auto [zrs, zc] = reduce_zs(zs, betas);
410 
411  M_t M = M_t::Zero();
412  N_t N = N_t::Zero();
413  for (int it = 0; it < n; it++)
414  {
415  const double beta = betas(it);
416  const z_t& zr = zrs.col(it);
417  const B_t& B = Bs.at(it);
418  const A_t A = calc_A(zr);
419  M += A*beta;
420  N += (eta.transpose()*A*eta)*B*(beta*beta);
421  }
422  return {M, N, zc};
423  }
424  //}
425 
426  /* calc_Bs() method //{ */
427  Bs_t calc_Bs(const Ps_t& Ps, const dzdxs_t& dzdxs) const
428  {
429  const int n = Ps.size();
430  Bs_t Bs;
431  Bs.reserve(n);
432  for (int it = 0; it < n; it++)
433  {
434  const P_t& P = Ps.at(it);
435  const dzdx_t& dzdx = dzdxs.at(it);
436  const B_t B = dzdx*P*dzdx.transpose();
437  Bs.push_back(B);
438  }
439  return Bs;
440  }
441  //}
442 
443  /* calc_betas() method //{ */
444  betas_t calc_betas(const eta_t& eta, const Bs_t& Bs) const
445  {
446  const int n = Bs.size();
447  betas_t betas(n);
448  for (int it = 0; it < n; it++)
449  {
450  const B_t& B = Bs.at(it);
451  const double beta = 1.0/(eta.transpose()*B*eta);
452  betas(it) = beta;
453  }
454  return betas;
455  }
456  //}
457 
458  /* calc_A() method //{ */
459  A_t calc_A(const z_t& z) const
460  {
461  return z*z.transpose();
462  }
463  //}
464 
465  /* calc_dtheta() method //{ */
466  double calc_dtheta(const theta_t& th1, const theta_t& th2) const
467  {
468  return std::min( (th1 - th2).norm(), (th1 + th2).norm() );
469  }
470  //}
471 
472  /* calc_theta() method //{ */
473  theta_t calc_theta(const eta_t& eta, const z_t& zc) const
474  {
475  const double alpha = -zc.transpose()*eta;
476  const theta_t theta( (theta_t() << eta, alpha).finished().normalized() );
477  return theta;
478  }
479  //}
480 
481  /* calc_min_eigvec() method //{ */
482  eta_t calc_min_eigvec(const M_t& M, const N_t& N) const
483  {
484  const Eigen::GeneralizedSelfAdjointEigenSolver<M_t> es(M, N);
485  if (es.info() != Eigen::Success)
486  throw eigenvector_exception();
487  // eigenvalues are sorted in increasing order when using the GeneralizedSelfAdjointEigenSolver as per Eigen documentation
488  const eta_t evec = es.eigenvectors().col(0).normalized(); // altough the Eigen documentation states that this vector should already be normalized, it isn't!!
489  return evec;
490  }
491  //}
492 
493  /* calc_min_eigvec() method //{ */
494  eta_t calc_min_eigvec(const M_t& M) const
495  {
496  const Eigen::SelfAdjointEigenSolver<M_t> es(M);
497  if (es.info() != Eigen::Success)
498  throw eigenvector_exception();
499  // eigenvalues are sorted in increasing order when using the SelfAdjointEigenSolver as per Eigen documentation
500  const eta_t evec = es.eigenvectors().col(0).normalized(); // altough the Eigen documentation states that this vector should already be normalized, it isn't!!
501  return evec;
502  }
503  //}
504 
505  /* precalculate_dxdzs() method //{ */
506  dzdxs_t precalculate_dxdzs(const xs_t& xs, const f_dzdx_t& f_dzdx)
507  {
508  const int n = xs.cols();
509  dzdxs_t ret;
510  ret.reserve(n);
511  for (int it = 0; it < n; it++)
512  {
513  const x_t& x = xs.col(it);
514  ret.push_back(f_dzdx(x));
515  }
516  return ret;
517  }
518  //}
519 
520  /* calc_centroid() method //{ */
521  z_t calc_centroid(const zs_t& zs, const betas_t& betas) const
522  {
523  const z_t zc = (zs.array().rowwise() * betas.array()).rowwise().sum()/betas.sum();
524  return zc;
525  }
526  //}
527 
528  /* reduce_zs() method //{ */
529  std::pair<zs_t, z_t> reduce_zs(const zs_t& zs, const betas_t& betas) const
530  {
531  const z_t zc = calc_centroid(zs, betas);
532  const zs_t zrs = zs.colwise() - zc;
533  return {zrs, zc};
534  }
535  //}
536 
537  /* cont_to_eigen() method //{ */
538  template <typename T_it>
539  xs_t cont_to_eigen(const T_it& begin, const T_it& end)
540  {
541  const auto n = end-begin;
542  xs_t ret(n_states, n);
543  size_t i = 0;
544  for (T_it it = begin; it != end; it++)
545  {
546  ret.template block<n_states, 1>(0, i) = *it;
547  i++;
548  }
549  return ret;
550  }
551  //}
552 
553  /* cont_to_vector() method //{ */
554  template <typename T_it>
555  Ps_t cont_to_vector(const T_it& begin, const T_it& end)
556  {
557  const auto n = end-begin;
558  Ps_t ret(n);
559  size_t i = 0;
560  for (T_it it = begin; it != end; it++)
561  {
562  ret.at(i) = *it;
563  i++;
564  }
565  return ret;
566  }
567  //}
568 
569 
570 // --------------------------------------------------------------
571 // | Algebraic Least Squares-related methods |
572 // --------------------------------------------------------------
573 
574  z_t calc_centroid(const zs_t& zs) const
575  {
576  return zs.rowwise().mean();
577  }
578 
579  std::pair<zs_t, z_t> reduce_zs(const zs_t& zs) const
580  {
581  const z_t zc = calc_centroid(zs);
582  const zs_t zrs = zs.colwise() - zc;
583  return {zrs, zc};
584  }
585 
586  theta_t fit_ALS_impl(const zs_t& zs) const
587  {
588  const auto [zrs, zc] = reduce_zs(zs);
589  M_t M = M_t::Zero();
590  for (int it = 0; it < zrs.cols(); it++)
591  {
592  const z_t& z = zrs.col(it);
593  const A_t A = calc_A(z);
594  M += A;
595  }
596  const eta_t eta = calc_min_eigvec(M);
597  const theta_t theta = calc_theta(eta, zc);
598  return theta;
599  }
600 
601  };
602  //}
603 
604 }
605 
606 #endif // HEIV_H
mrs_lib::RHEIV::dzdxs_t
std::vector< dzdx_t > dzdxs_t
Contained type for an array of the jacobian matrices.
Definition: rheiv.h:82
mrs_lib::RHEIV::f_dzdx_t
typename std::function< dzdx_t(const xs_t &)> f_dzdx_t
Function signature of the jacobian .
Definition: rheiv.h:83
mrs_lib::RHEIV::RHEIV
RHEIV(const f_z_t &f_z, const dzdx_t &dzdx, const double min_dtheta=1e-15, const unsigned max_its=100)
A convenience constructor constructor.
Definition: rheiv.h:189
mrs_lib::RHEIV::theta_t
Eigen::Matrix< double, l, 1 > theta_t
Parameter vector type .
Definition: rheiv.h:85
mrs_lib::RHEIV::RHEIV
RHEIV(const f_z_t &f_z, const dzdx_t &dzdx, const double min_dtheta=1e-15, const unsigned max_its=100, const time_t &timeout=std::chrono::duration_cast< time_t >(ms_t::zero()), const int debug_nth_it=-1)
A convenience constructor constructor.
Definition: rheiv.h:223
mrs_lib::RHEIV::lr
static const int lr
Length of the reduced parameter vector .
Definition: rheiv.h:69
mrs_lib::RHEIV::get_last_estimate
theta_t get_last_estimate() const
Returns the last valid estimate of .
Definition: rheiv.h:358
mrs_lib::RHEIV::fit_ALS
theta_t fit_ALS(const xs_t &xs)
Fit the defined model to the provided data using Algebraic Least Squares (not RHEIV).
Definition: rheiv.h:335
mrs_lib::RHEIV::u_t
Eigen::Matrix< double, l, 1 > u_t
Transformed input vector type .
Definition: rheiv.h:73
mrs_lib::RHEIV
Implementation of the Reduced Heteroscedastic Errors In Variables surface fitting algorithm .
Definition: rheiv.h:61
mrs_lib::RHEIV::RHEIV
RHEIV()
Convenience default constructor.
Definition: rheiv.h:107
mrs_lib::RHEIV::fit
theta_t fit(const xs_t &xs, const Ps_t &Ps)
Fit the defined model to the provided data.
Definition: rheiv.h:258
mrs_lib::RHEIV::fit
theta_t fit(const T_it1 &xs_begin, const T_it1 &xs_end, const T_it2 &Ps_begin, const T_it2 &Ps_end)
Fit the defined model to the provided data.
Definition: rheiv.h:314
mrs_lib::RHEIV::l
static const int l
Length of the parameter vector .
Definition: rheiv.h:68
mrs_lib::RHEIV::RHEIV
RHEIV(const f_z_t &f_z, const f_dzdx_t &f_dzdx, const double min_dtheta=1e-15, const unsigned max_its=100)
The main constructor.
Definition: rheiv.h:135
mrs_lib::RHEIV::get_ALS_estimate
theta_t get_ALS_estimate() const
Returns the Algebraic Least Squares estimate of .
Definition: rheiv.h:377
mrs_lib::RHEIV::k
static const int k
Length of the state vector x.
Definition: rheiv.h:67
mrs_lib::RHEIV::x_t
Eigen::Matrix< double, k, 1 > x_t
Input vector type .
Definition: rheiv.h:71
mrs_lib
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition: attitude_converter.h:29
mrs_lib::RHEIV::RHEIV
RHEIV(const f_z_t &f_z, const f_dzdx_t &f_dzdx, const double min_dtheta=1e-15, const unsigned max_its=100, const time_t &timeout=std::chrono::duration_cast< time_t >(ms_t::zero()), const int debug_nth_it=-1)
The main constructor.
Definition: rheiv.h:166
mrs_lib::RHEIV::z_t
Eigen::Matrix< double, lr, 1 > z_t
Type of a reduced transformed input vector .
Definition: rheiv.h:77
mrs_lib::RHEIV::f_z_t
typename std::function< zs_t(const xs_t &)> f_z_t
Function signature of the mapping function.
Definition: rheiv.h:79
mrs_lib::RHEIV::eta_t
z_t eta_t
Reduced parameter vector type .
Definition: rheiv.h:86
mrs_lib::RHEIV::Ps_t
std::vector< P_t > Ps_t
Container type for covariances P of the input data array.
Definition: rheiv.h:75
mrs_lib::RHEIV::xs_t
Eigen::Matrix< double, k, -1 > xs_t
Container type for the input data array.
Definition: rheiv.h:72
mrs_lib::eigenvector_exception
This exception may be thrown when solving the generalized eigenvalue problem with the M and N matrice...
Definition: rheiv.h:24
mrs_lib::RHEIV::zs_t
Eigen::Matrix< double, lr, -1 > zs_t
Container type for an array of the reduced transformed input vectors z.
Definition: rheiv.h:78
mrs_lib::RHEIV::P_t
Eigen::Matrix< double, k, k > P_t
Covariance type of the input vector .
Definition: rheiv.h:74
mrs_lib::RHEIV::dzdx_t
Eigen::Matrix< double, lr, k > dzdx_t
Type of the jacobian matrix , evaluated at .
Definition: rheiv.h:81
mrs_lib::eigenvector_exception::what
virtual const char * what() const override
Returns the error message, describing what caused the exception.
Definition: rheiv.h:31