indii/ml/sde/StochasticAdaptiveRungeKutta.hpp

00001 #ifndef INDII_ML_SDE_STOCHASTICADAPTIVERUNGEKUTTA_HPP
00002 #define INDII_ML_SDE_STOCHASTICADAPTIVERUNGEKUTTA_HPP
00003 
00004 #include "StochasticDifferentialModel.hpp"
00005 #include "StochasticNumericalSolver.hpp"
00006 
00007 namespace indii {
00008   namespace ml {
00009     namespace sde {
00010     
00011     template <class DT, class DDT>
00012     class StochasticAdaptiveRungeKuttaHelper;
00013     
00014 /**
00015  * Stochastic Adaptive Runge-Kutta method for solving a system of
00016  * stochastic differential equations.
00017  *
00018  * @author Lawrence Murray <lawrence@indii.org>
00019  * @version $Rev: 566 $
00020  * @date $Date: 2008-09-13 22:41:27 +0100 (Sat, 13 Sep 2008) $
00021  *
00022  * @param DT Type of the diffusion matrix.
00023  * @param DDT Type of the diffusion partial derivative matrices.
00024  *
00025  * This class numerically solves StochasticDifferentialModel models
00026  * defining a system of stochastic differential equations using an
00027  * adaptive time step 4th/5th order (~2th/2.5th order for stochastic
00028  * systems) Runge-Kutta-Fehlberg method, as implemented in the
00029  * @ref GSL "GSL".
00030  *
00031  * The general usage idiom is as for
00032  * indii::ml::ode::AdaptiveRungeKutta. The class will automatically
00033  * keep track of Wiener process increments.
00034  *
00035  * @section StochasticAdaptiveRungeKutta_references References
00036  *
00037  * @anchor Wilkie2004
00038  * Wilkie, J. Numerical methods for stochastic differential
00039  * equations. <i>Physical Review E</i>, <b>2004</b>, <i>70</i>
00040  *
00041  * @anchor Sarkka2006
00042  * Särkkä, S. Recursive Bayesian Inference on Stochastic Differential
00043  * Equations. PhD thesis, <i>Helsinki University of Technology</i>,
00044  * <b>2006</b>.
00045  *
00046  * @todo This class is tightly coupled with the GSL and would benefit from
00047  * greater independence.
00048  */
00049 template <class DT = indii::ml::aux::matrix,
00050     class DDT = indii::ml::aux::zero_matrix>
00051 class StochasticAdaptiveRungeKutta :
00052     public indii::ml::sde::StochasticNumericalSolver {
00053     
00054     friend class indii::ml::sde::StochasticAdaptiveRungeKuttaHelper<DT,DDT>;
00055     
00056 public:
00057   /**
00058    * Constructor.
00059    *
00060    * @param model Model to estimate.
00061    *
00062    * The time is initialised to zero, but the state is uninitialised
00063    * and should be set with setVariable() or setState().
00064    */
00065   StochasticAdaptiveRungeKutta(StochasticDifferentialModel<DT,DDT>* model);
00066 
00067   /**
00068    * Constructor.
00069    *
00070    * @param model Model to estimate.
00071    * @param y0 Initial state.
00072    *
00073    * The time is initialised to zero and the state to that given.
00074    */
00075   StochasticAdaptiveRungeKutta(StochasticDifferentialModel<DT,DDT>* model,
00076       const indii::ml::aux::vector& y0);
00077 
00078   /**
00079    * Destructor.
00080    */
00081   virtual ~StochasticAdaptiveRungeKutta();
00082 
00083   virtual double step(double upper);
00084 
00085   virtual double stepBack(double lower);
00086 
00087   virtual int calculateDerivativesForward(double t, const double y[],
00088       double dydt[]);
00089 
00090   virtual int calculateDerivativesBackward(double t, const double y[],
00091       double dydt[]);
00092 
00093 private:
00094   /**
00095    * Model.
00096    */
00097   StochasticDifferentialModel<DT,DDT>* model;
00098 
00099   /**
00100    * Drift.
00101    */
00102   indii::ml::aux::vector a;
00103   
00104   /**
00105    * Diffusion.
00106    */
00107   DT B;
00108   
00109   /**
00110    * Diffusion partial derivatives.
00111    */
00112   std::vector<DDT> dBdy;
00113 
00114   /**
00115    * GSL ordinary differential equations step type.
00116    */
00117   static const gsl_odeiv_step_type* gslStepType;
00118 
00119 };
00120 
00121 /* Omit these from documentation */
00122 /// @cond STOCHASTICADAPTIVERUNGEKUTTAHELPER
00123 
00124 /**
00125  * Helper class to facilitate specialisation for
00126  * DDT=indii::ml::aux::zero_matrix
00127  */
00128 template <class DT, class DDT>
00129 class StochasticAdaptiveRungeKuttaHelper {
00130 public:
00131   static int calculateDerivativesForward(double t, const double y[],
00132       double dydt[], StochasticAdaptiveRungeKutta<DT,DDT>* sde);
00133 };
00134 
00135 /**
00136  * Partial specialisation for zero matrix diffusion derivatives (additive
00137  * noise), completely bypassing calculateDiffusionDerivatives() function
00138  * calls.
00139  */
00140 template <class DT>
00141 class StochasticAdaptiveRungeKuttaHelper<DT,indii::ml::aux::zero_matrix> {
00142 public:
00143   static int calculateDerivativesForward(double t, const double y[],
00144       double dydt[],
00145       StochasticAdaptiveRungeKutta<DT,indii::ml::aux::zero_matrix>* sde);
00146 };
00147 
00148 /// @endcond
00149 
00150     }
00151   }
00152 }
00153 
00154 #include "boost/numeric/ublas/operation.hpp"
00155 #include "boost/numeric/ublas/operation_sparse.hpp"
00156 
00157 #include <assert.h>
00158 
00159 #include <gsl/gsl_errno.h>
00160 
00161 template <class DT, class DDT>
00162 const gsl_odeiv_step_type* indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::gslStepType
00163     = gsl_odeiv_step_rkf45;
00164 
00165 template <class DT, class DDT>
00166 indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::StochasticAdaptiveRungeKutta(
00167     StochasticDifferentialModel<DT,DDT>* model) :
00168     StochasticNumericalSolver(model->getDimensions(),
00169     model->getNoiseDimensions()), model(model), a(model->getDimensions()),
00170     B(model->getDimensions(), model->getNoiseDimensions()) {
00171   unsigned int i;
00172   DDT dBdyi(model->getDimensions(), model->getNoiseDimensions());
00173   
00174   a.clear();
00175   B.clear();
00176   //dBdyi.clear();
00177   for (i = 0; i < model->getDimensions(); i++) {
00178     dBdy.push_back(dBdyi);
00179   }
00180   
00181   setStepType(gslStepType);
00182 }
00183 
00184 template <class DT, class DDT>
00185 indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::StochasticAdaptiveRungeKutta(
00186     StochasticDifferentialModel<DT,DDT>* model,
00187     const indii::ml::aux::vector& y0) : StochasticNumericalSolver(y0,
00188     model->getNoiseDimensions()), model(model), a(model->getDimensions()),
00189     B(model->getDimensions(), model->getNoiseDimensions()) {
00190   /* pre-condition */
00191   assert (y0.size() == model->getDimensions());
00192 
00193   unsigned int i;
00194   DDT dBdyi(model->getDimensions(), model->getNoiseDimensions());
00195   
00196   a.clear();
00197   B.clear();
00198   //dBdyi.clear();
00199   for (i = 0; i < model->getDimensions(); i++) {
00200     dBdy.push_back(dBdyi);
00201   }
00202 
00203   setStepType(gslStepType);
00204 }
00205 
00206 template <class DT, class DDT>
00207 indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::~StochasticAdaptiveRungeKutta() {
00208   //
00209 }
00210 
00211 template <class DT, class DDT>
00212 double indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::step(
00213     double upper) {
00214   /* 
00215    * Implementation based on gsl_odeiv_evolve_apply() in 
00216    * ode-initval/evolve.c of the GSL (v1.11).
00217    */
00218 
00219   /* pre-condition */
00220   assert (upper > t);
00221 
00222   double ts, ts_new;
00223   double h, h_new;
00224   bool stepDecreased;
00225   int err;
00226   unsigned int i;
00227 
00228   /* make sure step size won't exceed upper bounds */
00229   if (maxStepSize > 0.0 && stepSize > maxStepSize) {
00230     stepSize = maxStepSize;
00231   }
00232   ts = t + stepSize;
00233   if (ts > upper) {
00234     ts = upper;
00235   }
00236   
00237   /* save initial state */
00238   memcpy(gslEvolve->y0, y, gslEvolve->dimension*sizeof(double));
00239 
00240   /* calculate initial derivative once */
00241   if (gslStep->type->can_use_dydt_in) {
00242     err = GSL_ODEIV_FN_EVAL(&gslForwardSystem, t, y, gslEvolve->dydt_in);
00243     assert (err == GSL_SUCCESS);
00244   }
00245 
00246   do {
00247     sampleNoise(&ts);
00248     h = ts - t;
00249 
00250     /* take proposed step */
00251     if (gslStep->type->can_use_dydt_in) {
00252       err = gsl_odeiv_step_apply(gslStep, t, h, y, gslEvolve->yerr,
00253           gslEvolve->dydt_in, gslEvolve->dydt_out, &gslForwardSystem);
00254     } else {
00255       err = gsl_odeiv_step_apply(gslStep, t, h, y, gslEvolve->yerr,
00256           NULL, gslEvolve->dydt_out, &gslForwardSystem);    
00257     }
00258     assert (err == GSL_SUCCESS);
00259     
00260     gslEvolve->last_step = h;
00261     
00262     /* adjust step size */
00263     /*
00264      * Usually we would call the following to adjust the step size
00265      * appropriately...
00266      */
00267     //err = gsl_odeiv_control_hadjust(gslControl, gslStep, y, gslEvolve->yerr,
00268     //    gslEvolve->dydt_out, &h);
00269     /*
00270      * ...This is translated to a simple function call like that below in
00271      * ode-initval/control.c of the GSL (v1.11). The order of the method
00272      * is passed on in this function call. Because we are working with a
00273      * stochastic system, the order of the method must be halved. We 
00274      * therefore expand out this function call directly here, halving
00275      * the order of the selected method.
00276      *
00277      * 03/08/08: The order is only used in adjusting the step size, so 
00278      * isn't that critical except for performance.
00279      * 05/08/08: If order is odd this rounds down, and in fact rkf45 is 
00280      * given as order 5, not 4... near enough though?
00281      */
00282     err = gslControl->type->hadjust(gslControl->state, gslStep->dimension,
00283         gslStep->type->order(gslStep->state) / 2, y, gslEvolve->yerr,
00284         gslEvolve->dydt_out, &h);
00285     
00286     stepDecreased = false;
00287     if (err == GSL_ODEIV_HADJ_DEC) {
00288       /* numerical checks */
00289       ts_new = t + h;
00290       if (ts_new > t && ts_new < ts) {
00291         h_new = ts_new - t;
00292         if (h_new > 0.0 && h_new < gslEvolve->last_step) {
00293           /* restore previous state and try again */
00294           ts = ts_new;
00295           memcpy(y, gslEvolve->y0, gslEvolve->dimension*sizeof(double));
00296           stepDecreased = true;
00297         } else {
00298           /* not ok, restore last step */
00299           h = gslEvolve->last_step;
00300         }
00301       } else {
00302         /* not ok, restore last step */
00303         h = gslEvolve->last_step;
00304       }
00305     }
00306   } while (stepDecreased); // repeat while step size too large
00307 
00308   /* update proposed step size */
00309   stepSize = h;
00310 
00311   /* update time */
00312   t = ts;
00313 
00314   /* clean up future path */
00315   tf.pop();
00316   dWf.pop();
00317 
00318   /* post-condition */
00319   assert (tf.empty() || t < tf.top());
00320   assert (t <= upper);
00321 
00322   return t;
00323 }
00324 
00325 template <class DT, class DDT>
00326 inline double indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::stepBack(
00327     double lower) {
00328   double t = NumericalSolver::stepBack(lower);
00329 
00330   return t;
00331 }
00332 
00333 template <class DT, class DDT>
00334 inline int indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::calculateDerivativesForward(
00335     double ts, const double y[], double dydt[]) {
00336   return StochasticAdaptiveRungeKuttaHelper<DT,DDT>::calculateDerivativesForward(
00337       ts, y, dydt, this);
00338 }
00339 
00340 template <class DT, class DDT>
00341 inline int indii::ml::sde::StochasticAdaptiveRungeKutta<DT,DDT>::calculateDerivativesBackward(
00342     double t, const double y[], double dydt[]) {
00343   /* convert the proposed step to a future time into a proposed step
00344      to a past time */
00345   int result = calculateDerivativesForward(2.0 * base - t, y, dydt);
00346 
00347   if (result == GSL_SUCCESS) {
00348     /* as we're moving backward in time, negate gradients */
00349     size_t i;
00350     for (i = 0; i < dimensions; i++) {
00351       dydt[i] *= -1.0;
00352     }
00353   }
00354 
00355   return result;
00356 }
00357 
00358 template <class DT>
00359 inline int
00360     indii::ml::sde::StochasticAdaptiveRungeKuttaHelper<DT,indii::ml::aux::zero_matrix>::calculateDerivativesForward(
00361     double ts, const double y[], double dydt[],
00362     StochasticAdaptiveRungeKutta<DT,indii::ml::aux::zero_matrix>* sde) {
00363   namespace aux = indii::ml::aux;
00364   namespace ublas = boost::numeric::ublas;
00365 
00366   StochasticDifferentialModel<DT,aux::zero_matrix>& model = *sde->model;
00367   aux::vector& a = sde->a;
00368   DT& B = sde->B;
00369     
00370   unsigned int i;
00371   const unsigned int N = model.getDimensions();
00372   const unsigned int W = model.getNoiseDimensions();
00373   const double t = sde->getTime();
00374   double delta;
00375   if (sde->tf.empty()) {
00376     delta = 0.0;
00377   } else {
00378     delta = sde->tf.top() - t;
00379   }
00380 
00381   aux::vector x(N);
00382   aux::arrayToVector(y, x);
00383 
00384   model.calculateDrift(ts, x, a);
00385   assert (a.size() == N);
00386 
00387   if (delta > 0.0) {
00388     model.calculateDiffusion(ts, x, B);
00389     assert (B.size1() == N && B.size2() == W);
00390     ublas::axpy_prod(B, sde->dWf.top() / delta, a, false);
00391   }
00392   aux::vectorToArray(a, dydt);
00393 
00394   return GSL_SUCCESS;
00395 }
00396 
00397 template <class DT, class DDT>
00398 inline int
00399     indii::ml::sde::StochasticAdaptiveRungeKuttaHelper<DT,DDT>::calculateDerivativesForward(
00400     double ts, const double y[], double dydt[],
00401     StochasticAdaptiveRungeKutta<DT,DDT>* sde) {
00402   namespace aux = indii::ml::aux;
00403   namespace ublas = boost::numeric::ublas;
00404 
00405   StochasticDifferentialModel<DT,DDT>& model = *sde->model;
00406   aux::vector& a = sde->a;
00407   DT& B = sde->B;
00408   std::vector<DDT>& dBdy = sde->dBdy;
00409 
00410   unsigned int i;
00411   const unsigned int N = model.getDimensions();
00412   const unsigned int W = model.getNoiseDimensions();
00413   const double t = sde->getTime();
00414   double delta;
00415   if (sde->tf.empty()) {
00416     delta = 0.0;
00417   } else {
00418     delta = sde->tf.top() - t;
00419   }
00420 
00421   aux::vector x(N);
00422   aux::arrayToVector(y, x);
00423 
00424   model.calculateDrift(ts, x, a);
00425   assert (a.size() == N);
00426 
00427   if (delta > 0.0) {
00428     model.calculateDiffusion(ts, x, B);
00429     assert (B.size1() == N && B.size2() == W);
00430 
00431     model.calculateDiffusionDerivatives(ts, x, dBdy);
00432     assert (dBdy.size() == 0 || dBdy.size() == N);
00433     #ifdef NDEBUG
00434     if (dBdy.size() == N) {
00435       unsigned int j;
00436       for (j = 0; j < N; j++) {
00437         assert (dBdy[i].size1() == N && dBdy[i].size2() == W);
00438       }
00439     }
00440     #endif
00441 
00442     if (dBdy.size() > 0) {
00443       aux::vector b(N);
00444       b.clear();
00445       for (i = 0; i < N; i++) {
00446         ublas::axpy_prod(dBdy[i], row(B,i), b, false);
00447       }
00448       noalias(a) += 0.5 * b;
00449     }
00450     ublas::axpy_prod(B, sde->dWf.top() / delta, a, false);
00451   }
00452   aux::vectorToArray(a, dydt);
00453 
00454   return GSL_SUCCESS;
00455 }
00456 
00457 #endif
00458 

Generated on Wed Dec 17 15:11:57 2008 for dysii Dynamical Systems Library by  doxygen 1.5.3