Source code for struphy.ode.solvers

from inspect import signature

import cunumpy as xp
from feectools.linalg.block import BlockVector
from feectools.linalg.stencil import StencilVector

from struphy.ode.utils import ButcherTableau


[docs] class ODEsolverFEEC: r""" Solver for FEEC coefficients based on explicit s-stage Runge-Kutta methods. .. image:: ../../pics/explicit_rk_methods.png :align: center :scale: 70% Parameters ---------- vector_field : dict The vector field of the ODE as a dictionary. Keys are the variables to be updated (i.e. Stencil- or BlockVectors), values are callables representing the respective component of the vector field. That means dy_i/dt = f_i(y_1, ..., y_n) for i = 1,...,n, where n is the number of variables. algo : str See :class:`~struphy.ode.utils.ButcherTableau` for available algorithms. """ def __init__( self, vector_field: dict, butcher: ButcherTableau = ButcherTableau(), ): # get algorithm self._butcher = butcher # check arguments and allocate k for each stage self._k = {} for vec, f in vector_field.items(): assert isinstance(vec, (StencilVector, BlockVector)) assert callable(f) sig = signature(f) assert len(sig.parameters) == len(vector_field) + 2, ( "Number of arguments of each callable must match the number of unknows plus two (for time and out)." ) self._k[vec] = [] for s in range(self.butcher.n_stages): self._k[vec] += [vec.space.zeros()] self._vector_field = vector_field # collect unknows in list self._y = list(self.vector_field.keys()) # allocate space for initial condition and intermediate values self._yn = [v.copy() for v in self.y] self._ystar = [v.copy() for v in self.y] def __call__(self, tn, h): a = self.butcher.a b = self.butcher.b c = self.butcher.c # keep initial condition for v, vn in zip(self.y, self.yn): v.copy(out=vn) # evaluate vector field for each stage for i in range(self.butcher.n_stages): # new intermediate y* (stored at self.y) for v, vn, vec in zip(self.y, self.yn, self.vector_field): # start with yn vn.copy(out=v) # add already computed k's for j in range(i): v += h * a[i, j] * self.k[vec][j] # compute new k_i for vec, f in self.vector_field.items(): self.k[vec][i] *= 0.0 self.k[vec][i] += f(tn + c[i] * h, *self.y) # final addition, start with vn for v, vn, vec in zip(self.y, self.yn, self.vector_field): vn.copy(out=v) for i in range(self.butcher.n_stages): v += h * b[i] * self.k[vec][i] @property def vector_field(self): """The vector field of the ode as a dictionary. Keys are the variables to be updated (i.e. Stencil- or BlockVectors), values are callables representing the respective component of the vector field.""" return self._vector_field @property def y(self): """List of variables to be updated.""" return self._y @property def yn(self): """List of allocated space for initial conditions for each variable.""" return self._yn @property def butcher(self): """See :class:`~struphy.ode.utils.ButcherTableau`.""" return self._butcher @property def k(self): """Dictionary of k values for each stage; keys are the variables and values are lists with one allocated k-vector for each stage.""" return self._k