mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
repredictor.h
1 #ifndef REPREDICTOR_H
2 #define REPREDICTOR_H
3 
4 #include <Eigen/Dense>
5 #include <boost/circular_buffer.hpp>
6 #include <std_msgs/Time.h>
7 #include <functional>
8 #include <ros/ros.h>
9 #include <mrs_lib/utils.h>
10 
11 namespace mrs_lib
12 {
38  template <class Model, bool disable_reprediction=false>
40  {
41  public:
42  /* states, inputs etc. definitions (typedefs, constants etc) //{ */
43 
44  using x_t = typename Model::x_t;
45  using u_t = typename Model::u_t;
46  using z_t = typename Model::z_t;
47  using P_t = typename Model::P_t;
48  using R_t = typename Model::R_t;
49  using Q_t = typename Model::Q_t;
50  using statecov_t = typename Model::statecov_t;
51  using ModelPtr = typename std::shared_ptr<Model>;
53  //}
54 
55  /* predictTo() method //{ */
66  template<bool check=disable_reprediction>
67  std::enable_if_t<!check, statecov_t> predictTo(const ros::Time& to_stamp)
68  {
69  assert(!m_history.empty());
70  auto hist_it = std::begin(m_history);
71  if (hist_it->is_measurement)
72  {
73  // if the first history point is measurement, don't use it for correction (to prevent it from being used twice)
74  hist_it->is_measurement = false;
75  }
76  // cur_stamp corresponds to the time point of cur_sc estimation
77  auto cur_stamp = hist_it->stamp;
78  // cur_sc is the current state and covariance estimate
79  auto cur_sc = m_sc;
80  do
81  {
82  cur_sc.stamp = hist_it->stamp;
83  // do the correction if current history point is a measurement
84  if ((hist_it->is_measurement))
85  cur_sc = correctFrom(cur_sc, *hist_it);
86 
87  // decide whether to predict to the next history point or straight to the desired stamp already
88  // (whichever comes sooner or directly to the desired stamp if no further history point is available)
89  ros::Time next_stamp = to_stamp;
90  if ((hist_it + 1) != std::end(m_history) && (hist_it + 1)->stamp <= to_stamp)
91  next_stamp = (hist_it + 1)->stamp;
92 
93  // do the prediction
94  cur_sc = predictFrom(cur_sc, *hist_it, cur_stamp, next_stamp);
95  cur_stamp = next_stamp;
96 
97  // increment the history pointer
98  hist_it++;
99  } while (hist_it != std::end(m_history) && hist_it->stamp <= to_stamp);
100  cur_sc.stamp = to_stamp;
101  return cur_sc;
102  }
103 
116  template<bool check=disable_reprediction>
117  std::enable_if_t<check, statecov_t> predictTo(const ros::Time& to_stamp)
118  {
119  assert(!m_history.empty());
120  const auto& info = m_history.front();
121  auto sc = predictFrom(m_sc, info, info.stamp, to_stamp);
122  sc.stamp = to_stamp;
123  return sc;
124  }
125  //}
126 
127  /* addInputChangeWithNoise() method //{ */
140  template<bool check=disable_reprediction>
141  std::enable_if_t<!check> addInputChangeWithNoise(const u_t& u, const Q_t& Q, const ros::Time& stamp, const ModelPtr& model = nullptr)
142  {
143  const info_t info(stamp, u, Q, model);
144  // find the next point in the history buffer
145  const auto next_it = std::lower_bound(std::begin(m_history), std::end(m_history), info, &Repredictor<Model>::earlier);
146  // add the point to the history buffer
147  const auto added = addInfo(info, next_it);
148  // update all measurements following the newly added system input up to the next system input
149  if (added != std::end(m_history))
150  for (auto it = added + 1; it != std::end(m_history) && it->is_measurement; it++)
151  it->updateUsing(info);
152  }
153 
166  template<bool check=disable_reprediction>
167  std::enable_if_t<check> addInputChangeWithNoise(const u_t& u, const Q_t& Q, [[maybe_unused]] const ros::Time& stamp, const ModelPtr& model = nullptr)
168  {
169  if (m_history.empty())
170  m_history.push_back({stamp});
171  m_history.front().u = u;
172  m_history.front().Q = Q;
173  m_history.front().stamp = stamp;
174  m_history.front().predict_model = model;
175  }
176  //}
177 
178  /* addInputChange() method //{ */
190  template<bool check=disable_reprediction>
191  std::enable_if_t<!check> addInputChange(const u_t& u, const ros::Time& stamp, const ModelPtr& model = nullptr)
192  {
193  assert(!m_history.empty());
194  // find the next point in the history buffer
195  const auto next_it = std::lower_bound(std::begin(m_history), std::end(m_history), stamp, &Repredictor<Model>::earlier);
196  // get the previous history point (or the first one to avoid out of bounds)
197  const auto prev_it = next_it == std::begin(m_history) ? next_it : next_it - 1;
198  // initialize a new history info point
199  const info_t info(stamp, u, prev_it->Q, model);
200  // add the point to the history buffer
201  const auto added = addInfo(info, next_it);
202  // update all measurements following the newly added system input up to the next system input
203  if (added != std::end(m_history))
204  for (auto it = added + 1; it != std::end(m_history) && it->is_measurement; it++)
205  it->updateUsing(info);
206  }
207 
219  template<bool check=disable_reprediction>
220  std::enable_if_t<check> addInputChange(const u_t& u, [[maybe_unused]] const ros::Time& stamp, const ModelPtr& model = nullptr)
221  {
222  if (m_history.empty())
223  m_history.push_back({stamp});
224  m_history.front().u = u;
225  m_history.front().stamp = stamp;
226  m_history.front().predict_model = model;
227  }
228  //}
229 
230  /* addProcessNoiseChange() method //{ */
242  template<bool check=disable_reprediction>
243  std::enable_if_t<!check> addProcessNoiseChange(const Q_t& Q, const ros::Time& stamp, const ModelPtr& model = nullptr)
244  {
245  assert(!m_history.empty());
246  // find the next point in the history buffer
247  const auto next_it = std::lower_bound(std::begin(m_history), std::end(m_history), stamp, &Repredictor<Model>::earlier);
248  // get the previous history point (or the first one to avoid out of bounds)
249  const auto prev_it = next_it == std::begin(m_history) ? next_it : next_it - 1;
250  // initialize a new history info point
251  const info_t info(stamp, prev_it->u, Q, model);
252  // add the point to the history buffer
253  const auto added = addInfo(info, next_it);
254  // update all measurements following the newly added system input up to the next system input
255  if (added != std::end(m_history))
256  for (auto it = added + 1; it != std::end(m_history) && it->is_measurement; it++)
257  it->updateUsing(info);
258  }
259 
271  template<bool check=disable_reprediction>
272  std::enable_if_t<check> addProcessNoiseChange(const Q_t& Q, [[maybe_unused]] const ros::Time& stamp, const ModelPtr& model = nullptr)
273  {
274  if (m_history.empty())
275  m_history.push_back({stamp});
276  m_history.front().Q = Q;
277  m_history.front().stamp = stamp;
278  m_history.front().predict_model = model;
279  }
280  //}
281 
282  /* addMeasurement() method //{ */
295  template<bool check=disable_reprediction>
296  std::enable_if_t<!check> addMeasurement(const z_t& z, const R_t& R, const ros::Time& stamp, const ModelPtr& model = nullptr, const double& meas_id = -1)
297  {
298  assert(!m_history.empty());
299  // helper variable for searching of the next point in the history buffer
300  const auto next_it = std::lower_bound(std::begin(m_history), std::end(m_history), stamp, &Repredictor<Model>::earlier);
301  // get the previous history point (or the first one to avoid out of bounds)
302  const auto prev_it = next_it == std::begin(m_history) ? next_it : next_it - 1;
303  // initialize a new history info point
304  const info_t info(stamp, z, R, model, *prev_it, meas_id);
305  // add the point to the history buffer
306  addInfo(info, next_it);
307  }
308 
321  template<bool check=disable_reprediction>
322  std::enable_if_t<check> addMeasurement(const z_t& z, const R_t& R, const ros::Time& stamp, const ModelPtr& model = nullptr, const double& meas_id = -1)
323  {
324  if (m_history.empty())
325  m_history.push_back({stamp});
326  auto& info = m_history.front();
327  const ros::Time to_stamp = stamp > info.stamp ? stamp : info.stamp;
328  const auto sc = predictTo(to_stamp);
329  info.z = z;
330  info.R = R;
331  info.stamp = to_stamp;
332  info.is_measurement = true;
333  info.meas_id = meas_id;
334  info.correct_model = model;
335  m_sc = correctFrom(sc, info);
336  }
337  //}
338 
339  public:
340  /* constructor //{ */
341 
355  Repredictor(const x_t& x0, const P_t& P0, const u_t& u0, const Q_t& Q0, const ros::Time& t0, const ModelPtr& model, const unsigned hist_len)
356  : m_sc{x0, P0}, m_default_model(model), m_history(history_t(hist_len))
357  {
358  assert(hist_len > 0);
359  addInputChangeWithNoise(u0, Q0, t0, model);
360  };
361 
367 
381  Repredictor(const x_t& x0, const P_t& P0, const Q_t& Q0, const ros::Time& t0, const ModelPtr& model, const unsigned hist_len)
382  : m_sc{x0, P0}, m_default_model(model), m_history(history_t(hist_len))
383  {
384  assert(hist_len > 0);
385  const u_t u0{0};
386  addInputChangeWithNoise(u0, Q0, t0, model);
387  };
388  //}
389 
390  protected:
391  // state and covariance corresponding to the oldest element in the history buffer
392  statecov_t m_sc;
393  // default model to use for updates
394  ModelPtr m_default_model;
395 
396  private:
397  /* helper structs and usings //{ */
398 
399  struct info_t
400  {
401  ros::Time stamp;
402 
403  // system input-related information
404  u_t u;
405  Q_t Q;
406  ModelPtr predict_model;
407 
408  // measurement-related information (unused in case is_measurement=false)
409  z_t z;
410  R_t R;
411  ModelPtr correct_model;
412  bool is_measurement;
413  int meas_id;
414 
415  // constructor for a dummy info (for searching in the history)
416  info_t(const ros::Time& stamp) : stamp(stamp), is_measurement(false){};
417 
418  // constructor for a system input
419  info_t(const ros::Time& stamp, const u_t& u, const Q_t& Q, const ModelPtr& model)
420  : stamp(stamp), u(u), Q(Q), predict_model(model), is_measurement(false){};
421 
422  // constructor for a measurement
423  info_t(const ros::Time& stamp, const z_t& z, const R_t& R, const ModelPtr& model, const info_t& prev_info, const int& meas_id)
424  : stamp(stamp), z(z), R(R), correct_model(model), is_measurement(true), meas_id(meas_id)
425  {
426  updateUsing(prev_info);
427  };
428 
429  // copy system input-related information from a previous info
430  void updateUsing(const info_t& info)
431  {
432  u = info.u;
433  Q = info.Q;
434  predict_model = info.predict_model;
435  };
436  };
437 
438 
439  //}
440 
441  protected:
442  using history_t = boost::circular_buffer<info_t>;
443  // the history buffer
444  history_t m_history;
445 
446  // | ---------------- helper debugging methods ---------------- |
447  /* checkMonotonicity() method //{ */
448  template <typename T>
449  bool checkMonotonicity(const T& buf)
450  {
451  if (buf.empty())
452  return true;
453  for (auto it = std::begin(buf) + 1; it != std::end(buf); it++)
454  if (earlier(*it, *(it - 1)))
455  return false;
456  return true;
457  }
458  //}
459 
460  /* printBuffer() method //{ */
461  template <typename T>
462  void printBuffer(const T& buf)
463  {
464  if (buf.empty())
465  return;
466  const auto first_stamp = buf.front().stamp;
467  std::cerr << "first stamp: " << first_stamp << std::endl;
468  for (size_t it = 0; it < buf.size(); it++)
469  {
470  std::cerr << it << ":\t" << buf.at(it).stamp - first_stamp << std::endl;
471  }
472  }
473  //}
474 
475  private:
476  // | -------------- helper implementation methods ------------- |
477 
478  /* addInfo() method //{ */
479  typename history_t::iterator addInfo(const info_t& info, const typename history_t::iterator& next_it)
480  {
481  // check if the new element would be added before the first element of the history buffer and ignore it if so
482  if (next_it == std::begin(m_history) && !m_history.empty())
483  {
484  ROS_WARN_STREAM_THROTTLE(1.0, "[Repredictor]: Added history point is older than the oldest by "
485  << (next_it->stamp - info.stamp).toSec()
486  << "s. Ignoring it! Consider increasing the history buffer size (currently: " << m_history.size() << ")");
487  return std::end(m_history);
488  }
489 
490  // check if adding a new element would throw out the oldest one
491  if (m_history.size() == m_history.capacity())
492  { // if so, first update m_sc
493  // figure out, what will be the oldest element in the buffer after inserting the newly received one
494  auto new_oldest = m_history.front();
495  if (m_history.size() == 1 || (m_history.size() > 1 && info.stamp > m_history.at(0).stamp && info.stamp < m_history.at(1).stamp))
496  {
497  new_oldest = info;
498  } else if (m_history.size() > 1)
499  {
500  new_oldest = m_history.at(1);
501  }
502  // update m_sc to the time of the new oldest element (after inserting the new element)
503  m_sc = predictTo(new_oldest.stamp);
504  // insert the new element into the history buffer, causing the original oldest element to be removed
505  }
506 
507  // add the new point finally
508  const auto ret = m_history.insert(next_it, info);
509  /* debug check //{ */
510 
511 #ifdef REPREDICTOR_DEBUG
512  if (!checkMonotonicity(m_history))
513  {
514  std::cerr << "History buffer is not monotonous after modification!" << std::endl;
515  }
516  std::cerr << "Added info (" << m_history.size() << " total)" << std::endl;
517 #endif
518 
519  //}
520  return ret;
521  }
522  //}
523 
524  /* earlier() method //{ */
525  static bool earlier(const info_t& ob1, const info_t& ob2)
526  {
527  return ob1.stamp < ob2.stamp;
528  }
529  //}
530 
531  /* predictFrom() method //{ */
532  statecov_t predictFrom(const statecov_t& sc, const info_t& inpt, const ros::Time& from_stamp, const ros::Time& to_stamp)
533  {
534  const auto model = inpt.predict_model == nullptr ? m_default_model : inpt.predict_model;
535  const auto dt = (to_stamp - from_stamp).toSec();
536  return model->predict(sc, inpt.u, inpt.Q, dt);
537  }
538  //}
539 
540  /* correctFrom() method //{ */
541  statecov_t correctFrom(const statecov_t& sc, const info_t& meas)
542  {
543  assert(meas.is_measurement);
544  const auto model = meas.correct_model == nullptr ? m_default_model : meas.correct_model;
545  auto sc_tmp = sc;
546  return model->correct(sc_tmp, meas.z, meas.R);
547  }
548  //}
549  };
550 } // namespace mrs_lib
551 
552 
553 #endif // REPREDICTOR_H
mrs_lib::Repredictor::Repredictor
Repredictor()
Empty constructor.
Definition: repredictor.h:366
mrs_lib::Repredictor::addProcessNoiseChange
std::enable_if_t< check > addProcessNoiseChange(const Q_t &Q, [[maybe_unused]] const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:272
mrs_lib::Repredictor< Model >::P_t
typename Model::P_t P_t
State uncertainty covariance matrix type .
Definition: repredictor.h:47
mrs_lib::Repredictor::Repredictor
Repredictor(const x_t &x0, const P_t &P0, const Q_t &Q0, const ros::Time &t0, const ModelPtr &model, const unsigned hist_len)
Variation of the constructor for cases without a system input.
Definition: repredictor.h:381
mrs_lib::Repredictor::predictTo
std::enable_if_t<!check, statecov_t > predictTo(const ros::Time &to_stamp)
Estimates the system state and covariance matrix at the specified time.
Definition: repredictor.h:67
mrs_lib::Repredictor< Model >::R_t
typename Model::R_t R_t
Measurement noise covariance matrix type .
Definition: repredictor.h:48
mrs_lib::Repredictor::addInputChange
std::enable_if_t< check > addInputChange(const u_t &u, [[maybe_unused]] const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:220
mrs_lib::Repredictor::addMeasurement
std::enable_if_t<!check > addMeasurement(const z_t &z, const R_t &R, const ros::Time &stamp, const ModelPtr &model=nullptr, const double &meas_id=-1)
Adds one measurement to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:296
utils.h
Defines various general utility functions.
mrs_lib::Repredictor< Model >::statecov_t
typename Model::statecov_t statecov_t
Helper struct for passing around the state and its covariance in one variable.
Definition: repredictor.h:50
mrs_lib::Repredictor::addMeasurement
std::enable_if_t< check > addMeasurement(const z_t &z, const R_t &R, const ros::Time &stamp, const ModelPtr &model=nullptr, const double &meas_id=-1)
Adds one measurement to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:322
mrs_lib::Repredictor< Model >::Q_t
typename Model::Q_t Q_t
Process noise covariance matrix type .
Definition: repredictor.h:49
mrs_lib::Repredictor< Model >::ModelPtr
typename std::shared_ptr< Model > ModelPtr
Shorthand type for a shared pointer-to-Model.
Definition: repredictor.h:51
mrs_lib
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition: attitude_converter.h:29
mrs_lib::Repredictor
Implementation of the Repredictor for fusing measurements with variable delays.
Definition: repredictor.h:39
mrs_lib::Repredictor::addInputChangeWithNoise
std::enable_if_t< check > addInputChangeWithNoise(const u_t &u, const Q_t &Q, [[maybe_unused]] const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:167
mrs_lib::Repredictor::addProcessNoiseChange
std::enable_if_t<!check > addProcessNoiseChange(const Q_t &Q, const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:243
mrs_lib::Repredictor< Model >::z_t
typename Model::z_t z_t
Measurement vector type .
Definition: repredictor.h:46
mrs_lib::Repredictor::addInputChange
std::enable_if_t<!check > addInputChange(const u_t &u, const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:191
mrs_lib::Repredictor< Model >::x_t
typename Model::x_t x_t
State vector type .
Definition: repredictor.h:44
mrs_lib::Repredictor::addInputChangeWithNoise
std::enable_if_t<!check > addInputChangeWithNoise(const u_t &u, const Q_t &Q, const ros::Time &stamp, const ModelPtr &model=nullptr)
Adds one system input to the history buffer, removing the oldest element in the buffer if it is full.
Definition: repredictor.h:141
mrs_lib::Repredictor::predictTo
std::enable_if_t< check, statecov_t > predictTo(const ros::Time &to_stamp)
Estimates the system state and covariance matrix at the specified time.
Definition: repredictor.h:117
mrs_lib::Repredictor::Repredictor
Repredictor(const x_t &x0, const P_t &P0, const u_t &u0, const Q_t &Q0, const ros::Time &t0, const ModelPtr &model, const unsigned hist_len)
The main constructor.
Definition: repredictor.h:355
mrs_lib::Repredictor< Model >::u_t
typename Model::u_t u_t
Input vector type .
Definition: repredictor.h:45