MUQ  0.4.3
StateSpaceGP.cpp
Go to the documentation of this file.
7 
9 
10 #include <Eigen/Dense>
11 #include <Eigen/SparseCore>
12 
13 using namespace muq::Approximation;
14 using namespace muq::Modeling;
15 using namespace muq::Utilities;
16 using namespace muq::Inference;
17 
18 StateSpaceGP::StateSpaceGP(std::shared_ptr<MeanFunctionBase> meanIn,
19  std::shared_ptr<KernelBase> covKernelIn,
20  boost::property_tree::ptree options) : StateSpaceGP(covKernelIn->GetStateSpace(options),
21  meanIn,
22  covKernelIn)
23 {
24 };
25 
26 
27 StateSpaceGP::StateSpaceGP(std::tuple<std::shared_ptr<muq::Modeling::LinearSDE>, std::shared_ptr<muq::Modeling::LinearOperator>, Eigen::MatrixXd> ssInfo,
28  std::shared_ptr<MeanFunctionBase> meanIn,
29  std::shared_ptr<KernelBase> covKernelIn) : GaussianProcess(meanIn, covKernelIn), stateDim(std::get<2>(ssInfo).rows()),
30  sde(std::get<0>(ssInfo)),
31  obsOp(std::get<1>(ssInfo)),
32  L(std::get<2>(ssInfo).selfadjointView<Eigen::Lower>().llt().matrixL()),
33  mean(meanIn),
34  covKernel(covKernelIn)
35 {
36 }
37 
39 {
40  if(hasNewObs)
41  {
42  auto obsComp = [](std::shared_ptr<ObservationInformation> a, std::shared_ptr<ObservationInformation> b) { return a->loc(0) < b->loc(0); };
43  std::sort(observations.begin(), observations.end(), obsComp);
44  hasNewObs = false;
45  }
46 }
47 
48 Eigen::MatrixXd StateSpaceGP::Sample(Eigen::MatrixXd const& times)
49 {
50 
51  // Make space for the simulated GP
52  Eigen::MatrixXd output(obsOp->rows(), times.size());
53 
54  if(observations.size()==0){
55 
56  // Generate sample for initial condition
57  Eigen::VectorXd x = L.triangularView<Eigen::Lower>()*RandomGenerator::GetNormal(L.rows());
58 
59  output.col(0) = obsOp->Apply(x);
60 
61  // Step through the each time and integrate the SDE between times
62  for(int i=0; i<times.size()-1; ++i)
63  {
64  x = sde->EvolveState(x, times(i+1)-times(i));
65  output.col(i+1) = obsOp->Apply(x);
66  }
67 
68  }else{
69 
70  throw muq::NotImplementedError("The Sample function of muq::Approximation::StateSpaceGP does not currently support Gaussian Processes that have been conditioned on data.");
71  }
72 
73  return output;
74 }
75 
76 
77 Eigen::MatrixXd StateSpaceGP::PredictMean(Eigen::MatrixXd const& newPts)
78 {
79  return Predict(newPts, GaussianProcess::NoCov).first;
80 }
81 
82 bool StateSpaceGP::ComputeAQ(double dt)
83 {
84  bool computeAQ = false;
85  if( (sdeA.rows()==0) || (sdeA.cols()==0) || (std::abs(dt-dtAQ)>2.0*std::numeric_limits<double>::epsilon()) ){
86  computeAQ = true;
87  }
88 
89  if(computeAQ){
90  std::tie(sdeA, sdeQ) = sde->Discretize(dt);
91  dtAQ = dt;
92  }
93 
94  return computeAQ;
95 }
96 
97 std::pair<Eigen::MatrixXd, Eigen::MatrixXd> StateSpaceGP::Predict(Eigen::MatrixXd const& times,
98  CovarianceType covType)
99 {
100 
101  if(observations.size()==0)
102  return GaussianProcess::Predict(times, covType);
103 
104 
105  // Make sure the evaluations points are one dimensional
106  if(times.rows() != 1)
107  throw muq::WrongSizeError("In StateSpaceGP::Predict: The StateSpaceGP class only supports 1d fields, but newPts has " + std::to_string(times.rows()) + " dimensions (i.e., rows).");
108 
109  // Make sure the evaluation points are sorted
110  for(int i=1; i<times.cols(); ++i)
111  {
112  if(times(0,i)<=times(0,i-1))
113  throw std::invalid_argument("In StateSpaceGP::Predict: The input points must be monotonically increasing, but index " + std::to_string(i-1) + " has a value of " + std::to_string(times(0,i-1)) + " and index " + std::to_string(i) + " has a value of " + std::to_string(times(1,i)) + ".");
114  }
115 
116  if(covType==GaussianProcess::FullCov)
117  {
118  throw std::invalid_argument("In StateSpaceGP::Predict: The statespace GP does not compute the full covariance.");
119  }
120 
121  std::vector< std::pair<Eigen::VectorXd, Eigen::MatrixXd>> evalDists(times.cols());
122 
123  std::vector< std::pair<Eigen::VectorXd, Eigen::MatrixXd>> obsDists(observations.size());
124  std::vector< std::pair<Eigen::VectorXd, Eigen::MatrixXd>> obsFilterDists(observations.size());
125 
126  std::pair<Eigen::VectorXd, Eigen::MatrixXd>* currDist;
127  double currTime, nextTime;
128 
129  int obsInd = 0;
130  int evalInd = 0;
131 
132  // Is the first time an observation or an evaluation?
133  if(observations.at(0)->loc(0) < times(0)){
134  currTime = observations.at(0)->loc(0);
135 
136  obsDists.at(0).second = L.triangularView<Eigen::Lower>()*L.transpose();
137  obsDists.at(0).first = Eigen::VectorXd::Zero(stateDim);
138 
139  std::shared_ptr<LinearOperator> H = std::make_shared<ProductOperator>(observations.at(0)->H, obsOp);
140  obsFilterDists.at(0) = KalmanFilter::Analyze(obsDists.at(0),
141  H,
142  observations.at(0)->obs - mean->Evaluate(observations.at(0)->loc),
143  observations.at(0)->obsCov);
144 
145  currDist = &obsFilterDists.at(0);
146 
147  obsInd++;
148  }else{
149  currTime = times(0);
150 
151  evalDists.at(0).second = L.triangularView<Eigen::Lower>()*L.transpose();
152  evalDists.at(0).first = Eigen::VectorXd::Zero(stateDim);
153 
154  currDist = &evalDists.at(0);
155  evalInd++;
156  }
157 
158  // Loop through the remaining times
159  for(int i=1; i<times.cols() + observations.size(); ++i)
160  {
161  bool hasObs = false;
162  if(obsInd<observations.size()){
163  if(evalInd>=times.cols()){
164  hasObs = true;
165  }else if(observations.at(obsInd)->loc(0) < times(evalInd)){
166  hasObs = true;
167  }
168  }
169 
170  // Is this time an observation or an evaluation?
171  if(hasObs){
172  ComputeAQ(observations.at(obsInd)->loc(0) - currTime);
173 
174  obsDists.at(obsInd).first = sdeA * currDist->first;
175  obsDists.at(obsInd).second = sdeA * currDist->second * sdeA.transpose() + sdeQ; //= sde->EvolveDistribution(*currDist, observations.at(obsInd)->loc(0) - currTime);
176 
177  currTime = observations.at(obsInd)->loc(0);
178 
179  std::shared_ptr<LinearOperator> H = std::make_shared<ProductOperator>(observations.at(obsInd)->H, obsOp);
180 
181  obsFilterDists.at(obsInd) = KalmanFilter::Analyze(obsDists.at(obsInd),
182  H,
183  observations.at(obsInd)->obs - observations.at(obsInd)->H->Apply(mean->Evaluate(observations.at(obsInd)->loc)),
184  observations.at(obsInd)->obsCov);
185 
186  currDist = &obsFilterDists.at(obsInd);
187 
188  obsInd++;
189 
190  }else{
191 
192  ComputeAQ(times(evalInd) - currTime);
193 
194  evalDists.at(evalInd).first = sdeA * currDist->first;
195  evalDists.at(evalInd).second = sdeA * currDist->second * sdeA.transpose() + sdeQ;
196 
197  currTime = times(evalInd);
198  currDist = &evalDists.at(evalInd);
199 
200  evalInd++;
201  }
202  }
203 
205  // BACKWARD (SMOOTHING) PASS
207 
208  // Find the last evaluation point before (or at the same time) as the last observation
209  obsInd = observations.size()-2;
210  evalInd = times.size()-1;
211  for(evalInd = times.size()-1; evalInd >=0; --evalInd)
212  {
213  if(times(evalInd) <= observations.at(observations.size()-1)->loc(0))
214  break;
215  }
216 
217  if(evalInd>=0)
218  {
219  nextTime = observations.at(observations.size()-1)->loc(0);
220 
221  std::pair<Eigen::VectorXd, Eigen::MatrixXd> filterDist = obsDists.at(observations.size()-1);
222  std::pair<Eigen::VectorXd, Eigen::MatrixXd> smoothDist = obsFilterDists.at(observations.size()-1);
223 
224  while( evalInd>=0 )
225  {
226  // Have we passed any new observations on the way backward?
227  bool hasObs = false;
228  if(obsInd>=0){
229  if(observations.at(obsInd)->loc(0) > times(evalInd) )
230  hasObs = true;
231  }
232 
233  if( hasObs )
234  {
235  // Update the value at the observation point if it's not the last observation
236 
237  ComputeAQ(nextTime - observations.at(obsInd)->loc(0));
238  smoothDist = KalmanSmoother::Analyze(obsFilterDists.at(obsInd), filterDist, smoothDist, sdeA);
239  filterDist.swap( obsDists.at(obsInd) );
240 
241  nextTime = observations.at(obsInd)->loc(0);
242  obsInd--;
243 
244  }else{
245  ComputeAQ(nextTime - times(evalInd));
246 
247  smoothDist = KalmanSmoother::Analyze(evalDists.at(evalInd), filterDist, smoothDist, sdeA);
248 
249  filterDist.swap( evalDists.at(evalInd) );
250  evalDists.at(evalInd) = smoothDist;
251 
252  nextTime = times(evalInd);
253  evalInd--;
254  }
255  }
256  }
257 
259  // GET MEAN AND COVARIANCE FROM SDE SOLUTION
261 
262  // Copy the solution into the output
263  std::pair<Eigen::MatrixXd, Eigen::MatrixXd> output;
264  output.first.resize(coDim, times.cols());
265  for(int i=0; i<times.cols(); ++i)
266  output.first.col(i) = mean->Evaluate(times.col(i)) + obsOp->Apply(evalDists.at(i).first);
267 
268  if(covType==GaussianProcess::BlockCov){
269 
270  output.second.resize(coDim, coDim*times.cols());
271 
272  for(int i=0; i<times.cols(); ++i)
273  output.second.block(0,i*coDim,coDim,coDim) = obsOp->Apply( obsOp->Apply(evalDists.at(i).second).transpose() );
274 
275  }else if(covType==GaussianProcess::DiagonalCov){
276  output.second.resize(coDim, times.cols());
277 
278  for(int i=0; i<times.cols(); ++i)
279  output.second.col(i) = obsOp->Apply( obsOp->Apply(evalDists.at(i).second).transpose() ).diagonal();
280  }
281 
282  return output;
283 }
284 
285 
286 double StateSpaceGP::LogLikelihood(Eigen::MatrixXd const& xs,
287  Eigen::MatrixXd const& vals)
288 {
289  return 0.0;
290 }
291 
292 double StateSpaceGP::MarginalLogLikelihood(Eigen::Ref<Eigen::VectorXd> grad,
293  bool computeGrad)
294 {
295  return 0.0;
296 }
297 
298 
299 
300 
301 
302 void StateSpaceGP::SetObs(std::shared_ptr<muq::Modeling::LinearOperator> newObs)
303 {
304 
305  if(newObs->cols() != obsOp->cols())
306  throw muq::WrongSizeError("In StateSpaceGP::SetObs: The new observation operator has " + std::to_string(newObs->cols()) + " columns, which does not match the system dimension " + std::to_string(obsOp->cols()));
307 
308  obsOp = newObs;
309 }
310 
311 // std::shared_ptr<StateSpaceGP> StateSpaceGP::Concatenate(std::vector<std::shared_ptr<StateSpaceGP>> const& gps)
312 // {
313 // // Build the concatenated SDE
314 // std::vector<std::shared_ptr<muq::Modeling::LinearSDE>> sdes(gps.size());
315 // for(int i=0; i<gps.size(); ++i)
316 // sdes.at(i) = gps.at(i)->GetSDE();
317 
318 // auto sde = LinearSDE::Concatenate(sdes);
319 
320 // // Build a concatenated observation operator
321 // std::vector<std::shared_ptr<muq::Modeling::LinearOperator>> obsOps(gps.size());
322 // for(int i=0; i<gps.size(); ++i)
323 // obsOps.at(i) = gps.at(i)->GetObs();
324 
325 // auto H = std::make_shared<BlockDiagonalOperator>(obsOps);
326 
327 // // Build the concatenated covariance
328 // Eigen::MatrixXd Q = Eigen::MatrixXd::Zero(sde->stateDim, sde->stateDim);
329 // int currRow = 0;
330 // for(int i=0; i<gps.size(); ++i)
331 // {
332 // Q.block(currRow,currRow, gps.at(i)->stateDim, gps.at(i)->stateDim) = gps.at(i)->GetCov();
333 // currRow += gps.at(i)->stateDim;
334 // }
335 
336 // return std::make_shared<StateSpaceGP>(sde, H, Q);
337 // }
virtual std::pair< Eigen::MatrixXd, Eigen::MatrixXd > Predict(Eigen::MatrixXd const &newLocs, CovarianceType covType)
std::vector< std::shared_ptr< ObservationInformation > > observations
virtual Eigen::MatrixXd PredictMean(Eigen::MatrixXd const &newPts) override
virtual std::pair< Eigen::MatrixXd, Eigen::MatrixXd > Predict(Eigen::MatrixXd const &newLocs, CovarianceType covType) override
virtual Eigen::MatrixXd Sample(Eigen::MatrixXd const &times) override
void SetObs(std::shared_ptr< muq::Modeling::LinearOperator > newObs)
std::shared_ptr< MeanFunctionBase > mean
Definition: StateSpaceGP.h:87
std::shared_ptr< muq::Modeling::LinearSDE > sde
Definition: StateSpaceGP.h:79
std::shared_ptr< muq::Modeling::LinearOperator > obsOp
Definition: StateSpaceGP.h:82
virtual double LogLikelihood(Eigen::MatrixXd const &xs, Eigen::MatrixXd const &vals) override
StateSpaceGP(MeanFunctionBase &meanIn, KernelBase &kernelIn, boost::property_tree::ptree options=boost::property_tree::ptree())
Definition: StateSpaceGP.h:25
Class for virtual base functions that are not implemented.
Definition: Exceptions.h:22
Exception to throw when matrices, vectors, or arrays are the wrong size.
Definition: Exceptions.h:58
auto get(const nlohmann::detail::iteration_proxy_value< IteratorType > &i) -> decltype(i.key())
Definition: json.h:3956
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:25172