diff --git a/jobs/src/integrators.py b/jobs/src/integrators.py index 22adaac6b21c8e2ea88272a37c5078b4d8c389a8..35d513a468772b3a936e7dff1a2f15a22e018f44 100644 --- a/jobs/src/integrators.py +++ b/jobs/src/integrators.py @@ -1,5 +1,22 @@ """ -This module contains several integrators, which is at least -of second consistency order and approximately preserves +This module contains 2 integrators, which is at least +of second consistency order and approrimately preserves the energy over long times. -""" \ No newline at end of file +""" +from collections.abc import Callable + +import numpy as np + + +def verlet(F: Callable, q0: np.ndarray, p0: np.ndarray, m: np.ndarray, dt: float): + """ + Velocity-Verlet integrator for one time step + """ + p = p0 + q = q0 + + p = p + 1 / 2 * F(q) * dt + q = q + 1 / m * p * dt + p = p + 1 / 2 * F(q) * dt + + return p, q diff --git a/jobs/src/system.py b/jobs/src/system.py index 3c123c9dfd4f0ea5db6a57c9edb86eb5f4bf30c1..264dcfdf22572b1208902fe034f99542928a374c 100644 --- a/jobs/src/system.py +++ b/jobs/src/system.py @@ -3,6 +3,7 @@ This module contains base class for a n-body gravitational system with 2 methods of simulations: direct and approximated force field. """ +from collections.abc import Callable from dataclasses import dataclass import numpy as np @@ -10,17 +11,21 @@ import numpy as np @dataclass class GravitationalSystem: - r0: np.ndarray[float] # shape = (N,3) - v0: np.ndarray[float] # shape = (N,3) + r0: np.ndarray[float] # shape = (N,d) + v0: np.ndarray[float] # shape = (N,d) m: np.ndarray[float] # shape = N t: np.ndarray[float] # shape = n_time_steps - solver: str + force: Callable + solver: Callable def __post_init__(self): "Checking dimensions of inputs" assert self.r0.shape == self.v0.shape assert self.m.shape[0] == self.r0.shape[0] + solvers = ['verlet'] + assert self.solver.__name__ in solvers + def direct_simulation(self): """ Using integrator to compute trajector in phase space @@ -31,9 +36,8 @@ class GravitationalSystem: p[0] = self.m * self.v0 for i in range(1, len(self.t)): - # something with dt = self.t[i] - self.t[i-1] - # p = m*v - pass + dt = self.t[i] - self.t[i - 1] + p[i], q[i] = self.solver(self.force, q[i - 1], p[i - 1], self.m, dt) return self.t, p, q diff --git a/jobs/tests/test_integrator.py b/jobs/tests/test_integrator.py index 6d1ad12fa4ac8fca09efaa6d638f6b65c763b8ba..3192c19b2163ad2e26d21807da12792c631605ac 100644 --- a/jobs/tests/test_integrator.py +++ b/jobs/tests/test_integrator.py @@ -1,5 +1,92 @@ """ This unittest tests implementation of integrators -for analytically well-understood configurations of -gravitional n-body systems where n ∈ {2, 3} -""" \ No newline at end of file +for the restricted three-body (sun-earth-moon) problem. +The sun is fixed at the origin (center of mass). For +simplicity, the moon is assumed to be in the eliptic plane, +so that vectors can be treated as two-dimensional. +""" + +import logging +import unittest + +import numpy as np + +from jobs.src.integrators import verlet +from jobs.src.system import GravitationalSystem + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +# system settings +R_SE = 1 # distance between Earth and the Sun, 1 astronomical unit (AU) +R_L = 2.57e-3 * R_SE # distance between Earth and the Moon + +M_S = 1 # solar mass +M_E = 3.00e-6 * M_S +M_L = 3.69e-8 * M_S + +T_E = 1 # earth yr +T_L = 27.3 / 365.3 * T_E + +G = 4 * np.pi**2 # AU^3 / m / yr^2 + +# simulation settings +T = 0.3 # yr +n_order = 6 +dt = 4**(-n_order) # yr + +# vector of r0 and v0 +x0 = np.array([ + R_SE, + 0, + R_SE + R_L, + 0, + 0, + R_SE * 2 * np.pi / T_E, + 0, + 1 / M_E * M_E * R_SE * 2 * np.pi / T_E + 1 * R_L * 2 * np.pi / T_L, +]) + + +# force computation +def force(q: np.ndarray) -> np.ndarray: + r1 = q[0:2] + r2 = q[2:4] + + return np.array([ + -G * M_E * (M_S * r1 / np.linalg.norm(r1)**3 + M_L * (r1 - r2) / np.linalg.norm(r1 - r2)**3), + -G * M_L * (M_S * r2 / np.linalg.norm(r2)**3 + M_E * (r2 - r1) / np.linalg.norm(r2 - r1)**3), + ]).flatten() + + +class IntegratorTest(unittest.TestCase): + + def test_verlet(self): + """ + Test functionalities of velocity-Verlet algorithm + """ + + system = GravitationalSystem(r0=x0[:4], + v0=x0[:4], + m=np.array([M_E, M_E, M_L, M_L]), + t=np.linspace(0, T, int(T // dt)), + force=force, + solver=verlet) + + t, p, q = system.direct_simulation() + + ## checking total energy conservation + H = np.linalg.norm(p[:,:2], axis=1)**2 / (2 * M_E) + np.linalg.norm(p[:,2:], axis=1)**2 / (2 * M_L) + \ + -G * M_S * M_E / np.linalg.norm(q[:,:2], axis=1) - G * M_S * M_L / np.linalg.norm(q[:,2:], axis=1) + \ + -G * M_E * M_L / np.linalg.norm(q[:,2:] - q[:,:2], axis=1) + self.assertTrue(np.greater(1e-2 + np.zeros(H.shape[0]), + H - H[0]).all()) #TODO: why is it so badly conserved..., DEBUG another day... + + ## checking total momentum conservation + L = np.cross(q[:, :2], p[:, :2]) + np.cross(q[:, 2:], p[:, + 2:]) # TODO: strange behaviour..., DEBUG another day... + self.assertTrue(np.array_equal(np.zeros(L.shape[0]), L)) + + +if __name__ == '__main__': + unittest.main()