MUQ  0.4.3
LinearSDE.cpp
Go to the documentation of this file.
2 
3 #include <random>
4 
5 #include <unsupported/Eigen/MatrixFunctions>
6 
9 
11 
12 using namespace muq::Modeling;
13 using namespace muq::Utilities;
14 
15 LinearSDE::LinearSDE(std::shared_ptr<LinearOperator> Fin,
16  std::shared_ptr<LinearOperator> Lin,
17  Eigen::MatrixXd const& Qin,
18  boost::property_tree::ptree options) : stateDim(Fin->rows()), F(Fin), L(Lin), Q(Qin)
19 {
20  if(F->rows() != F->cols())
21  {
22  throw muq::WrongSizeError("The system transition matrix, F, must be square, but F has " + std::to_string(F->rows()) + " rows and " + std::to_string(F->cols()) + " columns.");
23  }
24 
25  if(L){
26  if(F->rows() != L->rows())
27  {
28  throw muq::WrongSizeError("F and L must have the same number of rows, but F has " + std::to_string(F->rows()) + " rows and L has " + std::to_string(L->rows()) + " rows.");
29  }
30  }
31 
32  // Extract options from the ptree
33  ExtractOptions(options);
34 
35  // Compute the Cholesky decomposition of the white noise process
36  if(L){
37  sqrtQ = Q.llt().matrixL();
38  LQLT = L->Apply( L->Apply(Q).transpose().eval() );
39  LQLT = 0.5*(LQLT + LQLT.transpose()); // <- Make sure LQLT is symmetric
40  }else{
41  LQLT = Eigen::MatrixXd::Zero(F->rows(), F->rows());
42  }
43 };
44 
45 LinearSDE::LinearSDE(std::shared_ptr<LinearOperator> Fin,
46  boost::property_tree::ptree options) : LinearSDE(Fin, std::shared_ptr<LinearOperator>(nullptr), Eigen::MatrixXd(), options){};
47 
48 
49 void LinearSDE::ExtractOptions(boost::property_tree::ptree options)
50 {
51  dt = options.get("SDE.dt",1e-4);
52  if(dt<=0){
53  throw std::runtime_error("In LinearSDE: Step size dt is not positive.");
54  }
55 
56  integratorType = options.get("SDE.Method", "SRK4");
57  if( (integratorType != "SRK4") && (integratorType != "EM")){
58  std::stringstream msg;
59  msg << "In LinearSDE: Invalid integrator type. Given \"" << integratorType << "\" but valid options are {\"SRK4\",\"EM\"}";
60  throw std::runtime_error(msg.str());
61  }
62 }
63 
64 // Eigen::VectorXd LinearSDE::EvolveState(Eigen::VectorXd const& f0,
65 // double T) const
66 // {
67 // if(T<std::numeric_limits<double>::epsilon()){
68 // return f0;
69 // }
70 
71 // Eigen::VectorXd f = f0;
72 
73 // const int numTimes = std::ceil(T/dt);
74 
75 // Eigen::VectorXd z;
76 
77 // // Take all but the last step. The last step might be a partial step
78 // for(int i=0; i<numTimes-1; ++i)
79 // {
80 // if(L){
81 // z = sqrt(dt) * (sqrtQ.triangularView<Eigen::Lower>() * RandomGenerator::GetNormal(sqrtQ.cols()) ).eval();
82 // f += dt*F->Apply(f) + L->Apply( z );
83 // }else{
84 // f += dt*F->Apply(f);
85 // }
86 // }
87 
88 // // Now take the last step
89 // double lastDt = T-(numTimes-1)*dt;
90 // if(L){
91 // z = sqrt(lastDt) * (sqrtQ.triangularView<Eigen::Lower>() * RandomGenerator::GetNormal(sqrtQ.cols())).eval();
92 // f += lastDt*F->Apply(f) + L->Apply( z );
93 // }else{
94 // f += lastDt*F->Apply(f);
95 // }
96 
97 // return f;
98 // }
99 
100 
101 // std::pair<Eigen::VectorXd, Eigen::MatrixXd> LinearSDE::EvolveDistribution(Eigen::VectorXd const& mu0,
102 // Eigen::MatrixXd const& gamma0,
103 // double T) const
104 // {
105 // if(T<std::numeric_limits<double>::epsilon()){
106 // return std::make_pair(mu0,gamma0);
107 // }
108 
109 // Eigen::VectorXd mu = mu0;
110 // Eigen::MatrixXd gamma = gamma0;
111 
112 // const int numTimes = std::ceil(T/dt);
113 
114 // // Fourth-Order Stochastic Runge-Kutta method
115 // if(integratorType=="SRK4"){
116 
117 // Eigen::MatrixXd Fgamma, k1, k2, k3, k4;
118 
119 // // Take all but the last step because the last step might be a partial step.
120 // for(int i=0; i<numTimes-1; ++i)
121 // {
122 // k1 = F->Apply(mu);
123 // k2 = F->Apply(mu + 0.5*dt*k1);
124 // k3 = F->Apply(mu + 0.5*dt*k2);
125 // k4 = F->Apply(mu + dt*k3);
126 // mu = mu + (dt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
127 
128 // Fgamma = F->Apply(gamma);
129 // k1 = Fgamma + Fgamma.transpose() + LQLT;
130 // Fgamma = F->Apply(gamma + 0.5*dt*k1);
131 // k2 = Fgamma + Fgamma.transpose() + LQLT;
132 // Fgamma = F->Apply(gamma + 0.5*dt*k2);
133 // k3 = Fgamma + Fgamma.transpose() + LQLT;
134 // Fgamma = F->Apply(gamma + dt*k3);
135 // k4 = Fgamma + Fgamma.transpose() + LQLT;
136 
137 // gamma = gamma + (dt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
138 // }
139 
140 // // Take the last step
141 // double lastDt = T-(numTimes-1)*dt;
142 
143 // k1 = F->Apply(mu);
144 // k2 = F->Apply(mu + 0.5*lastDt*k1);
145 // k3 = F->Apply(mu + 0.5*lastDt*k2);
146 // k4 = F->Apply(mu + lastDt*k3);
147 // mu = mu + (lastDt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
148 
149 // Fgamma = F->Apply(gamma);
150 // k1 = Fgamma + Fgamma.transpose() + LQLT;
151 // Fgamma = F->Apply(gamma + 0.5*lastDt*k1);
152 // k2 = Fgamma + Fgamma.transpose() + LQLT;
153 // Fgamma = F->Apply(gamma + 0.5*lastDt*k2);
154 // k3 = Fgamma + Fgamma.transpose() + LQLT;
155 // Fgamma = F->Apply(gamma + lastDt*k3);
156 // k4 = Fgamma + Fgamma.transpose() + LQLT;
157 
158 // gamma = gamma + (lastDt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
159 
160 // // Euler-Maruyama method
161 // }else{
162 
163 // // Take all but the last step because the last step might be a partial step.
164 // for(int i=0; i<numTimes-1; ++i){
165 // mu += dt*F->Apply(mu);
166 // gamma += dt*dt*(F->Apply(F->Apply(gamma).transpose().eval()).transpose()) + dt*LQLT;
167 // }
168 
169 // // Take the last step
170 // double lastDt = T-(numTimes-1)*dt;
171 
172 // mu += lastDt*F->Apply(mu);
173 // gamma += lastDt*lastDt*(F->Apply(F->Apply(gamma).transpose().eval()).transpose()) + lastDt*LQLT;
174 
175 // }
176 // return std::make_pair(mu,gamma);
177 // }
178 
179 
180 std::shared_ptr<LinearSDE> LinearSDE::Concatenate(std::vector<std::shared_ptr<LinearSDE>> const& sdes,
181  boost::property_tree::ptree options)
182 {
183 
184  int stateDim = 0;
185  int stochDim = 0;
186  for(auto& sde : sdes){
187  stateDim += sde->stateDim;
188  stochDim += sde->L->cols();
189  }
190 
191  std::vector<std::shared_ptr<LinearOperator>> Fs(sdes.size());
192  std::vector<std::shared_ptr<LinearOperator>> Ls(sdes.size());
193 
194  Eigen::MatrixXd Q = Eigen::MatrixXd::Zero(stochDim, stochDim);
195 
196  int currDim = 0;
197  for(int i=0; i<sdes.size(); ++i){
198  Fs.at(i) = sdes.at(i)->GetF();
199  Ls.at(i) = sdes.at(i)->GetL();
200 
201  Q.block(currDim, currDim, Ls.at(i)->cols(), Ls.at(i)->cols()) = sdes.at(i)->GetQ();
202 
203  currDim += Ls.at(i)->cols();
204  }
205 
206  auto F = std::make_shared<BlockDiagonalOperator>(Fs);
207  auto L = std::make_shared<BlockDiagonalOperator>(Ls);
208 
209  return std::make_shared<LinearSDE>(F,L,Q, options);
210 }
211 
212 
213 std::pair<Eigen::MatrixXd, Eigen::MatrixXd> LinearSDE::Discretize(double deltaT)
214 {
215  Eigen::MatrixXd A = Eigen::MatrixXd::Identity(stateDim,stateDim);
216  Eigen::MatrixXd gamma = Eigen::MatrixXd::Zero(stateDim,stateDim);
217 
218  const int numTimes = std::ceil(deltaT/dt);
219 
220  // Fourth-Order Stochastic Runge-Kutta method
221  if(integratorType=="SRK4"){
222  Eigen::MatrixXd Fgamma, k1, k2, k3, k4;
223 
224  // Take all but the last step because the last step might be a partial step.
225  for(int i=0; i<numTimes-1; ++i)
226  {
227  k1 = F->Apply(A);
228  k2 = F->Apply(A + 0.5*dt*k1);
229  k3 = F->Apply(A + 0.5*dt*k2);
230  k4 = F->Apply(A + dt*k3);
231  A = A + (dt/6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4);
232 
233  Fgamma = F->Apply(gamma);
234  k1 = Fgamma + Fgamma.transpose() + LQLT;
235  Fgamma = F->Apply(gamma + 0.5*dt*k1);
236  k2 = Fgamma + Fgamma.transpose() + LQLT;
237  Fgamma = F->Apply(gamma + 0.5*dt*k2);
238  k3 = Fgamma + Fgamma.transpose() + LQLT;
239  Fgamma = F->Apply(gamma + dt*k3);
240  k4 = Fgamma + Fgamma.transpose() + LQLT;
241 
242  gamma = gamma + (dt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
243  }
244 
245  double lastDt = deltaT-(numTimes-1)*dt;
246  k1 = F->Apply(A);
247  k2 = F->Apply(A + 0.5*lastDt*k1);
248  k3 = F->Apply(A + 0.5*lastDt*k2);
249  k4 = F->Apply(A + lastDt*k3);
250  A = A + (lastDt/6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4);
251 
252  Fgamma = F->Apply(gamma);
253  k1 = Fgamma + Fgamma.transpose() + LQLT;
254  Fgamma = F->Apply(gamma + 0.5*lastDt*k1);
255  k2 = Fgamma + Fgamma.transpose() + LQLT;
256  Fgamma = F->Apply(gamma + 0.5*lastDt*k2);
257  k3 = Fgamma + Fgamma.transpose() + LQLT;
258  Fgamma = F->Apply(gamma + lastDt*k3);
259  k4 = Fgamma + Fgamma.transpose() + LQLT;
260 
261  gamma = gamma + (lastDt/6.0)*(k1 + 2.0*k2 + 2.0*k3 + k4);
262 
263  // Euler-Maruyama
264  }else{
265 
266  // Take all but the last step because the last step might be a partial step.
267  for(int i=0; i<numTimes-1; ++i){
268  A += dt*F->Apply(A);
269  gamma += dt*dt*(F->Apply(F->Apply(gamma).transpose().eval()).transpose()) + dt*LQLT;
270  }
271 
272  // Take the last step
273  double lastDt = deltaT-(numTimes-1)*dt;
274 
275  A += lastDt*F->Apply(A);
276  gamma += lastDt*lastDt*(F->Apply(F->Apply(gamma).transpose().eval()).transpose()) + lastDt*LQLT;
277  }
278 
279  return std::make_pair(A,gamma);
280 
281 }
Generic linear operator base class.
Defines a linear time invariant stochastic differential equation with Gaussian process noise.
Definition: LinearSDE.h:28
Eigen::MatrixXd Q
Definition: LinearSDE.h:255
Eigen::MatrixXd sqrtQ
Definition: LinearSDE.h:256
std::shared_ptr< muq::Modeling::LinearOperator > L
Definition: LinearSDE.h:253
Eigen::MatrixXd LQLT
Definition: LinearSDE.h:261
void ExtractOptions(boost::property_tree::ptree options)
Definition: LinearSDE.cpp:49
std::shared_ptr< muq::Modeling::LinearOperator > F
Definition: LinearSDE.h:252
std::pair< Eigen::MatrixXd, Eigen::MatrixXd > Discretize(double deltaT)
Definition: LinearSDE.cpp:213
static std::shared_ptr< LinearSDE > Concatenate(std::vector< std::shared_ptr< LinearSDE >> const &sdes, boost::property_tree::ptree options=boost::property_tree::ptree())
Combines the states of multiple SDEs into a single monolitch SDE.
Definition: LinearSDE.cpp:180
const int stateDim
The dimension of the state variable .
Definition: LinearSDE.h:240
std::string integratorType
Definition: LinearSDE.h:259
LinearSDE(EigenType1 const &Fin, EigenType2 const &Lin, Eigen::MatrixXd const &Qin, boost::property_tree::ptree options)
Definition: LinearSDE.h:33
Exception to throw when matrices, vectors, or arrays are the wrong size.
Definition: Exceptions.h:58
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