Source code for atomsmltr.simulation.simulator.scipy

"""Scipy-based integrators
============================

Implements integrator based on scipy's ``solve_ivp`` method.
"""

# % IMPORTS
import numpy as np
from scipy.integrate import solve_ivp
from functools import partial

# % LOCAL IMPORTS
from .simbase import Simulation, get_force_vec, _get_force_vec
from ..configurator import Configuration


# % SIMULATOR BASED ON SCIPY'S SOLVE_IVP


[docs] def get_force_vec_scipy( pos_speed_vector: np.ndarray, config: Configuration ) -> np.ndarray: """Computes the force on an atom, by adding all radiations pressures, in a Scipy compatible vectorization style Parameters ---------- pos_speed_vector : array, shape (6,) or (6,k) cartesian position and speed vector config : Configuration a configuration object Returns ------- force : array, shape (3,) or (3,k) the force at the coordinates given by ``pos_speed_vector`` Notes ----- The position/speed vector 'pos_speed_vector' should be of shape (6,) or (6,k) with the first dimension containing (x, y, z, vx, vy, vz) in the lab frame Note ---- The function is vectorized to be compatible with Scipy's ``solve_ivp`` function. Hence, it does not satisfy the functionnal vectorization used in the rest of this module Examples --------- .. code-block:: python # ... init a config object with the `Configuration` class from atomsmltr.simulation.simulator import get_force_vec_scipy import numpy as np # - init a position & speed vector grid # vx spans from -10 to 30 # x, y, z, vy, vz set to 0 vx_list = np.linspace(-10, 30, 301) pos_speed_vector = np.array([(0,0,0,vx,0,0,) for vx in vx_list]).T # - compute the force force = get_force_vec_scipy(pos_speed_vector, config) FX, FY, FZ = force # - print shapes for illustration print(f"{FX.shape=}") print(f"{pos_speed_vector.shape=}") print(f"{force.shape=}") This returns .. code-block:: python FX.shape=(301,) pos_speed_vector.shape=(6, 301) force.shape=(3, 301) """ # TODO should we move that to the Configuration class ??? # - get position and speed position = pos_speed_vector[0:3, ...].T speed = pos_speed_vector[3:6, ...].T # - compute force force = _get_force_vec(position, speed, config) # - transpose to satisfy vectorization rules force = force.T return force
[docs] def stop_position_event_scipy( t: float, u: np.ndarray, stop_position: list, offset: float = 0.0 ): """Implements 'stop' events for Scipy's solve_ivp, based on atom's position Parameters ---------- t : float time, not used here but required for the ``events`` functions in ``solve_ivp`` u : array, shape (6,k) position/speed vector, according to ``solve_ivp`` vectorization convention stop_position : list list of Zones objects targetting position with actions set to stop Returns ------- res: bool whether to stop the simulation See also -------- atomsmltr.environment.zones atomsmltr.simulation.configurator.Configuration.get_stop_zones() """ position = u[0:3, ...].T res = np.logical_and.reduce([zone.get_value(position) for zone in stop_position]) res = res + offset return res
[docs] def stop_speed_event_scipy( t: float, u: np.ndarray, stop_speed: list, offset: float = 0.0 ): """Implements 'stop' events for Scipy's solve_ivp, based on atom's speed Parameters ---------- t : float time, not used here but required for the ``events`` functions in ``solve_ivp`` u : array, shape (6,k) position/speed vector, according to ``solve_ivp`` vectorization convention stop_speed : list list of Zones objects targetting speed with actions set to stop Returns ------- res: bool whether to stop the simulation See also -------- atomsmltr.environment.zones atomsmltr.simulation.configurator.Configuration.get_stop_zones() """ speed = u[3:6, ...].T res = np.logical_and.reduce([zone.get_value(speed) for zone in stop_speed]) res = res + offset return res
[docs] class ScipyIVP_3D(Simulation): """A simulation class based on Scipy's ``solve_ivp`` solver Parameters ---------- config : Configuration, optional the configuration to consider for the simulation method : str, optional method used for the ``solve_ivp`` solver, by default "Radau" **solve_ivp_args all other arguments are directly passed to ``solve_ivp`` References ---------- https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html """ def __init__( self, config: Configuration = None, method: str = "Radau", **solve_ivp_args ): super(ScipyIVP_3D, self).__init__(config) self.solve_ivp_args = solve_ivp_args self.method = method # -- REQUESTED FUNCTIONS def _get_force_scipy(self, u): force = get_force_vec_scipy(u, self.config) return force
[docs] def get_force(self, u): force = get_force_vec(u, self.config) return force
[docs] def dudt(self, t, u): F = self._get_force_scipy(u) _, _, _, vx, vy, vz = u dx, dy, dz = vx, vy, vz dvx, dvy, dvz = F / self.config.atom.mass res = np.array([dx, dy, dz, dvx, dvy, dvz]) return res
def _integrate(self, u0, t): # - u0 to array u0 = np.asanyarray(u0) # - get stop events events = [] stop_position, stop_speed = self.config.get_stop_zones() if stop_position: stop_pos = partial( stop_position_event_scipy, stop_position=stop_position, offset=-0.5 ) stop_pos.terminal = True events.append(stop_pos) if stop_speed: stop_sp = partial( stop_speed_event_scipy, stop_speed=stop_speed, offset=-0.5 ) stop_sp.terminal = True events.append(stop_sp) # - time t = np.asanyarray(t) if not t.shape: t = np.asanyarray([0, t]) t = np.sort(t) t_span = (t[0], t[-1]) # - integrate res = solve_ivp( fun=self.dudt, t_span=t_span, y0=u0, method=self.method, t_eval=t, events=events, **self.solve_ivp_args, ) return res def _stop_event_speed(self, t, u): pass def _stop_event_position(self, t, u): pass def _u0_list_checker(self, value): if not hasattr(value, "__iter__"): raise ValueError("'u0_list' should be an iterable object") if value: for u0 in value: if np.asanyarray(u0).shape != (6,): raise ValueError("'u0_list' should be a list of arrays of size 6") return value