time_series.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
25 #include <corbo-core/console.h>
26 #include <corbo-core/time_series.h>
27 
28 #include <cmath>
29 #include <iomanip>
30 
31 namespace corbo {
32 
33 void TimeSeries::setValueDimension(int value_dim)
34 {
35  if (value_dim != _value_dim)
36  {
37  clear();
38  _value_dim = value_dim;
39  }
40 }
41 
42 void TimeSeries::reserve(int time_dim, int value_dim)
43 {
44  _time.reserve(time_dim);
45  _values.reserve(time_dim * value_dim);
46 }
47 
48 bool TimeSeries::add(double time, const std::vector<double>& values)
49 {
50  if (values.empty()) return true;
51  if (_time.empty())
52  _value_dim = values.size(); // if this is the first value, inherit value dimension from input
53  else if (values.size() != _value_dim)
54  {
55  PRINT_ERROR("TimeSeries::add(): dimension mismatch: values.size() must be " << _value_dim);
56  return false;
57  }
58  _time.push_back(time);
59  // just concatenate vectors (since we are storing in column-major)
60  _values.insert(_values.end(), values.begin(), values.end());
61  return true;
62 }
63 
64 bool TimeSeries::add(double time, const Eigen::Ref<const Eigen::VectorXd>& values)
65 {
66  if (_time.empty())
67  _value_dim = values.size(); // if this is the first value, inherit value dimension from input
68  else if (values.size() != _value_dim)
69  {
70  PRINT_ERROR("TimeSeries::add(): dimension mismatch: values.size() must be " << _value_dim);
71  return false;
72  }
73  _time.push_back(time);
74  // just concatenate vectors (since we are storing in column-major)
75  _values.insert(_values.end(), values.data(), values.data() + _value_dim);
76  return true;
77 }
78 
79 bool TimeSeries::set(const Eigen::Ref<const Eigen::VectorXd>& time, const Eigen::Ref<const Eigen::MatrixXd>& values_matrix, double time_from_start)
80 {
81  if (values_matrix.cols() != time.size())
82  {
83  PRINT_ERROR("TimeSeries::set(): time.size() != values_matrix.cols()");
84  clear();
85  return false;
86  }
87  _time_from_start = time_from_start;
88 
89  _value_dim = values_matrix.rows();
90  _time.assign(time.data(), time.data() + time.size());
91  if (values_matrix.IsRowMajor)
92  {
93  // TODO(roesmann) here is place for optimization: avoid default construction in resize:
96  target = values_matrix;
97  }
98  else
99  {
100  const int size = values_matrix.rows() * values_matrix.cols();
101  _values.assign(values_matrix.data(), values_matrix.data() + size);
102  }
103  return true;
104 }
105 
106 bool TimeSeries::set(const std::vector<double>& time, const std::vector<Eigen::VectorXd>& values_vector, double time_from_start)
107 {
108  if (values_vector.size() != time.size())
109  {
110  PRINT_ERROR_NAMED("time.size() != values_vector.size()");
111  clear();
112  return false;
113  }
114 
115  _time_from_start = time_from_start;
116 
117  if (time.empty())
118  {
119  clear();
120  return true; // TODO(roesmann) or better false?
121  }
122 
123  _value_dim = values_vector.front().size();
124 
125  reserve(time.size(), _value_dim);
126 
127  _time = time;
128 
129  for (const Eigen::VectorXd& vec : values_vector)
130  {
131  assert(vec.size() == _value_dim);
132  _values.insert(_values.end(), vec.data(), vec.data() + vec.size());
133  }
134 
135  if (_value_dim * values_vector.size() != _values.size())
136  {
137  PRINT_ERROR_NAMED("Vectors in values_vector must be of equal size. Clearing time series object.");
138  clear();
139  return false;
140  }
141  return true;
142 }
143 
145 {
146  _time.clear();
147  _values.clear();
148  _value_dim = 0;
149 }
150 
151 std::vector<double> TimeSeries::getValues(int time_idx) const
152 {
153  assert(time_idx < _time.size());
154  std::vector<double> temp;
155  const int value_idx_begin = _value_dim * time_idx;
156  const int value_idx_end = value_idx_begin + _value_dim;
157  temp.assign(_values.begin() + value_idx_begin, _values.begin() + value_idx_end);
158  return temp;
159 }
160 
162 {
163  assert(time_idx < _time.size());
164  const int value_idx_begin = _value_dim * time_idx;
165  return Eigen::Map<const Eigen::VectorXd>(_values.data() + value_idx_begin, _value_dim);
166 }
167 
168 bool TimeSeries::getValuesInterpolate(double time, Eigen::Ref<Eigen::VectorXd> values, Interpolation interpolation, Extrapolation extrapolation,
169  double tolerance) const
170 {
171  if (_time.empty()) return false;
172 
173  auto it = std::find_if(_time.begin(), _time.end(), [time](double val) { return val >= time; }); // find next time interval
174  if (it == _time.end())
175  {
176  switch (extrapolation)
177  {
179  {
180  break;
181  }
183  {
184  values = getValuesMap(getTimeDimension() - 1);
185  return true;
186  }
187  default:
188  {
189  PRINT_ERROR("TimeSeries::valuesInterpolate(): desired extrapolation method not implemented.");
190  return false;
191  }
192  }
193  }
194  // interpolate
195  // int n = (int)_time.size();
196  int idx = (int)std::distance(_time.begin(), it);
197  if (idx >= getTimeDimension()) idx = getTimeDimension() - 1; // this might occure with floating point comparison
198  if (std::abs(time - _time[idx]) < tolerance)
199  {
200  // we are close to the next value, in particular idx
201  values = getValuesMap(idx);
202  return true;
203  }
204  if (idx < 1)
205  {
206  PRINT_ERROR("accessing a time idx in the past which is not this time series");
207  }
208  // okay, so now we have the above case val > time -> so idx corresponds to the next sample.
209  double dt = time - _time[idx - 1];
210  PRINT_ERROR_COND_NAMED(dt < 0, "dt < 0 in interpolation. This cannot be correct.");
211 
212  switch (interpolation)
213  {
215  {
216  if (idx < 1) idx = 1;
217  values = getValuesMap(idx - 1); // since dt > 0 -> we use the previous value
218  break;
219  }
221  {
222  if (idx < 1)
223  {
224  values = getValuesMap(0);
225  }
226  else
227  {
228  double dt_data = _time[idx] - _time[idx - 1];
229  double dt_frac = dt / dt_data;
230  values.noalias() = getValuesMap(idx - 1) + dt_frac * (getValuesMap(idx) - getValuesMap(idx - 1));
231  }
232  break;
233  }
234  default:
235  {
236  PRINT_ERROR("TimeSeries::valuesInterpolate(): desired interpolation method not implemented.");
237  return false;
238  }
239  }
240  return true;
241 }
242 
244 
246 {
247  if (mean_values.size() != getValueDimension())
248  {
249  PRINT_ERROR("TimeSeries::computeMeanCwise(): provided mean_values vector does not match value dimension");
250  return;
251  }
252  mean_values = getValuesMatrixView().rowwise().mean();
253 }
254 
255 void TimeSeries::normalize(Normalization norm_method, bool value_cwise)
256 {
257  if (isEmpty()) return;
258 
259  if (value_cwise)
260  {
261  for (int i = 0; i < getValueDimension(); ++i) normalize(norm_method, i);
262  return;
263  }
264 
265  switch (norm_method)
266  {
268  {
269  double first_value = _values.front();
270  if (first_value != 0)
271  getValuesMatrixView() /= first_value;
272  else
274  break;
275  }
277  {
278  double max_value = getValuesMatrixView().lpNorm<Eigen::Infinity>();
279  if (max_value != 0)
280  getValuesMatrixView() /= max_value;
281  else
283  break;
284  }
286  {
287  double max_value = getValuesMatrixView().maxCoeff();
288  if (max_value != 0)
289  getValuesMatrixView() /= max_value;
290  else
292  break;
293  }
294  case Normalization::Mean:
295  {
296  double mean_value = computeMeanOverall();
297  if (mean_value != 0)
298  getValuesMatrixView() /= mean_value;
299  else
301  break;
302  }
303  default:
304  {
305  PRINT_ERROR("TimeSeries::normalize(): selected method not implemented.");
306  break;
307  }
308  }
309 }
310 
311 void TimeSeries::normalize(Normalization norm_method, int value_idx)
312 {
313  if (isEmpty()) return;
314  if (value_idx >= getValueDimension())
315  {
316  PRINT_ERROR("TimeSeries::normalize(): specified value_idx does not match getValueDimension().");
317  return;
318  }
319 
320  switch (norm_method)
321  {
323  {
324  double first_value = getValues(0)[value_idx];
325  if (first_value != 0)
326  getValuesMatrixView().row(value_idx) /= first_value;
327  else
328  getValuesMatrixView().row(value_idx).fill(CORBO_INF_DBL);
329  break;
330  }
332  {
333  double max_value = getValuesMatrixView().row(value_idx).lpNorm<Eigen::Infinity>();
334  if (max_value != 0)
335  getValuesMatrixView().row(value_idx) /= max_value;
336  else
337  getValuesMatrixView().row(value_idx).fill(CORBO_INF_DBL);
338  break;
339  }
341  {
342  double max_value = getValuesMatrixView().row(value_idx).maxCoeff();
343  if (max_value != 0)
344  getValuesMatrixView().row(value_idx) /= max_value;
345  else
346  getValuesMatrixView().row(value_idx).fill(CORBO_INF_DBL);
347  break;
348  }
349  case Normalization::Mean:
350  {
351  double mean_value = getValuesMatrixView().row(value_idx).mean();
352  if (mean_value != 0)
353  getValuesMatrixView().row(value_idx) /= mean_value;
354  else
355  getValuesMatrixView().row(value_idx).fill(CORBO_INF_DBL);
356  break;
357  }
358  default:
359  {
360  PRINT_ERROR("TimeSeries::normalize(): selected method not implemented.");
361  break;
362  }
363  }
364 }
365 
366 #ifdef MESSAGE_SUPPORT
367 bool TimeSeries::toMessage(corbo::messages::TimeSeries& message) const
368 {
369  // we use asserts since the raw-matrix manipulation should be hided in the public api.
370  assert(getValueDimension() * getTimeDimension() == (int)_values.size() && "Dimension mismatch: values raw data does not match rows*cols.");
371 
372  // time
373  google::protobuf::RepeatedField<double> time(_time.begin(), _time.end());
374  message.mutable_time()->Swap(&time);
375 
376  // values
377  message.mutable_values()->set_cols(getTimeDimension());
378  message.mutable_values()->set_rows(_value_dim);
379  message.mutable_values()->set_row_major(false);
380  google::protobuf::RepeatedField<double> values(_values.begin(), _values.end());
381  message.mutable_values()->mutable_data()->Swap(&values);
382 
383  // time from start
384  message.set_time_from_start(_time_from_start);
385 
386  // labels
387  message.clear_value_labels();
388  for (const std::string& label : _value_labels) message.add_value_labels(label);
389  return true;
390 }
391 bool TimeSeries::fromMessage(const corbo::messages::TimeSeries& message, std::stringstream* issues)
392 {
393  // time
394  _time.assign(message.time().begin(), message.time().end());
395 
396  // values
397  _value_dim = message.values().rows();
398  if (_value_dim * getTimeDimension() != (int)message.values().data_size())
399  {
400  PRINT_ERROR("TimeSeries::fromMessage(): Dimension mismatch: values raw data does not match rows*cols: "
401  << message.values().data_size() << "=" << _value_dim << "*" << getTimeDimension());
402  if (issues) *issues << "Dimension mismatch: values raw data does not match rows*cols.\n";
403  clear();
404  return false;
405  }
406  if (message.values().row_major())
407  {
409  MapRowMajor source(message.values().data().data(), _value_dim, getTimeDimension());
410  // TODO(roesmann) here is place for optimization: avoid default construction in resize:
411  _values.resize(_value_dim * getTimeDimension());
412  ValuesMatMap target(_values.data(), _value_dim, getTimeDimension());
413  target = source;
414  }
415  else
416  {
417  _values.assign(message.values().data().begin(), message.values().data().end());
418  }
419 
420  // time from start
421  _time_from_start = message.time_from_start();
422 
423  // labels
424  _value_labels.clear();
425  for (int i = 0; i < message.value_labels_size(); ++i) _value_labels.push_back(message.value_labels(i));
426  return true;
427 }
428 #endif
429 
430 void TimeSeries::print(int precision) const { PRINT_INFO(std::endl << std::fixed << std::setprecision(precision) << *this); }
431 
432 std::ostream& operator<<(std::ostream& out, const TimeSeries& ts)
433 {
434  if (ts.isEmpty())
435  {
436  out << "TimeSeries is empty." << std::endl;
437  return out;
438  }
439  for (int i = 0; i < ts.getTimeDimension(); ++i)
440  {
441  // TODO(roesmann) include std::precision here?
442  out << "time: " << ts.getTime()[i] << "\t values: " << ts.getValuesMap(i).transpose() << std::endl;
443  }
444  return out;
445 }
446 
448 {
449  if (value_dim != _value_dim)
450  {
451  clear();
452  _value_dim = value_dim;
453  }
454 }
455 
457 {
458  if (!ts) return false;
459 
460  if (_ts_sequence.empty())
461  {
462  setValueDimension(ts->getValueDimension());
463  }
464  else if (ts->getValueDimension() != _value_dim)
465  {
466  PRINT_ERROR("TimeSeriesSequence::add(): cannot add TimeSeries since its dimension differs with previously added ones.");
467  return false;
468  }
469  _ts_sequence.push_back(ts);
470  return true;
471 }
472 
474 {
475  _ts_sequence.clear();
476  _value_dim = 0;
477 }
478 
480 {
481  std::sort(_ts_sequence.begin(), _ts_sequence.end(),
482  [](const TimeSeries::Ptr& ts1, const TimeSeries::Ptr& ts2) { return (ts1->getTimeFromStart() < ts2->getTimeFromStart()); });
483 }
484 
485 #ifdef MESSAGE_SUPPORT
486 bool TimeSeriesSequence::toMessage(corbo::messages::TimeSeriesSequence& message) const
487 {
488  // ts sequence
489  message.clear_ts_sequence();
490  for (const TimeSeries::Ptr& ts : _ts_sequence)
491  {
492  ts->toMessage(*message.add_ts_sequence());
493  }
494 
495  // value dimension
496  message.set_value_dimension(_value_dim);
497 
498  return true;
499 }
500 bool TimeSeriesSequence::fromMessage(const corbo::messages::TimeSeriesSequence& message, std::stringstream* issues)
501 {
502  int ret_val = true;
503 
504  clear();
505 
506  // value dimension
507  setValueDimension(message.value_dimension());
508 
509  // ts sequence
510  for (int i = 0; i < message.ts_sequence_size(); ++i)
511  {
512  TimeSeries::Ptr new_ts = std::make_shared<TimeSeries>();
513  new_ts->fromMessage(message.ts_sequence(i));
514  if (new_ts->getValueDimension() != _value_dim)
515  {
516  if (issues)
517  *issues << "Imported TimeSeries object has a value dimension other than specified: " << new_ts->getValueDimension() << "/"
518  << _value_dim << ". Skipping...\n";
519  ret_val = false;
520  continue;
521  }
522  _ts_sequence.push_back(new_ts);
523  }
524  return ret_val;
525 }
526 #endif
527 
528 } // namespace corbo
#define PRINT_ERROR_NAMED(msg)
Definition: console.h:260
std::vector< double > _time
Definition: time_series.h:239
virtual double computeMeanOverall()
Compute and return the mean value of all values among all dimensions.
Time Series (trajectory resp. sequence of values w.r.t. time)
Definition: time_series.h:54
bool set(const Eigen::Ref< const Eigen::VectorXd > &time, const Eigen::Ref< const Eigen::MatrixXd > &values_matrix, double time_from_start=0.0)
Set time vector and values matrix.
Definition: time_series.cpp:79
#define PRINT_ERROR_COND_NAMED(cond, msg)
Definition: console.h:262
return int(ret)+1
A matrix or vector expression mapping an existing array of data.
Definition: Map.h:94
Interpolation
List of available interpolation methods.
Definition: time_series.h:58
double _time_from_start
Speciy an offset for the time sequence _time;.
Definition: time_series.h:241
static constexpr size_t size(Tuple< Args... > &)
Provides access to the number of elements in a tuple as a compile-time constant expression.
virtual bool getValuesInterpolate(double time, Eigen::Ref< Eigen::VectorXd > values, Interpolation interpolation=Interpolation::Linear, Extrapolation extrapolate=Extrapolation::NoExtrapolation, double tolerance=1e-6) const
Retrieve value vector at a desired time stamp (seconds) via interpolation.
int getValueDimension() const
Return dimension of the value vector.
Definition: time_series.h:79
void sortByTimeFromStart()
Ascending sort of the time series objects according to their TimeSeries::timeFromStart() ...
EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const AbsReturnType abs() const
void clear()
Clear time series (except valueLabels())
void reserve(int time_dim, int value_dim)
Allocate memory for a given time and value vector dimension.
Definition: time_series.cpp:42
int getTimeDimension() const
Return dimension of the time vector.
Definition: time_series.h:81
void print(int precision=2) const
Print the current time series content to console (with a desired precision for decimal numbers) ...
virtual void normalize(Normalization norm_method, bool value_cwise=true)
Normalize values according to a specified method.
Normalization
List of available normalizations.
Definition: time_series.h:62
ValuesMatConstMap getValuesMatrixView() const
Read access to the complete values matrix in Eigen matrix format [getValueDimension() x getTimeDimens...
Definition: time_series.h:164
bool add(double time, const std::vector< double > &values)
Add time and value vector pair (STL version)
Definition: time_series.cpp:48
virtual void computeMeanCwise(Eigen::Ref< Eigen::VectorXd > mean_values)
Compute and return the component-wise mean values.
std::vector< double > _values
value x time [column major], column: set of values corresponding to the given time idx ...
Definition: time_series.h:238
constexpr const double CORBO_INF_DBL
Representation for infinity (double version)
void setValueDimension(int value_dim)
Change value dimension (warning: clears existing values)
bool isEmpty() const
Determine if time series is empty.
Definition: time_series.h:83
const std::vector< double > & getTime() const
Read access to the underlying time values [getTimeDimension() x 1].
Definition: time_series.h:169
void clear()
Erase all time series.
friend std::ostream & operator<<(std::ostream &out, const TimeSeries &ts)
Print TimeSeries object.
A matrix or vector expression mapping an existing expression.
Definition: Ref.h:192
bool add(TimeSeries::Ptr ts)
Add shared instance of a time series (without copying)
Extrapolation
List of available interpolation methods.
Definition: time_series.h:60
Eigen::Map< const Eigen::VectorXd > getValuesMap(int time_idx) const
Read access to a value vector at a given time idx (< getTimeDimension) without copying.
void setValueDimension(int value_dim)
Change value dimension (warning: clears existing values)
Definition: time_series.cpp:33
const std::vector< double > & getValues() const
Read access to the complete values matrix in column-major format [getValueDimension() x getTimeDimens...
Definition: time_series.h:160
std::vector< std::string > _value_labels
label values should be empty or valueDimension()
Definition: time_series.h:243
std::shared_ptr< TimeSeries > Ptr
Definition: time_series.h:64
#define PRINT_INFO(msg)
Print msg-stream.
Definition: console.h:117
const int Infinity
Definition: Constants.h:31
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Mon Feb 28 2022 22:07:52