Source code for einsteinpy.integrators.fantasy

import warnings

import numpy as np

from .utils import _Z, _flow_A, _flow_B, _flow_mixed


[docs] class GeodesicIntegrator: """ Geodesic Integrator, based on [1]_. This module uses Forward Mode Automatic Differentiation to calculate metric derivatives to machine precision leading to stable simulations. References ---------- .. [1] Christian, Pierre and Chan, Chi-Kwan; "FANTASY: User-Friendly Symplectic Geodesic Integrator for Arbitrary Metrics with Automatic Differentiation"; `2021 ApJ 909 67 <https://doi.org/10.3847/1538-4357/abdc28>`__ """ # TODO: Update arXiv attributions to ApJ (See #572) def __init__( self, metric, metric_params, q0, p0, time_like=True, steps=100, delta=0.5, rtol=1e-2, atol=1e-2, order=2, omega=1.0, suppress_warnings=False, ): """ Constructor Parameters ---------- metric : callable Metric Function. Currently, these metrics are supported: 1. Schwarzschild 2. Kerr 3. KerrNewman metric_params : array_like Tuple of parameters to pass to the metric E.g., ``(a,)`` for Kerr q0 : array_like Initial 4-Position p0 : array_like Initial 4-Momentum time_like : bool, optional Determines type of Geodesic ``True`` for Time-like geodesics ``False`` for Null-like geodesics Defaults to ``True`` steps : int Number of integration steps Defaults to ``50`` delta : float Initial integration step-size Defaults to ``0.5`` rtol : float Relative Tolerance Defaults to ``1e-2`` atol : float Absolute Tolerance Defaults to ``1e-2`` order : int Integration Order Defaults to ``2`` omega : float Coupling between Hamiltonian Flows Smaller values imply smaller integration error, but too small values can make the equation of motion non-integrable. For non-capture trajectories, ``omega = 1.0`` is recommended. For trajectories, that either lead to a capture or a grazing geodesic, a decreased value of ``0.01`` or less is recommended. Defaults to ``1.0`` suppress_warnings : bool Whether to suppress warnings during simulation Warnings are shown for every step, where numerical errors exceed specified tolerance (controlled by ``rtol`` and ``atol``) Defaults to ``False`` Raises ------ NotImplementedError If ``order`` is not in [2, 4, 6, 8] """ ORDERS = { 2: self._ord_2, 4: self._ord_4, 6: self._ord_6, 8: self._ord_8, } self.metric = metric self.metric_params = metric_params self.q0 = q0 self.p0 = p0 self.time_like = time_like self.steps = steps self.delta = delta self.omega = omega if order not in ORDERS: raise NotImplementedError( f"Order {order} integrator has not been implemented." ) self.order = order self.integrator = ORDERS[order] self.rtol = rtol self.atol = atol self.suppress_warnings = suppress_warnings self.step_num = 0 self.res_list = [q0, p0, q0, p0] self.results = list() def __str__(self): return f"""{self.__class__.__name__}(\n\ metric : {self.metric}\n\ metric_params : {self.metric_params}\n\ q0 : {self.q0},\n\ p0 : {self.p0},\n\ time_like : {self.time_like},\n\ steps : {self.steps},\n\ delta : {self.delta},\n\ omega : {self.omega},\n\ order : {self.order},\n\ rtol : {self.rtol},\n\ atol : {self.atol}\n\ suppress_warnings : {self.suppress_warnings} )""" def __repr__(self): return self.__str__() def _ord_2(self, q1, p1, q2, p2, delta): """ Order 2 Integration Scheme References ---------- .. [1] Christian, Pierre and Chan, Chi-Kwan; "FANTASY : User-Friendly Symplectic Geodesic Integrator for Arbitrary Metrics with Automatic Differentiation"; `2021 ApJ 909 67 <https://doi.org/10.3847/1538-4357/abdc28>`__ """ dl, omg = delta, self.omega g = self.metric g_prms = self.metric_params HA1 = np.array( [ q1, _flow_A(g, g_prms, q1, p1, q2, p2, 0.5 * dl)[1], _flow_A(g, g_prms, q1, p1, q2, p2, 0.5 * dl)[0], p2, ] ) HB1 = np.array( [ _flow_B(g, g_prms, HA1[0], HA1[1], HA1[2], HA1[3], 0.5 * dl)[0], HA1[1], HA1[2], _flow_B(g, g_prms, HA1[0], HA1[1], HA1[2], HA1[3], 0.5 * dl)[1], ] ) HC = _flow_mixed(HB1[0], HB1[1], HB1[2], HB1[3], dl, omg) HB2 = np.array( [ _flow_B(g, g_prms, HC[0], HC[1], HC[2], HC[3], 0.5 * dl)[0], HC[1], HC[2], _flow_B(g, g_prms, HC[0], HC[1], HC[2], HC[3], 0.5 * dl)[1], ] ) HA2 = np.array( [ HB2[0], _flow_A(g, g_prms, HB2[0], HB2[1], HB2[2], HB2[3], 0.5 * dl)[1], _flow_A(g, g_prms, HB2[0], HB2[1], HB2[2], HB2[3], 0.5 * dl)[0], HB2[3], ] ) return HA2 def _ord_4(self, q1, p1, q2, p2, delta): """ Order 4 Integration Scheme References ---------- .. [1] Yoshida, Haruo, "Construction of higher order symplectic integrators"; Physics Letters A, vol. 150, no. 5-7, pp. 262-268, 1990. `DOI: <https://doi.org/10.1016/0375-9601(90)90092-3>`__ """ dl = delta Z0, Z1 = _Z(self.order) step1 = self._ord_2(q1, p1, q2, p2, dl * Z1) step2 = self._ord_2(step1[0], step1[1], step1[2], step1[3], dl * Z0) step3 = self._ord_2(step2[0], step2[1], step2[2], step2[3], dl * Z1) return step3 def _ord_6(self, q1, p1, q2, p2, delta): """ Order 6 Integration Scheme References ---------- .. [1] Yoshida, Haruo, "Construction of higher order symplectic integrators"; Physics Letters A, vol. 150, no. 5-7, pp. 262-268, 1990. `DOI: <https://doi.org/10.1016/0375-9601(90)90092-3>`__ """ dl = delta Z0, Z1 = _Z(self.order) step1 = self._ord_4(q1, p1, q2, p2, dl * Z1) step2 = self._ord_4(step1[0], step1[1], step1[2], step1[3], dl * Z0) step3 = self._ord_4(step2[0], step2[1], step2[2], step2[3], dl * Z1) return step3 def _ord_8(self, q1, p1, q2, p2, delta): """ Order 8 Integration Scheme References ---------- .. [1] Yoshida, Haruo, "Construction of higher order symplectic integrators"; Physics Letters A, vol. 150, no. 5-7, pp. 262-268, 1990. `DOI: <https://doi.org/10.1016/0375-9601(90)90092-3>`__ """ dl = delta Z0, Z1 = _Z(self.order) step1 = self._ord_6(q1, p1, q2, p2, dl * Z1) step2 = self._ord_6(step1[0], step1[1], step1[2], step1[3], dl * Z0) step3 = self._ord_6(step2[0], step2[1], step2[2], step2[3], dl * Z1) return step3
[docs] def step(self): """ Advances integration by one step """ rl = self.res_list arr = self.integrator(rl[0], rl[1], rl[2], rl[3], self.delta) self.res_list = arr self.step_num += 1 # Stability check if not self.suppress_warnings: g = self.metric g_prms = self.metric_params q1 = arr[0] p1 = arr[1] # Ignoring # q_2 = arr[2] # p_2 = arr[3] const = -int(self.time_like) # g.p.p ~ -1 or 0 (const) if not np.allclose( g(q1, *g_prms) @ p1 @ p1, const, rtol=self.rtol, atol=self.atol ): warnings.warn( f"Numerical error has exceeded specified tolerance at step = {self.step_num}.", RuntimeWarning, ) self.results.append(self.res_list)