Source code for gym_electric_motor.physical_systems.solvers

from scipy.integrate import ode, odeint, solve_ivp


[docs]class OdeSolver: """ Interface and base class for all used OdeSolvers in gym-electric-motor. """ #: Current system time t _t = 0 #: Current system state y _y = None #: Function parameters that are passed to the system equation and the system jacobian additionally to t and y _f_params = None #: System equation in the form: _system_equation(t, y, *f_params) _system_equation = None #: System jacobian in the form _system_jacobian(t,y, *f_params) _system_jacobian = None @property def t(self): """ Returns: float: Current system time t """ return self._t @property def y(self): """ Returns: float: Current system state y """ return self._y
[docs] def set_initial_value(self, initial_value, t=0): """ Set the new initial system state after reset. Args: initial_value(numpy.ndarray(float)): Initial system state t(float): Initial system time """ self._y = initial_value self._t = t
[docs] def integrate(self, t): """ Integrate the ODE-System from current time until time t Args: t(float): Time until the system shall be integrated Returns: ndarray(float): New system state at time t """ raise NotImplementedError
[docs] def set_system_equation(self, system_equation, jac=None): """ Setting of the systems equation. Args: system_equation(function_pointer): Pointer to the systems equation with the parameters (t, y, *args) jac(function_pointer): Pointer to the systems jacobian with the parameters (t, y, *args) """ self._system_equation = system_equation self._system_jacobian = jac
[docs] def set_f_params(self, *args): """ Set further arguments for the systems function call like input quantities. Args: args(list): Additional arguments for the next function calls. """ self._f_params = args
[docs]class EulerSolver(OdeSolver): """ Solves a system of differential equations of first order for a given time step with linear approximation. .. math: x^\prime(t) = f(x(t)) .. math: x(t + \\frac{\\tau}{nsteps}) = x(t) + x^\prime(t) * \\frac{\\tau}{nsteps} """ def __init__(self, nsteps=1): """ Args: nsteps(int): Number of cycles to calculate for each iteration. Higher steps make the system more accurate, but take also longer to compute. """ self._nsteps = nsteps self._integrate = self._integrate_one_step if nsteps == 1 else self._integrate_nsteps
[docs] def integrate(self, t): # Docstring of superclass return self._integrate(t)
def _integrate_nsteps(self, t): """ Integration method for nsteps > 1 Args: t(float): Time until the system shall be calculated Returns: ndarray(float):The new state of the system. """ tau = (t - self._t) / self._nsteps state = self._y current_t = t for _ in range(self._nsteps): delta = self._system_equation(current_t + tau, state, *self._f_params) * tau state = state + delta current_t += tau self._y = state self._t = t return self._y def _integrate_one_step(self, t): """ Integration method for nsteps = 1. (For faster computation) Args: t(float): Time until the system shall be calculated Returns: ndarray(float):The new state of the system. """ self._y = self._y + self._system_equation(self._t, self._y, *self._f_params) * (t - self._t) self._t = t return self._y
[docs]class ScipyOdeSolver(OdeSolver): """ Wrapper class for all ode-solvers in the scipy.integrate.ode package. https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html """ #: Integrator object _ode = None @property def t(self): return self._ode.t @property def y(self): return self._ode.y def __init__(self, integrator="dopri5", **kwargs): """ Args: integrator(str): String to choose the integrator from the scipy.integrate.ode kwargs(dict): All parameters that can be set in the "set_integrator"-method of scipy.integrate.ode """ self._solver = None self._solver_args = kwargs self._integrator = integrator
[docs] def set_system_equation(self, system_equation, jac=None): # Docstring of superclass super().set_system_equation(system_equation, jac) self._ode = ode(system_equation, jac).set_integrator(self._integrator, **self._solver_args)
[docs] def set_initial_value(self, initial_value, t=0): # Docstring of superclass self._ode.set_initial_value(initial_value, t)
[docs] def set_f_params(self, *args): # Docstring of superclass super().set_f_params(*args) self._ode.set_f_params(*args) self._ode.set_jac_params(*args)
[docs] def integrate(self, t): # Docstring of superclass return self._ode.integrate(t)
[docs]class ScipySolveIvpSolver(OdeSolver): """ Wrapper class for all ode-solvers in the scipy.integrate.solve_ivp function https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html """ def __init__(self, **kwargs): # Docstring of superclass self._solver_kwargs = kwargs
[docs] def set_system_equation(self, system_equation, jac=None): # Docstring of superclass method = self._solver_kwargs.get("method", None) super().set_system_equation(system_equation, jac) # Only Radau BDF and LSODA support the jacobian. if method in ["Radau", "BDF", "LSODA"]: self._solver_kwargs["jac"] = self._system_jacobian
[docs] def integrate(self, t): # Docstring of superclass result = solve_ivp( self._system_equation, [self._t, t], self._y, t_eval=[t], args=self._f_params, **self._solver_kwargs, ) self._t = t self._y = result.y.T[-1] return self._y
[docs]class ScipyOdeIntSolver(OdeSolver): """ Wrapper class for all ode-solvers in the scipy.integrate.odeint function. https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html """ def __init__(self, **kwargs): """ Args: kwargs(dict): Arguments to pass to the solver. See the scipy description for further information. """ self._solver_args = kwargs
[docs] def integrate(self, t): # Docstring of superclass result = odeint( self._system_equation, self._y, [self._t, t], args=self._f_params, Dfun=self._system_jacobian, tfirst=True, **self._solver_args, ) self._t = t self._y = result[-1] return self._y