hops
GaussianProcess.hpp
Go to the documentation of this file.
1 #ifndef HOPS_GAUSSIANPROCESS_HPP
2 #define HOPS_GAUSSIANPROCESS_HPP
3 
5 
6 #include <Eigen/Core>
7 #include <Eigen/Cholesky>
8 #include <Eigen/SVD>
9 
10 #include <random>
11 #include <vector>
12 #include <cmath>
13 
14 #include <iostream>
15 
16 namespace hops {
17  namespace internal{
18  template<typename MatrixType>
19  MatrixType append(const MatrixType& rhs, const MatrixType& lhs) {
20  MatrixType newRhs(rhs.rows() + lhs.rows(), lhs.cols());
21  if (rhs.size() > 0) {
22  newRhs << rhs, lhs;
23  } else {
24  newRhs << lhs;
25  }
26  return newRhs;
27  }
28 
29  template<typename MatrixType, typename VectorType>
30  MatrixType append(const MatrixType& rhs, const VectorType& lhs) {
31  MatrixType newRhs(rhs.rows() + 1, lhs.cols());
32  if (rhs.size() > 0) {
33  newRhs << rhs, lhs.transpose();
34  } else {
35  newRhs << lhs.transpose();
36  }
37  return newRhs;
38  }
39 
40  template<typename VectorType>
41  VectorType append(const VectorType& rhs, double lhs) {
42  VectorType newRhs(rhs.rows() + 1);
43  if (rhs.size() > 0) {
44  newRhs << rhs, lhs;
45  } else {
46  newRhs << lhs;
47  }
48  return newRhs;
49  }
50  }
51 
52  template<typename MatrixType, typename VectorType, typename Kernel>
54  public:
55  GaussianProcess (Kernel kernel, double constantPriorMean = 0) :
56  kernel(kernel), isNewObservations(true) {
57  priorMeanFunction = [=](VectorType) -> double { return constantPriorMean; };
58  }
59 
60  GaussianProcess (Kernel kernel, std::function<double (VectorType)> priorMeanFunction) :
61  priorMeanFunction(priorMeanFunction),
62  kernel(kernel),
63  isNewObservations(true) {
64  //
65  }
66 
68  return GaussianProcess<MatrixType, VectorType, Kernel>(this->kernel, this->priorMeanFunction);
69  }
70 
73  gp.storedInputs = storedInputs;
74  gp.inputPriorMean = inputPriorMean;
75  //gp.inputAggregatedErrors = inputAggregatedErrors;
76  gp.observationInputCovariance = observationInputCovariance;
77  gp.inputCovariance = inputCovariance;
78 
79  gp.posteriorMean = posteriorMean;
80  gp.posteriorCovariance = posteriorCovariance;
81  gp.sqrtPosteriorCovariance = sqrtPosteriorCovariance;
82 
83  gp.isNewObservations = isNewObservations;
84  gp.observedCovariance = observedCovariance;
85  gp.sqrtObservedCovariance = sqrtObservedCovariance;
86 
87  gp.observedInputs = observedInputs;
88  gp.observedValues = observedValues;
89  gp.observedValueErrors = observedValueErrors;
90  return gp;
91  }
92 
98  void computePosterior(const MatrixType& input) {
99  bool isNewInput = (!storedInputs.size() || input != storedInputs);
100  if (isNewObservations || isNewInput) {
101  observationInputCovariance = kernel(observedInputs, input);
102  //inputAggregatedErrors = aggregateErrors(input);
103 
104  if (isNewInput) {
105  inputCovariance = kernel(input, input);
106  inputPriorMean = priorMean(input);
107 
108  storedInputs = input;
109  }
110 
111  if (isNewObservations) {
112  // compute the cholesky factorization on the new observed covariance
113  sqrtObservedCovariance = observedCovariance.llt().matrixL();
114 
115  assert(sqrtObservedCovariance.isLowerTriangular()
116  && "error computing the cholesky factorization of the observed covariance, check code.");
117 
118  posteriorMean = sqrtObservedCovariance.template triangularView<Eigen::Lower>().solve(observedValues - priorMean(observedInputs));
119  posteriorMean = sqrtObservedCovariance.template triangularView<Eigen::Lower>().transpose().solve(posteriorMean);
120 
121  posteriorCovariance = sqrtObservedCovariance.template triangularView<Eigen::Lower>().solve(observationInputCovariance);
122  posteriorCovariance = sqrtObservedCovariance.template triangularView<Eigen::Lower>().transpose().solve(posteriorCovariance);
123  }
124 
125 //#ifndef NDEBUG
126 // MatrixType invObservedCovariance = observedCovariance.inverse();
127 // MatrixType control = invObservedCovariance * (observedValues - priorMean(observedInputs));
128 // control -= posteriorMean;
129 // assert((control.size() == 0 || control.cwiseAbs().maxCoeff() < 1.e-5) && "computing the inverse for control might have failed.");
130 //#endif
131 
132  posteriorMean = inputPriorMean + observationInputCovariance.transpose() * posteriorMean;
133 
134 //#ifndef NDEBUG
135 // control = invObservedCovariance * observationInputCovariance;
136 // control -= posteriorCovariance;
137 // assert((control.size() == 0 || control.cwiseAbs().maxCoeff() < 1.e-5) && "computing the inverse for control might have failed.");
138 //#endif
139 
140  posteriorCovariance = inputCovariance - observationInputCovariance.transpose() * posteriorCovariance;
141  //posteriorCovariance += inputAggregatedErrors.asDiagonal();
142 
143  Eigen::BDCSVD<MatrixType> solver(MatrixType(posteriorCovariance), Eigen::ComputeFullU);
144  sqrtPosteriorCovariance = solver.matrixU() * solver.singularValues().cwiseSqrt().asDiagonal();
145 
146  isNewObservations = false;
147  }
148  }
149 
154  Eigen::VectorXd sample(const MatrixType& input,
155  hops::RandomNumberGenerator& randomNumberGenerator,
156  size_t& maxElement) {
157  computePosterior(input);
158 
159  VectorType draw(input.rows());
160  auto standardNormal = std::normal_distribution<double>();
161 
162  for (long i = 0; i < input.rows(); ++i) {
163  draw(i) = standardNormal(randomNumberGenerator);
164  assert(!std::isnan(draw(i)));
165  }
166 
167  draw = posteriorMean + sqrtPosteriorCovariance * draw;
168  draw.maxCoeff(&maxElement);
169 
170  return draw;
171  }
172 
173  Eigen::VectorXd sample(hops::RandomNumberGenerator& randomNumberGenerator,
174  size_t& maxElement) {
175  return sample(storedInputs, randomNumberGenerator, maxElement);
176  }
177 
178  Eigen::VectorXd sample(const MatrixType& x,
179  hops::RandomNumberGenerator& randomNumberGenerator) {
180  size_t max;
181  return sample(x, randomNumberGenerator, max);
182  }
183 
184  Eigen::VectorXd sample(hops::RandomNumberGenerator& randomNumberGenerator) {
185  size_t max;
186  return sample(storedInputs, randomNumberGenerator, max);
187  }
188 
199  std::tuple<MatrixType, MatrixType, VectorType> updateObservations(const MatrixType& x,
200  const VectorType& y,
201  const VectorType& error,
202  bool isUnique = false) {
203  assert(x.size() == y.size());
204  assert(y.size() == error.size());
205 
206  // collect indices of observations which should be updated
207  std::vector<long> updateCandidates;
208  std::vector<long> notFound;
209  for (long i = 0; i < x.rows(); ++i) {
210  bool foundInput = false;
211  for (long j = 0; j < observedInputs.rows(); ++j) {
212  if (x.row(i) == observedInputs.row(j)) {
213  updateCandidates.push_back(j);
214  foundInput = true;
215 
216  if (isUnique) {
217  break;
218  }
219  }
220  }
221 
222  // record data which could not be updated, because it was not stored in *this the first place
223  // this data is "returned" in the reference arguments, such that it can be added after this function returns
224  if (!foundInput) {
225  notFound.push_back(i);
226  }
227  }
228 
229  // if any entries were updated, set the isNewObservations flag
230  if (updateCandidates.size() > 0) {
231  isNewObservations = true;
232  }
233 
234  for (size_t i = 0; i < updateCandidates.size(); ++i) {
235  size_t k = updateCandidates[i];
236 
237  // update the data
238  observedValues(k) = y(i);
239  observedValueErrors(k) = error(i);
240 
241  // update the observed covariance
242  //observedCovariance.row(k) = kernel({observedInputs[k]}, observedInputs);
243  //observedCovariance.col(k) = observedCovariance.row(k).transpose();
244  observedCovariance(k, k) = kernel(observedInputs.row(k), observedInputs.row(k))(0, 0) + observedValueErrors(k);
245  //observedCovariance(k, k) += 1.e-5;
246  }
247 
248 //#ifndef NDEBUG
249 // MatrixType control = kernel(observedInputs, observedInputs);
250 // control -= observedCovariance;
251 // assert(control.size() == 0 || control.cwiseAbs().maxCoeff() < 1.e-5);
252 //#endif
253 
254  MatrixType inputsNotFound(notFound.size(), x.cols());
255  VectorType valuesNotFound(notFound.size());
256  VectorType errorsNotFound(notFound.size());
257 
258  for (size_t i = 0; i < notFound.size(); ++i) {
259  long k = notFound[i];
260 
261  inputsNotFound.row(i) = x.row(k);
262  valuesNotFound.row(i) = y.row(k);
263  errorsNotFound.row(i) = error.row(k);
264  }
265 
266  //x = inputsNotFound;
267  //y = valuesNotFound;
268  //error = errorsNotFound;
269  return {inputsNotFound, valuesNotFound, errorsNotFound};
270  }
271 
272  //void updateObservations(MatrixType& x,
273  // VectorType& y,
274  // VectorType& error,
275  // bool isUnique = false) {
276  // std::tie(x, y, error) = updateObservationsConstArgs(x, y, error);
277  //}
278 
279  void updateObservations(const MatrixType& x, const VectorType& y) {
280  updateObservations(x, y, VectorType::Zeros(y.rows()));
281  }
282 
283  //void updateObservation(const VectorType& x, double y, double error = 0) {
284  // updateObservations({x}, {y}, {error});
285  //}
286 
288  assert(x.size() == y.size());
289  assert(y.size() == error.size());
290 
291  isNewObservations = true;
292 
293  auto n = observedValues.size();
294  auto m = x.size();
295 
296  //VectorType newObservedValues = VectorType(n + m);
297  //newObservedValues << observedValues, y;
298  observedValues = internal::append(observedValues, y);
299 
300  //VectorType newObservedValueErrors = VectorType(n + m);
301  //newObservedValueErrors << observedValueErrors, error;
302  observedValueErrors = internal::append(observedValueErrors, error);
303 
304  MatrixType newObservedCovariance = MatrixType::Zero(n + m, n + m);
305 
306  newObservedCovariance.block(0, 0, n, n) = observedCovariance; // hopefully saves some computation time
307  newObservedCovariance.block(0, n, n, m) = kernel(observedInputs, x);
308  newObservedCovariance.block(n, 0, m, n) = newObservedCovariance.block(0, n, n, m).transpose().eval();
309  newObservedCovariance.block(n, n, m, m) = kernel(x, x);
310  //newObservedCovariance.block(n, n, m, m).diagonal().array() += 1.e-5;
311 
312  newObservedCovariance.block(n, n, m, m) += MatrixType(error.asDiagonal());
313 
314  //MatrixType newObservedInputs = MatrixType::Zero(n + m, n + m);
315  //newObservedInputs << observedInputs, x;
316  observedInputs = internal::append(observedInputs, x);
317 
318 #ifndef NDEBUG
319  MatrixType control = kernel(observedInputs, observedInputs);
320  control += Eigen::MatrixXd(observedValueErrors.asDiagonal());
321  //control.diagonal().array() += 1.e-5;
322  control -= newObservedCovariance;
323  assert((control.size() == 0 || control.cwiseAbs().maxCoeff() < 1.e-5));
324 #endif
325 
326  //observedInputs = newObservedInputs;
327  //observedValues = newObservedValues;
328  //observedValueErrors = newObservedValueErrors;
329  observedCovariance = newObservedCovariance;
330  //observedCovariance = observedCovariance;
331  }
332 
333  void addObservations(const MatrixType& x, const VectorType& y) {
334  addObservations(x, y, VectorType::Zeros(y.rows()));
335  }
336 
337  //void addObservation(const VectorType& x, double y, double error = 0) {
338  // addObservations({x}, {y}, {error});
339  //}
340 
342  computePosterior(storedInputs);
343  return posteriorMean;
344  }
345 
347  computePosterior(storedInputs);
348  return posteriorCovariance;
349  }
350 
352  computePosterior(storedInputs);
353  return sqrtPosteriorCovariance;
354  }
355 
356  const MatrixType& getObservedInputs() const { return observedInputs; }
357  const VectorType& getObservedValues() const { return observedValues; }
358  const VectorType& getObservedValueErrors() const { return observedValueErrors; }
359 
360  const MatrixType& getObservedCovariance() const { return observedCovariance; }
361 
363  return priorMeanFunction;
364  }
365 
366  void setKernelSigma(double m_sigma) {
367  double oldSigma = kernel.m_sigma;
368  kernel.m_sigma = m_sigma;
369  observedCovariance -= MatrixType(observedValueErrors.asDiagonal());
370  observedCovariance.array() *= (m_sigma / oldSigma);
371  observedCovariance += MatrixType(observedValueErrors.asDiagonal());
372  }
373 
374  Kernel getKernel() {
375  return kernel;
376  }
377 
378  private:
379  std::function<double (VectorType)> priorMeanFunction;
380  Kernel kernel;
381 
382  MatrixType storedInputs;
383  VectorType inputPriorMean;
384  //VectorType inputAggregatedErrors;
385  MatrixType observationInputCovariance;
386  MatrixType inputCovariance;
387 
388  VectorType posteriorMean;
389  MatrixType posteriorCovariance;
390  MatrixType sqrtPosteriorCovariance;
391 
392  bool isNewObservations;
393  MatrixType observedCovariance;
394  MatrixType sqrtObservedCovariance;
395 
396  MatrixType observedInputs;
397  VectorType observedValues;
398  VectorType observedValueErrors;
399 
400  VectorType priorMean(const MatrixType& x) {
401  VectorType prior(x.rows());
402  for (long i = 0; i < x.rows(); ++i) {
403  prior(i) = priorMeanFunction(x.row(i));
404  }
405  return prior;
406  }
407 
408  //VectorType aggregateErrors(const std::vector<VectorType>& x) {
409  // VectorType errors(x.size());
410  // for (size_t i = 0; i < x.size(); ++i) {
411  // double mean = 0, error = 0;
412  // size_t count = 0;
413  //
414  // for (size_t j = 0; j < observedInputs.size(); ++j) {
415  // if (x[i] == observedInputs[j]) {
416  // ++count;
417  // mean += observedValues(j);
418  // error += std::pow(observedValueErrors(j), 2) + std::pow(observedValues(j), 2);
419  // }
420  // }
421 
422  // if (count > 0) {
423  // mean /= count;
424  // error /= count;
425  // error -= std::pow(mean, 2);
426  // }
427 
428  // assert(error >= 0);
429  // errors(i) = error;
430  // }
431  // return errors;
432  //}
433  };
434 }
435 
436 #endif // HOPS_GAUSSIANPROCESS_HPP
hops::GaussianProcess::getPriorCopy
GaussianProcess getPriorCopy()
Definition: GaussianProcess.hpp:67
hops::GaussianProcess::getKernel
Kernel getKernel()
Definition: GaussianProcess.hpp:374
hops::GaussianProcess::getObservedValues
const VectorType & getObservedValues() const
Definition: GaussianProcess.hpp:357
hops::GaussianProcess::getObservedInputs
const MatrixType & getObservedInputs() const
Definition: GaussianProcess.hpp:356
hops::MatrixType
Eigen::MatrixXd MatrixType
Definition: MatrixType.hpp:7
RandomNumberGenerator.hpp
hops::GaussianProcess::getPosteriorMean
const VectorType & getPosteriorMean()
Definition: GaussianProcess.hpp:341
hops::internal::append
MatrixType append(const MatrixType &rhs, const MatrixType &lhs)
Definition: GaussianProcess.hpp:19
hops::GaussianProcess::sample
Eigen::VectorXd sample(hops::RandomNumberGenerator &randomNumberGenerator)
Definition: GaussianProcess.hpp:184
hops::GaussianProcess::updateObservations
std::tuple< MatrixType, MatrixType, VectorType > updateObservations(const MatrixType &x, const VectorType &y, const VectorType &error, bool isUnique=false)
Definition: GaussianProcess.hpp:199
pcg_detail::engine
Definition: pcg_random.hpp:364
hops::GaussianProcess::computePosterior
void computePosterior(const MatrixType &input)
Definition: GaussianProcess.hpp:98
hops::GaussianProcess::addObservations
void addObservations(const MatrixType &x, const VectorType &y)
Definition: GaussianProcess.hpp:333
hops::GaussianProcess::sample
Eigen::VectorXd sample(hops::RandomNumberGenerator &randomNumberGenerator, size_t &maxElement)
Definition: GaussianProcess.hpp:173
hops::GaussianProcess::getSqrtPosteriorCovariance
const MatrixType & getSqrtPosteriorCovariance()
Definition: GaussianProcess.hpp:351
hops::GaussianProcess::getPosteriorCopy
GaussianProcess getPosteriorCopy()
Definition: GaussianProcess.hpp:71
hops::GaussianProcess::sample
Eigen::VectorXd sample(const MatrixType &x, hops::RandomNumberGenerator &randomNumberGenerator)
Definition: GaussianProcess.hpp:178
hops::GaussianProcess::getObservedCovariance
const MatrixType & getObservedCovariance() const
Definition: GaussianProcess.hpp:360
hops::GaussianProcess
Definition: GaussianProcess.hpp:53
hops
Definition: CsvReader.hpp:8
hops::GaussianProcess::getPriorMeanFunction
std::function< double(VectorType)> & getPriorMeanFunction()
Definition: GaussianProcess.hpp:362
H5Easy::detail::error
Exception error(const File &file, const std::string &path, const std::string &message)
Definition: H5Easy_misc.hpp:52
hops::GaussianProcess::GaussianProcess
GaussianProcess(Kernel kernel, double constantPriorMean=0)
Definition: GaussianProcess.hpp:55
hops::GaussianProcess::addObservations
void addObservations(const MatrixType &x, VectorType &y, VectorType &error)
Definition: GaussianProcess.hpp:287
hops::GaussianProcess::GaussianProcess
GaussianProcess(Kernel kernel, std::function< double(VectorType)> priorMeanFunction)
Definition: GaussianProcess.hpp:60
hops::VectorType
Eigen::VectorXd VectorType
Definition: VectorType.hpp:7
function
void function(const HighFive::Object &obj)
Definition: simpleton.cpp:5
hops::GaussianProcess::getPosteriorCovariance
const MatrixType & getPosteriorCovariance()
Definition: GaussianProcess.hpp:346
hops::GaussianProcess::setKernelSigma
void setKernelSigma(double m_sigma)
Definition: GaussianProcess.hpp:366
hops::GaussianProcess::sample
Eigen::VectorXd sample(const MatrixType &input, hops::RandomNumberGenerator &randomNumberGenerator, size_t &maxElement)
Definition: GaussianProcess.hpp:154
hops::GaussianProcess::updateObservations
void updateObservations(const MatrixType &x, const VectorType &y)
Definition: GaussianProcess.hpp:279
hops::GaussianProcess::getObservedValueErrors
const VectorType & getObservedValueErrors() const
Definition: GaussianProcess.hpp:358