Source code for gala.integrate.pyintegrators.dopri853

"""Wrapper around SciPy DOPRI853 integrator."""

# Third-party
from scipy.integrate import ode

# Project
from ..core import Integrator
from ..timespec import parse_time_specification

__all__ = ["DOPRI853Integrator"]


[docs] class DOPRI853Integrator(Integrator): r"""The Dormand-Prince 85(3) integration scheme. For Python integration, this class serves as a wrapper around Scipy's implementation of the integrator. See the Scipy documentation (`scipy.integrate. DOP853`) for more details and parameters. Note that the default tolerances for the Scipy integrator are quite poor for most applications, so you may want to set them to something like ``atol=1e-10`` and ``rtol=1e-10`` for better accuracy. Pass these to the class constructor as additional keyword arguments, e.g.:: >>> integrator = DOPRI853Integrator(func, atol=1e-10, rtol=1e-10) For Cython integration, this class serves as a wrapper around a modified version of a C implementation of the integrator. The C implementation is based on the original Hairer and Wanner implementation, which is a C translation of the original Fortran code by Dormand and Prince. The available arguments for the Cython integrator are: - ``atol`` (float) - the minimum absolute error allowed in each integration variable (i.e. position and velocity components) per step - ``rtol`` (float) - the minimum relative error allowed in each integration variable (i.e. position and velocity components) per step - ``nmax`` (int) - the maximum number of integration steps - ``dt_max`` (float) - the maximum internal timestep used for integration - ``nstiff`` (int) - the number of steps to take before checking for stiffness - ``err_if_fail`` (bool) - raise an error if the integration fails - ``log_output`` (bool) - log any debug or additional error messages from the C integrator (useful for debugging failed integrations) New in version 1.10: The Cython implementation of the DOPRI853 integrator now uses the dense output functionality of the integrator, which allows for more efficient evaluation of the orbit on a dense grid of times. This is done by using internal interpolation to compute the orbit at the requested times, rather than using the original integration steps. .. seealso:: - Numerical recipes (Dopr853) - http://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method Parameters ---------- func : callable A callable object that computes the phase-space coordinate derivatives with respect to the independent variable at a point in phase space. func_args : tuple (optional) Any extra arguments for the function. func_units : `~gala.units.UnitSystem` (optional) If using units, this is the unit system assumed by the integrand function. progress : bool (optional) Display a progress bar during integration. **kwargs Additional keyword arguments to pass to the integrator. See the Scipy documentation for more details about Python integration options. For Cython integration, see the class docstring above for details. """ def __init__( self, func, func_args=(), func_units=None, progress=False, save_all=True, **kwargs, ): super(DOPRI853Integrator, self).__init__( func, func_args, func_units, progress=progress, save_all=save_all ) self._ode_kwargs = kwargs
[docs] def __call__(self, w0, mmap=None, **time_spec): # generate the array of times times = parse_time_specification(self._func_units, **time_spec) n_steps = len(times) - 1 w0, arr_w0, ws = self._prepare_ws(w0, mmap, n_steps) _size_1d = 2 * self.ndim * self.norbits # need this to do resizing, and to handle func_args because there is some # issue with the args stuff in scipy... def func_wrapper(t, x): _x = x.reshape((2 * self.ndim, self.norbits)) val = self.F(t, _x, *self._func_args) return val.reshape((_size_1d,)) self._ode = ode(func_wrapper, jac=None) self._ode = self._ode.set_integrator("dop853", **self._ode_kwargs) # create the return arrays if self.save_all: ws[:, 0] = arr_w0 # make 1D arr_w0 = arr_w0.reshape((_size_1d,)) # set the initial conditions self._ode.set_initial_value(arr_w0, times[0]) # Integrate the ODE(s) across each delta_t timestep range_ = self._get_range_func() for k in range_(1, n_steps + 1): self._ode.integrate(times[k]) outy = self._ode.y if self.save_all: ws[:, k] = outy.reshape(2 * self.ndim, self.norbits) if not self._ode.successful(): raise RuntimeError("ODE integration failed!") if not self.save_all: ws = outy.reshape(2 * self.ndim, 1, self.norbits) times = times[-1:] return self._handle_output(w0, times, ws)