Source code for assimulo.solvers.odepack

#!/usr/bin/env python 
# -*- coding: utf-8 -*-

# Copyright (C) 2010 Modelon AB
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import numpy as N
import scipy.linalg as Sc
import scipy.sparse as sp
import sys
from assimulo.exception import *
from assimulo.ode import *
import logging

from assimulo.explicit_ode import Explicit_ODE

try:
    from assimulo.lib.odepack import dlsodar, dcfode, dintdy
    from assimulo.lib.odepack import set_lsod_common, get_lsod_common
except ImportError:
    sys.stderr.write("Could not find ODEPACK functions.\n")

# Real work array  
class common_like(object):
    def __call__(self):
         return self.__dict__

def g_dummy(t,y):
    return y

def jac_dummy(t,y):
    return N.zeros((len(y),len(y)))

[docs]class LSODAR(Explicit_ODE): """ LOSDAR is a multistep method for solving explicit ordinary differential equations on the form, .. math:: \dot{y} = f(t,y), \quad y(t_0) = y_0. LSODAR automatically switches between using an ADAMS method or an BDF method and is also able to monitor events. LSODAR is part of ODEPACK, http://www.netlib.org/odepack/opkd-sum """ def __init__(self, problem): """ Initiates the solver. Parameters:: problem - The problem to be solved. Should be an instance of the 'Implicit_Problem' class. """ Explicit_ODE.__init__(self, problem) #Calls the base class #Default values self.options["atol"] = 1.0e-6*N.ones(self.problem_info["dim"]) #Absolute tolerance self.options["rtol"] = 1.0e-6 #Relative tolerance self.options["usejac"] = False self.options["maxsteps"] = 100000 self.options["rkstarter"] = 1 self.options["maxordn"] = 12 self.options["maxords"] = 5 self.options["maxh"] = 0. self._leny = len(self.y) #Dimension of the problem self._nordsieck_array = [] self._nordsieck_order = 0 self._nordsieck_time = 0.0 self._nordsieck_h = 0.0 self._update_nordsieck = False # Solver support self.supports["state_events"] = True self.supports["report_continuously"] = True self.supports["interpolated_output"] = True self._RWORK = N.array([0.0]*(22 + self.problem_info["dim"] * max(16,self.problem_info["dim"]+9) + 3*self.problem_info["dimRoot"])) self._IWORK = N.array([0]*(20 + self.problem_info["dim"]))
[docs] def initialize(self): """ Initializes the overall simulation process (called before _simulate) """ #Reset statistics self.statistics.reset() #self._tlist = [] #self._ylist = [] #starts simulation with classical multistep starting procedure # Runge-Kutta starter will be started if desired (see options) # only after an event occured. self._rkstarter_active = False
[docs] def interpolate(self, t): """ Helper method to interpolate the solution at time t using the Nordsieck history array. Wrapper to ODEPACK's subroutine DINTDY. """ #print 'interpolate at t={} and nyh={}'.format(t,self._nyh) if self._update_nordsieck: #Nordsieck start index nordsieck_start_index = 21+3*self.problem_info["dimRoot"] - 1 hu, nqu ,nq ,nyh, nqnyh = get_lsod_common() self._nordsieck_array = \ self._RWORK[nordsieck_start_index:nordsieck_start_index+(nq+1)*nyh].reshape((nyh,-1),order='F') self._nyh = nyh self._update_nordsieck = False dky, iflag = dintdy(t, 0, self._nordsieck_array, self._nyh) if iflag!= 0 and iflag!=-2: raise ODEPACK_Exception("DINTDY returned with iflag={} (see ODEPACK documentation).".format(iflag)) elif iflag==-2: dky=self.y.copy() return dky
[docs] def autostart(self,t,y,sw0=[]): """ autostart determines the initial stepsize for Runge--Kutta solvers """ RWORK=self._RWORK.copy() IWORK=self._IWORK.copy() pr=self.rkstarter+1 tol=self.options["atol"] tolscale=tol[0]**(1./pr) normscale=1. f=self.problem.rhs t0=t tf=RWORK[0] T=abs(tf-t0) direction=N.sign(tf-t0) #Perturb initial condition and compute rough Lipschitz constant cent=Sc.norm(y)/normscale/100. v0=y+cent*N.random.rand(len(y),1) u0prime=f(t,y,sw0) v0prime=f(t,v0,sw0) Lip=Sc.norm(u0prime-v0prime)/Sc.norm(y-v0) h=direction*min(1e-3*T,max(1e-8*T,0.05/Lip)) #step 1: fwd Euler step u1=y+h*u0prime t1=t+h #step 2: fwd Euler step in reverse u1prime=f(t1,u1,sw0) u0comp=u1-h*u1prime #step 3: estimate of local error du=u0comp-y dunorm=Sc.norm(du) errnorm=dunorm/normscale #step 4: new estimate of Lipschitz constant u0comprime=f(t0,u0comp,sw0) L=Sc.norm(u0comprime-u0prime)/dunorm M=N.dot(du,u0comprime-u0prime)/dunorm**2 #step 5: construct a refined starting stepsize theta1=tolscale/N.sqrt(errnorm) theta2=tolscale/abs(h*(L+M/2)) h=h*(theta1+theta2)/2 h=direction*min(3e-3*T,abs(h)) return h
[docs] def integrate_start(self, t, y): """ Helper program for the initialization of LSODAR """ #print ' We have rkstarter {} and rkstarter_active {}'.format(self.rkstarter, self._rkstarter_active) if not(self.rkstarter>1 and self._rkstarter_active): # first call or classical restart after a discontinuity ISTATE=1 # reset work arrays RWORK = 0.0*self._RWORK IWORK = 0.0*self._IWORK else: #self.rkstarter and self._rkstarter_active # RK restart RWORK=self._RWORK.copy() IWORK=self._IWORK.copy() ISTATE=2 # should be 2 dls001=common_like() dlsr01=common_like() # invoke rkstarter # a) get previous stepsize if any hu, nqu ,nq ,nyh, nqnyh = get_lsod_common() #H = hu if hu != 0. else 1.e-4 # this needs some reflections #H =(abs(RWORK[0]-t)*((self.options["rtol"])**(1/(self.rkstarter+1))))/(100*Sc.norm(self.problem.rhs(t,y,self.sw))+10)#if hu != 0. else 1.e-4 H=1e-2 #H=self.autostart(t,y) #H=3*H # b) compute the Nordsieck array and put it into RWORK rkNordsieck = RKStarterNordsieck(self.problem.rhs,H,number_of_steps=self.rkstarter) t,nordsieck = rkNordsieck(t,y,self.sw) nordsieck=nordsieck.T nordsieck_start_index = 21+3*self.problem_info["dimRoot"] - 1 RWORK[nordsieck_start_index:nordsieck_start_index+nordsieck.size] = \ nordsieck.flatten(order='F') # c) compute method coefficients and update the common blocks dls001.init = 1 #dls001.jstart = -1.0 #take the next step with a new value of H,n,meth,.. mf = 20 nq = self.rkstarter dls001.meth = meth = mf // 10 dls001.miter =mf % 10 elco,tesco =dcfode(meth) # dls001.maxord= 5 #max order dls001.nq= self.rkstarter #Next step order dls001.nqu=self.rkstarter #Method order last used (check if this is needed) dls001.meo= meth #meth dls001.nqnyh= nq*self.problem_info["dim"] #nqnyh dls001.conit= 0.5/(nq+2) #conit dls001.el= elco[:,nq-1] # el0 is set internally dls001.hu=H # this sets also hold and h internally dls001.jstart=1 # IWORK[...] = #IWORK[13]=dls001.nqu IWORK[14]=dls001.nq #IWORK[18]=dls001.meth #IWORK[7]=dlsa01.mxordn #max allowed order for Adams methods #IWORK[8]=dlsa01.mxords #max allowed order for BDF IWORK[19]=meth #the current method indicator #RWORK[...] dls001.tn=t RWORK[12]=t RWORK[10]=hu #step-size used successfully RWORK[11]=H #step-size to be attempted for the next step #RWORK[6]=dls001.hmin #RWORK[5]=dls001.hmxi number_of_fevals=N.array([1,2,4,7,11]) # d) Reset statistics IWORK[9:13]=[0]*4 dls001.nst=1 dls001.nfe=number_of_fevals[self.rkstarter-1] # from the starter dls001.nje=0 dlsr01.nge=0 # set common block commonblocks={} commonblocks.update(dls001()) commonblocks.update(dlsr01()) set_lsod_common(**commonblocks) return ISTATE, RWORK, IWORK
def _jacobian(self, t, y): """ Calculates the Jacobian, either by an approximation or by the user defined (jac specified in the problem class). """ jac = self.problem.jac(t,y) if isinstance(jac, sp.csc_matrix): jac = jac.toarray() return jac def integrate(self, t, y, tf, opts): ITOL = 2 #Only atol is a vector ITASK = 5 #For one step mode and hitting exactly tcrit, normally tf IOPT = 1 #optional inputs are used # provide work arrays and set common blocks (if needed) ISTATE, RWORK, IWORK = self.integrate_start( t, y) JT = 1 if self.usejac else 2#Jacobian type indicator JROOT = N.array([0]*self.problem_info["dimRoot"]) #Setting work options RWORK[0] = tf #Do not integrate past tf RWORK[5] = self.options["maxh"] #Setting iwork options IWORK[5] = self.maxsteps #Setting maxord to IWORK IWORK[7] = self.maxordn IWORK[8] = self.maxords #Dummy methods #g_dummy = (lambda t:x) if not self.problem_info["state_events"] else self.problem.state_events g_fcn = g_dummy if not self.problem_info["state_events"] else self.problem.state_events #jac_dummy = (lambda t,y:N.zeros((len(y),len(y)))) if not self.usejac else self.problem.jac jac_fcn = jac_dummy if not self.usejac else self._jacobian #Extra args to rhs and state_events rhs_extra_args = (self.sw,) if self.problem_info["switches"] else () g_extra_args = (self.sw,) if self.problem_info["switches"] else () #Store the opts self._opts = opts #Outputs tlist = [] ylist = [] #Run in normal mode? normal_mode = 1 if opts["output_list"] is not None else 0 #Tolerances: atol = self.atol rtol = self.rtol*N.ones(self.problem_info["dim"]) rhs = self.problem.rhs #if normal_mode == 0: if opts["report_continuously"] or opts["output_list"] is None: while (ISTATE == 2 or ISTATE == 1) and t < tf: y, t, ISTATE, RWORK, IWORK, roots = dlsodar(rhs, y.copy(), t, tf, ITOL, rtol, atol, ITASK, ISTATE, IOPT, RWORK, IWORK, jac_fcn, JT, g_fcn, JROOT, f_extra_args = rhs_extra_args, g_extra_args = g_extra_args) self._update_nordsieck = True self._IWORK = IWORK self._RWORK = RWORK #hu, nqu ,nq ,nyh, nqnyh = get_lsod_common() #self._nordsieck_array = \ # RWORK[nordsieck_start_index:nordsieck_start_index+(nq+1)*nyh].reshape((nyh,-1),order='F') #self._nyh = nyh self._event_info = roots if opts["report_continuously"]: flag_initialize = self.report_solution(t, y, opts) if flag_initialize: #If a step event has occured the integration has to be reinitialized ISTATE = 3 else: #Store results tlist.append(t) ylist.append(y.copy()) #Checking return if ISTATE == 2: flag = ID_PY_COMPLETE elif ISTATE == 3: flag = ID_PY_EVENT else: raise ODEPACK_Exception("LSODAR failed with flag %d"%ISTATE) else: #Change the ITASK ITASK = 4 #For computation of yout output_index = opts["output_index"] output_list = opts["output_list"][output_index:] flag = ID_PY_COMPLETE for tout in output_list: output_index += 1 y, t, ISTATE, RWORK, IWORK, roots = dlsodar(rhs, y.copy(), t, tout, ITOL, rtol, atol, ITASK, ISTATE, IOPT, RWORK, IWORK, jac_fcn, JT, g_fcn, JROOT, f_extra_args = rhs_extra_args, g_extra_args = g_extra_args) #Store results tlist.append(t) ylist.append(y.copy()) self._event_info = roots #Checking return if ISTATE == 2 and t >= tf: flag = ID_PY_COMPLETE break elif ISTATE == 3: flag = ID_PY_EVENT break elif ISTATE < 0: raise ODEPACK_Exception("LSODAR failed with flag %d"%ISTATE) opts["output_index"] = output_index # deciding on restarting options self._rkstarter_active = True if ISTATE == 3 and self.rkstarter > 1 else False #print 'rkstarter_active set to {} and ISTATE={}'.format(self._rkstarter_active, ISTATE) #Retrieving statistics self.statistics["nstatefcns"] += IWORK[9] self.statistics["nsteps"] += IWORK[10] self.statistics["nfcns"] += IWORK[11] self.statistics["njacs"] += IWORK[12] self.statistics["nstateevents"] += 1 if flag == ID_PY_EVENT else 0 # save RWORK, IWORK for restarting feature if self.rkstarter>1: self._RWORK=RWORK self._IWORK=IWORK return flag, tlist, ylist
[docs] def get_algorithm_data(self): """ Returns the order and step size used in the last successful step. """ hu, nqu ,nq ,nyh, nqnyh = get_lsod_common() return hu, nqu
[docs] def state_event_info(self): """ Returns the state events indicator as a list where a one (1) indicates that the event function have been activated and a (0) if not. """ return self._event_info
[docs] def print_statistics(self, verbose=NORMAL): """ Prints the run-time statistics for the problem. """ Explicit_ODE.print_statistics(self, verbose) #Calls the base class self.log_message('\nSolver options:\n', verbose) self.log_message(' Solver : LSODAR ', verbose) self.log_message(' Absolute tolerances : {}'.format(self.options["atol"]), verbose) self.log_message(' Relative tolerances : {}'.format(self.options["rtol"]), verbose) if self.rkstarter==1: self.log_message(' Starter : {}'.format('classical'), verbose) else: self.log_message(' Starter : Runge-Kutta order {}'.format(self.rkstarter), verbose) if self.maxordn < 12 or self.maxords < 5: self.log_message(' Maximal order Adams : {}'.format(self.maxordn), verbose) self.log_message(' Maximal order BDF : {}'.format(self.maxords), verbose) if self.maxh > 0. : self.log_message(' Maximal stepsize maxh : {}'.format(self.maxh), verbose) self.log_message('', verbose)
def _set_usejac(self, jac): self.options["usejac"] = bool(jac) def _get_usejac(self): """ This sets the option to use the user defined jacobian. If a user provided jacobian is implemented into the problem the default setting is to use that jacobian. If not, an approximation is used. Parameters:: usejac - True - use user defined jacobian False - use an approximation - Should be a boolean. Example: usejac = False """ return self.options["usejac"] usejac = property(_get_usejac,_set_usejac) def _set_atol(self,atol): self.options["atol"] = N.array(atol,dtype=N.float) if len(N.array(atol,dtype=N.float).shape)>0 else N.array([atol],dtype=N.float) if len(self.options["atol"]) == 1: self.options["atol"] = self.options["atol"]*N.ones(self._leny) elif len(self.options["atol"]) != self._leny: raise ODEPACK_Exception("atol must be of length one or same as the dimension of the problem.") def _get_atol(self): """ Defines the absolute tolerance(s) that is to be used by the solver. Can be set differently for each variable. Parameters:: atol - Default '1.0e-6'. - Should be a positive float or a numpy vector of floats. Example: atol = [1.0e-4, 1.0e-6] """ return self.options["atol"] atol=property(_get_atol,_set_atol) def _set_rtol(self,rtol): try: self.options["rtol"] = float(rtol) except (ValueError, TypeError): raise ODEPACK_Exception('Relative tolerance must be a (scalar) float.') if self.options["rtol"] <= 0.0: raise ODEPACK_Exception('Relative tolerance must be a positive (scalar) float.') def _get_rtol(self): """ Defines the relative tolerance that is to be used by the solver. Parameters:: rtol - Default '1.0e-6'. - Should be a positive float. Example: rtol = 1.0e-4 """ return self.options["rtol"] rtol=property(_get_rtol,_set_rtol) def _get_maxsteps(self): """ The maximum number of steps allowed to be taken to reach the final time. Parameters:: maxsteps - Default 10000 - Should be a positive integer """ return self.options["maxsteps"] def _set_maxsteps(self, max_steps): try: max_steps = int(max_steps) except (TypeError, ValueError): raise ODEPACK_Exception("Maximum number of steps must be a positive integer.") self.options["maxsteps"] = max_steps maxsteps = property(_get_maxsteps, _set_maxsteps) def _get_hmax(self): """ The absolute value of the maximal stepsize for all methods Parameters:: hmax - Default: 0. (no maximal step size) - Should be a positive float """ logging.warning("The option 'hmax' is deprecated and will be removed, please use 'maxh' instead.") return self.options["maxh"] def _set_hmax(self,hmax): if not (isinstance(hmax,float) and hmax >= 0.): raise ODEPACK_Exception("Maximal step size hmax should be a positive float") logging.warning("The option 'hmax' is deprecated and will be removed, please use 'maxh' instead.") self.options["maxh"]=hmax hmax = property(_get_hmax, _set_hmax) def _get_maxh(self): """ The absolute value of the maximal stepsize for all methods Parameters:: maxh - Default: 0. (no maximal step size) - Should be a positive float """ return self.options["maxh"] def _set_maxh(self,maxh): if not (isinstance(maxh,float) and maxh >= 0.): raise ODEPACK_Exception("Maximal step size maxh should be a positive float") self.options["maxh"]=maxh maxh = property(_get_maxh, _set_maxh) def _get_maxordn(self): """ The maximum order used by the Adams-Moulton method (nonstiff case) Parameters:: maxordn - Default 12 - Should be a positive integer """ return self.options["maxordn"] def _set_maxordn(self, maxordn): try: maxordn = int(maxordn) except (TypeError, ValueError): raise ODEPACK_Exception("Maximum order must be a positive integer.") if maxordn > 12: raise ODEPACK_Exception("Maximum order should not exceed 12.") self.options["maxordn"] = maxordn maxordn = property(_get_maxordn, _set_maxordn) def _get_maxords(self): """ The maximum order used by the BDF method (stiff case) Parameters:: maxords - Default 5 - Should be a positive integer """ return self.options["maxords"] def _set_maxords(self, maxords): try: maxords = int(maxords) except (TypeError, ValueError): raise ODEPACK_Exception("Maximum order must be a positive integer.") if maxords > 5: raise ODEPACK_Exception("Maximum order should not exceed 5.") self.options["maxords"] = maxords maxords = property(_get_maxords, _set_maxords) def _get_rkstarter(self): """ This defines how LSODAR is started. (classically or with a fourth order Runge-Kutta starter) Parameters:: rkstarter - Default False starts LSODAR in the classical multistep way - Should be a Boolean """ return self.options["rkstarter"] def _set_rkstarter(self, rkstarter): if not rkstarter in {1,2,3,4,5}: raise ODEPACK_Exception("Must be a positive integer less than 6") self.options["rkstarter"] = rkstarter rkstarter = property(_get_rkstarter, _set_rkstarter)
class RKStarterNordsieck(object): """ A family of Runge-Kutta starters producing a Nordsieck array to (re-)start a Nordsieck based multistep method with a given order. See: Mohammadi (2013): https://lup.lub.lu.se/luur/download?func=downloadFile&recordOId=4196026&fileOId=4196027 """ # Gamma matrix of Gear's RK starter which produce Nordsieck vector at t0 Gamma_0=[N.array([[1.,0.], # 1st order [0.,1.]]), N.array([[1.,0.,0.], # 2nd order [0.,1.,-1.], [0.,0.,1.]]), N.array([[1.,0.,0.,0.], # 3rd order [0.,1.,-5./3.,1.], [0.,0.,3.,-2.], [0.,0.,0.,1.], [0.,0.,-4./3.,0.]]), N.array([[1.,0.,0.,0.,0.], # 4th order [0.,1.,-5./6.,4./9.,-1./9.], [0.,0.,0.,0.,0.], [0.,0.,1./2.,-4./9.,1./9.], [0.,0.,7./3.,-19./9.,7./9.], [0.,0.,-3.,10./3.,-4./3.], [0.,0.,1.,-11./9.,5./9.]])] # A matrices of RK starter with equidistanced states A_s=[ N.array([1]), # 1st order N.array([[0.,0],[1,0]]), # 2nd order N.array([[0.,0.,0.,0.,0.],[1./2,0.,0.,0.,0.], # 3rd order [0.,3./4,0.,0.,0.],[2./9,1./3,4./9,0.,0.], [17./72,1./6,2./9,-1./8,0.]]), N.array([[0.,0.,0.,0.,0.,0.,0.], # 4th order [1./6.,0.,0.,0.,0.,0.,0.], [0.,1./6.,0.,0.,0.,0.,0.], [0.,0.,1./3.,0.,0.,0.,0.], [1./18.,1./9.,1./9.,1./18.,0.,0.,0.], [2.5,-3.,-3.,2.25,2.25,0.,0.], [10./45.,-8./45.,-8./45.,-4./45.,13./15.,1./45.,0.]]), N.array([[0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.], # 5th order [1./20,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.], [3./160,9./160,0.,0.,0.,0.,0., 0.,0.,0.,0.,0.,0.,0.], [3./40,-9./40,6./20,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.], [-11./216,5/8,-70/108,35./108,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.], [1631/221184,175./2048,575./55296,44275./442368,253./16384,0.,0.,0.,0.,0.,0.,0.,0.,0.], [37./1512,0.,250/2484,125./2376,0.,512/7084,0.,0.,0.,0.,0.,0.,0.,0.], [32.2687682,-39.4956646,-20.7849921,22.2296308,45.4543383,51.9961815,-91.1682621,0.,0.,0.,0.,0.,0.,0.], [-23.867154567764317,41+31195543/31462660,8+6080897/76520184,-23-7543649/15003372,-63-91662407/130833420,-55-27823937/32947321,117+23615874/27978401,0.,0.,0.,0.,0.,0.,0.], [-0.5118403967750724,0.,2+379808/6250467,-2-866339/6430593,-1-151963/7810968,-18723895/33225736,8/3,0.,303385/143008499,0.,0.,0.,0.,0.], [-118.50992840087292,4+1586290/42052551,7+76050433/143964928,11+61385198/111742353,16+54354964/63299173,15+84679766/214253769,15+71618817/71932238,24+14108046/149713813, 2+12784603/81260661,21+38484710/59808171,0.,0.,0.,0.], [10.183365237189525,0.,-36-34947657/185923229,38+50251318/55929787,18+114017528/325586295,10+78529445/98887874,-43.5,0.,-6714159/175827524,2.25,0.,0.,0.,0.], [-0.1491588850008383,0.,19145766/113939551,8434687/36458574,2012005/39716421,8409989/57254530,10739409/94504714,0.,-3321/86525909,-30352753/150092385,0.,70257074/109630355, 0.,0.], [0.03877310906055409,0.,3021245/89251943,5956469/58978530,851373/32201684,11559106/149527791,11325471/112382620,0.,-12983/235976962,17692261/82454251,0., 38892959/120069679,11804845./141497517]])] co_ord_s=[[],[],[4],[4,6],[6,9,11]] b_s=[ N.array([1]), N.array([1./2,1./2]), N.array([1./6,0.,0.,1./6,2./3]), N.array([29./1062.,83./531.,83./531.,83./1062.,2./531.,56./531.,251./531.]), # b-vectors of of 1st to 5th order RK starter method N.array([0.03877310906055409,0.,3021245/89251943,5956469/58978530,851373/32201684,11559106/149527791,11325471/112382620,0.,-12983/235976962,17692261/82454251,0., 38892959/120069679,11804845./141497517,0.])] C_s=[ N.array([0.]), N.array([0.]), N.array([0.,1./2,3./4,1.,1./2,1.]), N.array([0.,1./6,1./6,1./3,1./3,1.,1.,2./3,1.]), N.array([0.,1./20,3./40,3./20,1./4,7./28,1./4,2./4,1.,2./4,3./4,3./4,1.,1.])] # C-values in Butcher tableau of 8-stages Runge-Kutta # A matrices of RK starter with non-equidistanced stages A_n=[ N.array([1]), N.array([[0.,0.],[0.,0.]]), N.array([[0.,0.,0.,0.],[1./2,0.,0.,0.],[0.,1./2,0.,0.],[0.,0.,1.,0.]]), # 3rd order N.array([[0.,0.,0.,0.,0.,0.],[2./5,0.,0.,0.,0.,0.],[-3./20,3./4,0.,0.,0.,0.], # 4th order [19./44,-15./44,40./44,0.,0.,0.],[-31./64,185./192,5./64,-11./192,0.,0.],[11./72,25./72,25./72,11./72,0.,0.]])] b_n=[ N.array([0.]), N.array([1./2.,1./2.]), N.array([[5./24,1./6,1./6,-1/24],[1./6,1./3,1./3,1./6]]), N.array([[802./5625,68./225,-67./225,-143./5625,144./625,6./125],[699./5000,81./200,-39./200,99./5000,144./625,0.],[11./72,25./72,25./72,11./72,0.,0.]])] C_n=[ N.array([0.]), N.array([0.]), N.array([0.,1./2,1./2,1.]), N.array([0.,2./5,3./5,1.,1./2,1.])] #co_ord_n=[[],[],[1./2,1.],[2./5,3./5,1.]] #A=N.array([[1.,0.,0.,0.], # [1.,1./9.,1./27,1./81.], # [1.,4./9.,8./27,16./81.], # [1.,1.,1.,1.]]) A=[N.array([0.]), # Convert the state values to Nordsieck vector N.array([0.]), N.array([1.]), N.array([[1./4,1./8],[1.,1.]]), N.array([[1./9,1./27,1./81.], [4./9.,8./27,16./81], [1.,1.,1.]]), N.array([[1./16,1./64,1./256,1./1024], [1./4,1./8,1./16,1./32], [9./16,27./64,81./256,243./1024], [1.,1.,1.,1.]])] scale=N.array([1, 1, 1/2., 1./6., 1./24., 1./120.]).reshape(-1,1) def __init__(self, rhs, H, method='RKs_f', eval_at=0., number_of_steps=4): """ Initiates the Runge-Kutta Starter. Parameters:: rhs - The problem's rhs function H - starter step size eval_at - Where to evaluate the Nordiseck vector evaluation point is (eval_at)*H Possible choices eval_at=0. eval_at=1. number_of_steps - the number of steps :math:`(k)` to start the multistep method with This will determine the initial order of the method, which is :math:`k` for BDF methods and :math:`k+1` for Adams Moulton Methods . """ self.f = rhs self.H = H self.method=method if not 1 < number_of_steps < 6: raise RKStarter_Exception('Step number larget than 4 not yet implemented') self.number_of_steps = number_of_steps self.eval_at = float(eval_at) if not self.eval_at == 0.: raise RKStarter_Exception("Parameter eval_at different from 0 not yet implemented.") def RKs_f(self,t0,y0,sw0): s=self.number_of_steps A_s,C_s,b_s,co_ord_s=self.A_s,self.C_s,self.b_s,self.co_ord_s A_s=A_s[s-1] C_s=C_s[s-1] b_s=b_s[s-1] co_ord_s=co_ord_s[s-1] H=(s-1)*self.H K=N.zeros((N.size(A_s,0),len(y0))) for i in range(N.size(A_s,0)): K[i,:]=self.f(t0+C_s[i]*H,y0+H*N.dot(A_s[i,:],K),sw0) y=N.zeros((s,len(y0))) y[0,:]=y0 for i in range(1,s): if i==s-1: y[i,:]=y0+H*N.dot(b_s,K) else: y[i,:]=y0+H*N.dot(A_s[co_ord_s[i-1],:],K) return y def RKn_f(self,t0,y0,sw0): s=self.number_of_steps H=(s-1)*self.H A_n,C_n,b_n=self.A_n,self.C_n,self.b_n A_n=A_n[s-1] C_n=C_n[s-1] b_n=b_n[s-1] K=N.zeros((N.size(A_n,0),len(y0))) for i in range(N.size(A_n,0)): K[i,:]=self.f(t0+C_n[i]*H,y0+H*N.dot(A_n[i,:],K),sw0) y=N.zeros((s,len(y0))) y[0,:]=y0 for i in range(1,s): y[i,:]=y0+H*N.dot(b_n[i-1],K) return y def rk_like4(self, t0, y0, sw0): """ rk_like computes Runge-Kutta stages Note, the currently implementation is **only** correct for autonomous systems. """ f = lambda y: self.f(t0, y, sw0) h = self.H/4. k1 = h*f(y0) k2 = h*f(y0 + k1) k3 = h*f(y0 + 2. * k2) k4 = h*f(y0 + 3./4. * k1 + 9./4. * k3) k5 = h*f(y0 + k1/2. + k2 + k3/2. + 2. * k4) k6 = h*f(y0+k1/12.+2. * k2 + k3/4. + 2./3. * k4 + 2. * k5) return N.array([y0,k1,k2,k3,k4,k5,k6]) def rk_like3(self, t0, y0, sw0): """ rk_like computes Runge-Kutta stages Note, the currently implementation is **only** correct for autonomous systems. """ f = lambda y: self.f(t0, y, sw0) h = self.H/3. k1 = h*f(y0) k2 = h*f(y0 + k1) k3 = h*f(y0 + k1+ k2) k4 = h*f(y0 + 3./2. * k1) return N.array([y0,k1,k2,k3,k4]) def rk_like2(self, t0, y0, sw0): """ rk_like2 computes Runge-Kutta 2nd-stages Note, the currently implementation is **only** correct for autonomous systems. """ f=lambda y: self.f(t0, y, sw0) h=self.H/2. k1=h*f(y0) k2=h*f(y0+k1) return N.array([y0,k1,k2]) def rk_like13(self, t0, y0, sw0): """ rk_like6 computes Runge-Kutta 8th-stages """ h = self.H self.Gamma_2=self.Gamma_0[3] f=lambda y: self.f(t0 , y , sw0) K=N.zeros((6,len(y0))) sol=N.zeros((3,len(y0))) b=N.zeros((2,len(y0))) #remove the fifth stage value that is for error estimation nord = N.zeros((4,len(y0))) #Nordsieck vector for i in range(5): K[i,:]= f(y0+h*N.dot(self.Gamma_2[i,:],K)) c=0 for i in range(3): sol[i,:]=y0+h*N.dot(self.Gamma_2[i+3,:],K) if i!=0: b[c,:]=sol[i,:]-y0-(c+1)*h/2*K[0,:] c+=1 nord[0,:] = y0 nord[1,:] = h*K[0,:] nord[2:,:] = Sc.solve(self.A[self.number_of_steps],b) return nord def rk_like14(self, t0, y0, sw0): """ rk_like6 computes Runge-Kutta 8th-stages """ h = self.H Gamma_2=self.Gamma_0[4] f=lambda y: self.f(t0 , y , sw0) K=N.zeros((8,len(y0))) sol=N.zeros((4,len(y0))) b=N.zeros((3,len(y0))) #remove the fifth stage value that is for error estimation nord = N.zeros((5,len(y0))) #Nordsieck vector for i in range(7): K[i,:]= f(y0+h*N.dot(Gamma_2[i,:],K)) c=0 for i in range(4): sol[i,:]=y0+h*N.dot(Gamma_2[i+4,:],K) if i!=1: b[c,:]=sol[i,:]-y0-(c+1)*h/3*K[0,:] c+=1 nord[0,:] = y0 nord[1,:] = h*K[0,:] nord[2:,:] = Sc.solve(self.A[self.number_of_steps],b) return nord def rk_like15(self, t0, y0, sw0): """ rk_like6 computes Runge-Kutta 5th-stages ****needs to be modified**** """ h = self.H Gamma_2=self.Gamma_0[5] f=lambda y: self.f(t0 , y , sw0) K=N.zeros((14,len(y0))) sol=N.zeros((8,len(y0))) b=N.zeros((4,len(y0))) #remove the fifth stage value that is for error estimation nord = N.zeros((6,len(y0))) #Nordsieck vector for i in range(13): K[i,:]= f(y0+h*N.dot(Gamma_2[i,:],K)) c=0 for i in range(8): sol[i,:]=y0+h*N.dot(Gamma_2[i+6,:],K) if (i!=1) and (i!=2) and (i!=4) and (i!=6): b[c,:]=sol[i,:]-y0-(c+1)*h/4*K[0,:] c+=1 nord[0,:] = y0 nord[1,:] = h*K[0,:] nord[2:,:] = Sc.solve(self.A[self.number_of_steps],b) return nord def nordsieck(self,k): """ Nordsieck array computed at initial point """ nord=self.scale[:self.number_of_steps+1]*N.dot(self.Gamma_0[self.number_of_steps-1].T,k) return nord def Nordsieck_RKn(self,t0,y,sw0): s=self.number_of_steps H=(s-1)*self.H co_nord=[N.array([1./2,1.]),N.array([2./5,3./5,1.])] l=size(y,0) y0=y[0,:] yf=self.f(t0,y0,sw0) if l==3: co=N.array([co_nord[0]]) nord_n=N.vander(co_nord[0],self.number_of_steps+1) b=y[1:]-y0-co.T*yf nord=Sc.solve(nord_n[0:2,0:2],b) elif l==4: co=N.array([co_nord[1]]) nord_n=N.vander(co_nord[1],self.number_of_steps+1) b=y[1:]-y0-H*co.T*yf nord=Sc.solve(nord_n[0:3,0:3],b) nord=N.vstack((y0,H*yf,nord[::-1])) return nord def Nordsieck_RKs(self,t0,y,sw0): s=self.number_of_steps H=(s-1)*self.H co_nord=[N.array([1]),N.array([1./2,1]),N.array([1./3,2./3,1]), N.array([1./4,2./4,3./4,1.])] A=self.A y0=y[0,:] yf=self.f(t0,y0,sw0) co=co_nord[s-2] co=N.array([co]) b=y[1:]-y0-H*co.T*yf nord=Sc.solve(A[s],b) nord=N.vstack((y0,H*yf,nord)) return nord def __call__(self, t0 , y0, sw0=[]): """ Evaluates the Runge-Kutta starting values Parameters:: y0 - starting value """ if self.method=='RK_G': # We construct a call like: rk_like4(self, t0, y0, sw0) k=self.__getattribute__('rk_like{}'.format(self.number_of_steps))(t0, y0, sw0) t = t0+self.eval_at*self.H #t= t0 + self.H k=self.nordsieck(k) elif self.method=='RKs_f': y=self.RKs_f(t0, y0, sw0) t = t0+self.eval_at*self.H k=self.Nordsieck_RKs(t0,y,sw0) elif self.method=='RKn_f': y=self.RKn_f(t0,y0,sw0) t=t0+self.eval_at*self.H k=self.Nordsieck_RKn(t0,y,sw0) return t,k