Source code for atomsmltr.simulation.simulator.deterministic

"""Home-made deterministic integrators
=========================================

Implements homemade integrators for deterministic systems, that is, not taking into
acount diffusion due to photon scattering.
"""

# % IMPORTS
import numpy as np
from functools import partial
from abc import abstractmethod

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


# % HOME-MADE SIMULATORS


[docs] def stop_position_event(u: np.ndarray, stop_position: list): """Implements 'stop' events for home-made simulators, based on atom's position Parameters ---------- u : array, shape (n,m,...,6) position/speed vector, according to our 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() """ x, y, z, _, _, _ = u.T position = np.array([x, y, z]).T res = np.logical_and.reduce([zone.get_value(position) for zone in stop_position]) res = res return res
[docs] def stop_speed_event(u: np.ndarray, stop_speed: list): """Implements 'stop' events for home-made simulators, based on atom's speed Parameters ---------- u : array, shape (n,m,...,6) position/speed vector, according to our 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() """ _, _, _, vx, vy, vz = u.T speed = np.array([vx, vy, vz]).T res = np.logical_and.reduce([zone.get_value(speed) for zone in stop_speed]) res = res return res
[docs] class CustomSimulationBase(Simulation): """A Base for home-made deterministic simulations Not meant to be used directly, just gathering common method """ def __init__( self, config: Configuration = None, ): super(CustomSimulationBase, self).__init__(config)
[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(u) _, _, _, vx, vy, vz = u.T dx, dy, dz = vx, vy, vz dvx, dvy, dvz = F.T / self.config.atom.mass res = np.array([dx, dy, dz, dvx, dvy, dvz]).T return res
@abstractmethod def _iterate(self, t, u, dt): """returns the evolution du of u between t and t+dt""" def _integrate(self, u0, t): # - u0 to array u = 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, stop_position=stop_position) events.append(stop_pos) if stop_speed: stop_sp = partial(stop_speed_event, stop_speed=stop_speed) events.append(stop_sp) # - time # TODO : add checks on time t = np.asanyarray(t) t = np.sort(t) dt = np.diff(t) # - initialize y = np.empty((*u.shape, len(t))) y[..., 0] = u stop = False # - integrate i = 1 u_none = np.full((6,), np.nan) for i, (tt, h) in enumerate(zip(t[1:], dt)): # check events if events: for ev in events: test = ev(u) u[np.logical_not(test), :] = u_none if not np.any(test): stop = True if stop: break # perform step u = u + self._iterate(tt, u, h) y[..., i + 1] = u if stop: y = y[..., : i + 1] t = t[: i + 1] res = SimRes(t=t, y=y) return res 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
[docs] class RK4(CustomSimulationBase): """A homemade simulator based on fourth order Runge-Kutta method Parameters ---------- config : Configuration, optional the configuration to consider for the simulation References ---------- https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods """ def __init__( self, config: Configuration = None, ): super(RK4, self).__init__(config) def _iterate(self, t, u, dt): """returns the evolution du of u between t and t+dt Here we use the fourth order Runge-Kutta method """ # perform step k1 = self.dudt(t, u) k2 = self.dudt(t + 0.5 * dt, u + 0.5 * k1 * dt) k3 = self.dudt(t + 0.5 * dt, u + 0.5 * k2 * dt) k4 = self.dudt(t + dt, u + k3 * dt) du = (dt / 6) * (k1 + 2 * k2 + 2 * k3 + k4) return du
[docs] class Euler(CustomSimulationBase): """A homemade simulator based on Euler's method Parameters ---------- config : Configuration, optional the configuration to consider for the simulation References ---------- TODO: put here """ def __init__( self, config: Configuration = None, ): super(Euler, self).__init__(config) def _iterate(self, t, u, dt): """returns the evolution du of u between t and t+dt Here we use the Euler method """ F = self.get_force(u) _, _, _, vx, vy, vz = u.T dvx, dvy, dvz = F.T / self.config.atom.mass * dt dx = vx * dt + 0.5 * dvx * dt dy = vy * dt + 0.5 * dvy * dt dz = vz * dt + 0.5 * dvz * dt du = np.array([dx, dy, dz, dvx, dvy, dvz]).T return du
[docs] class VelocityVerlet(CustomSimulationBase): """A homemade simulator based on velocity verlet method Parameters ---------- config : Configuration, optional the configuration to consider for the simulation References ---------- TODO: put here """ def __init__( self, config: Configuration = None, ): super(VelocityVerlet, self).__init__(config) def _iterate(self, t, u, dt): """returns the evolution du of u between t and t+dt Here we use the Velocity verlet method """ # 1) compute next position F = self.get_force(u) x, y, z, vx, vy, vz = u.T ax, ay, az = F.T / self.config.atom.mass dx = vx * dt + 0.5 * ax * dt**2 dy = vy * dt + 0.5 * ay * dt**2 dz = vz * dt + 0.5 * az * dt**2 # 2) compute next speed u_next = np.array([x + dx, y + dy, z + dz, vx, vy, vz]).T F_next = self.get_force(u_next) ax_next, ay_next, az_next = F_next.T / self.config.atom.mass dvx = 0.5 * (ax + ax_next) * dt dvy = 0.5 * (ay + ay_next) * dt dvz = 0.5 * (az + az_next) * dt du = np.array([dx, dy, dz, dvx, dvy, dvz]).T return du