//============================================================================
//
// Ordinary differential equation solver using the Runge-Kutta method.
// $Id$
// Copyright (C) 2004 Georg Drenkhahn
//
// This file is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License version 2 as published by the
// Free Software Foundation.
//
//============================================================================

#include <cstdlib>
#include <kdebug.h>
#include "rkodesolver.h"

template<typename T>
RkOdeSolver<T>::RkOdeSolver(const T &x,
                            const std::valarray<T>& y,
                            const T &dx,
                            const T &eps)
   : mX(x)
{
   Y(y);
   dX(dx);
   Eps(eps);
}

// virtual dtor
template<typename T>
RkOdeSolver<T>::~RkOdeSolver(void)
{}

// accessors

template<typename T>
void
RkOdeSolver<T>::dX(const T &a)
{
   if (a < 0.0)
   {
      kdDebug() << "RkOdeSolver: dx was negative, made it positive" << endl;
      mStep = -a;
   }
   else if (a == 0.0)
   {
      mStep = 0.001;            // a very arbitrary value
      kdDebug() << "RkOdeSolver: dx == 0, set it to " << mStep << endl;
   }
   else
   {
      mStep = a;
   }
}

template<typename T>
void
RkOdeSolver<T>::Eps(const T &a)
{
   if (a < 0.0)
   {
      kdDebug() << "RkOdeSolver: eps was negative, made it positive" << endl;
      mEps = -a;
   }
   else if (a == 0.0)
   {
      mEps = 1e-5;              // a very arbitrary value
      kdDebug() << "RkOdeSolver: eps == 0, set it to 1e-5" << endl;
   }
   else
   {
      mEps = a;
   }
}

template<typename T>
void
RkOdeSolver<T>::Y(const std::valarray<T> &a)
{
   mY.resize(a.size());
   mY = a;
}


// public member functions

template<typename T>
void
RkOdeSolver<T>::integrate(const T &deltaX)
{
   if (deltaX == 0)
   {
      return;                   // nothing to integrate
   }

   // init dydx if uninitialised
   if (mDydx.size() != mY.size())
   {
      mDydx.resize(mY.size());
      mDydx = f(mX,mY);
   }

   static const unsigned int maxiter = 10000;
   const T x2 = mX + deltaX;

   unsigned int iter;
   for (iter=0;
        iter<maxiter && rkStepCheck(x2-mX) == false;
        ++iter)
   {}

   if (iter>maxiter)
   {
      kdDebug() << "RkOdeSolver: More than " << maxiter
                << " iterations in RkOdeSolver::integrate" << endl;
      // TODO throw exeption here
   }
}


// private member functions

template<typename T>
bool
RkOdeSolver<T>::rkStepCheck(const T& dx_requested)
{
   static const T safety =  0.9;
   static const T pshrnk = -0.25;
   static const T pgrow  = -0.2;

   // reduce step size by no more than a factor 10
   static const T shrinkLimit = 0.1;
   // enlarge step size by no more than a factor 5
   static const T growthLimit = 5.0;
   // errmax_sl = 6561.0
   static const T errmax_sl = pow(shrinkLimit/safety, 1.0/pshrnk);
   // errmax_gl = 1.89e-4
   static const T errmax_gl = pow(growthLimit/safety, 1.0/pgrow);

   static const unsigned int maxiter = 100;

   if (dx_requested == 0)
   {
      return true;              // integration done
   }

   std::valarray<T> ytmp(mY.size());
   std::valarray<T> yerr(mY.size());
   std::valarray<T> t(mY.size());

   bool stepSizeWasMaximal;
   T dx;
   if (std::abs(dx_requested) > mStep)
   {
      stepSizeWasMaximal = true;
      dx = dx_requested>0 ? mStep : -mStep;
   }
   else
   {
      stepSizeWasMaximal = false;
      dx = dx_requested;
   }

   // generic scaling factor
   std::valarray<T> yscal = std::abs(mY) + std::abs(dx*mDydx) + 1e-15;

   unsigned int iter = 0;
   T errmax = 0;
   do
   {
      if (errmax >= 1.0)
      {
         // reduce step size
         dx *= errmax<errmax_sl ? safety * pow(errmax, pshrnk) : shrinkLimit;
         stepSizeWasMaximal = true;
         if (mX == mX + dx)
         {
            // stepsize below numerical resolution
            kdDebug() << "RkOdeSolver: stepsize underflow in rkStepCheck"
                      << endl;
            // TODO throw exeption here
            exit(0);
         }
         // new dx -> update scaling vector
         yscal = std::abs(mY) + std::abs(dx*mDydx) + 1e-15;
      }

      ytmp   = rkStep(dx, yerr); // try to make a step forward
      t      = std::abs(yerr/yscal); // calc the error vector
      errmax = t.max()/mEps;    // calc the rel. maximal error
      ++iter;
   } while (iter < maxiter && errmax >= 1.0);

   if (iter >= maxiter)
   {
      kdDebug() << "RkOdeSolver: too many iterations in rkStepCheck" << endl;
      // TODO throw exeption here
      exit(0);
   }

   if (stepSizeWasMaximal == true)
   {
      // estimate next step size if used step size was maximal
      mStep  =
         std::abs(dx)
         * (errmax>errmax_gl ? safety * pow(errmax, pgrow) : growthLimit);
   }
   mX    += dx;                 // make step forward
   mY     = ytmp;               // save new function values
   mDydx  = f(mX,mY);           // and update derivatives

   return std::abs(dx) < std::abs(dx_requested);
}

template<typename T>
std::valarray<T>
RkOdeSolver<T>::rkStep(const T& dx, std::valarray<T>& yerr) const
{
   static const T
      a2=0.2, a3=0.3, a4=0.6, a5=1.0, a6=0.875,
      b21=0.2,
      b31=3.0/40.0,       b32=9.0/40.0,
      b41=0.3,            b42=-0.9,        b43=1.2,
      b51=-11.0/54.0,     b52=2.5,         b53=-70.0/27.0, b54=35.0/27.0,
      b61=1631.0/55296.0, b62=175.0/512.0, b63=575.0/13824.0,
      b64=44275.0/110592.0, b65=253.0/4096.0,
      c1=37.0/378.0, c3=250.0/621.0, c4=125.0/594.0, c6=512.0/1771.0,
      dc1=c1-2825.0/27648.0,  dc3=c3-18575.0/48384.0,
      dc4=c4-13525.0/55296.0, dc5=-277.0/14336.0, dc6=c6-0.25;

   std::valarray<T> ak2 = f(mX + a2*dx,
                            mY + dx*b21*mDydx);             // 2. step
   std::valarray<T> ak3 = f(mX + a3*dx,
                            mY + dx*(b31*mDydx + b32*ak2)); // 3.step
   std::valarray<T> ak4 = f(mX + a4*dx,
                            mY + dx*(b41*mDydx + b42*ak2
                                     + b43*ak3));           // 4.step
   std::valarray<T> ak5 = f(mX + a5*dx,
                            mY + dx*(b51*mDydx + b52*ak2
                                     + b53*ak3 + b54*ak4)); // 5.step
   std::valarray<T> ak6 = f(mX + a6*dx,
                            mY + dx*(b61*mDydx + b62*ak2
                                     + b63*ak3 + b64*ak4
                                     + b65*ak5));           // 6.step
   yerr      = dx*(dc1*mDydx + dc3*ak3 + dc4*ak4 + dc5*ak5 + dc6*ak6);
   return mY + dx*( c1*mDydx +            c3*ak3 +  c4*ak4 +  c6*ak6);
}


// explicite instantiations
//template RkOdeSolver<long double>;
template class RkOdeSolver<double>;
//template RkOdeSolver<float>;
