From 1ceda29b67ac83ec27277f2437b3506f56a0ccd2 Mon Sep 17 00:00:00 2001
From: nguyed99 <nguyed99@zedat.fu-berlin.de>
Date: Thu, 7 Mar 2024 23:19:13 +0100
Subject: [PATCH] Testing with NASA data

---
 jobs/inputs/earth_ephemeris.txt | 20 ++++++++++++
 jobs/inputs/moon_ephemeris.txt  | 20 ++++++++++++
 jobs/inputs/sun_ephemeris.txt   | 20 ++++++++++++
 jobs/src/a_job.py               | 28 -----------------
 jobs/src/integrators.py         |  1 +
 jobs/src/jpl_data_query.py      | 47 ++++++++++++++++++++++++++--
 jobs/src/visualization.py       | 35 +++++++++++++++++++++
 jobs/tests/test_a_job.py        | 41 -------------------------
 tasks/src/direct_simulation.py  | 54 ++++++++++++++++++++++++++++++++-
 9 files changed, 193 insertions(+), 73 deletions(-)
 create mode 100644 jobs/inputs/earth_ephemeris.txt
 create mode 100644 jobs/inputs/moon_ephemeris.txt
 create mode 100644 jobs/inputs/sun_ephemeris.txt
 delete mode 100644 jobs/src/a_job.py
 delete mode 100644 jobs/tests/test_a_job.py

diff --git a/jobs/inputs/earth_ephemeris.txt b/jobs/inputs/earth_ephemeris.txt
new file mode 100644
index 0000000..3075981
--- /dev/null
+++ b/jobs/inputs/earth_ephemeris.txt
@@ -0,0 +1,20 @@
+!$$SOF
+MAKE_EPHEM=YES
+COMMAND=399
+EPHEM_TYPE=VECTORS
+CENTER='coord@10'
+COORD_TYPE=GEODETIC
+SITE_COORD='13.35075983141236,52.497297553950155,0.0354'
+START_TIME='2023-01-01'
+STOP_TIME='2023-05-01'
+STEP_SIZE='1 DAYS'
+VEC_TABLE='2x'
+REF_SYSTEM='ICRF'
+REF_PLANE='ECLIPTIC'
+VEC_CORR='NONE'
+CAL_TYPE='M'
+OUT_UNITS='KM-S'
+VEC_LABELS='YES'
+VEC_DELTA_T='NO'
+CSV_FORMAT='NO'
+OBJ_DATA='YES'
\ No newline at end of file
diff --git a/jobs/inputs/moon_ephemeris.txt b/jobs/inputs/moon_ephemeris.txt
new file mode 100644
index 0000000..79859f9
--- /dev/null
+++ b/jobs/inputs/moon_ephemeris.txt
@@ -0,0 +1,20 @@
+!$$SOF
+MAKE_EPHEM=YES
+COMMAND=301
+EPHEM_TYPE=VECTORS
+CENTER='coord@10'
+COORD_TYPE=GEODETIC
+SITE_COORD='13.35075983141236,52.497297553950155,0.0354'
+START_TIME='2023-01-01'
+STOP_TIME='2023-05-01'
+STEP_SIZE='1 DAYS'
+VEC_TABLE='2x'
+REF_SYSTEM='ICRF'
+REF_PLANE='ECLIPTIC'
+VEC_CORR='NONE'
+CAL_TYPE='M'
+OUT_UNITS='KM-S'
+VEC_LABELS='YES'
+VEC_DELTA_T='NO'
+CSV_FORMAT='NO'
+OBJ_DATA='YES'
\ No newline at end of file
diff --git a/jobs/inputs/sun_ephemeris.txt b/jobs/inputs/sun_ephemeris.txt
new file mode 100644
index 0000000..3b5c007
--- /dev/null
+++ b/jobs/inputs/sun_ephemeris.txt
@@ -0,0 +1,20 @@
+!$$SOF
+MAKE_EPHEM='YES'
+COMMAND='10'
+EPHEM_TYPE='VECTORS'
+CENTER='coord@399'
+COORD_TYPE='GEODETIC'
+SITE_COORD='13.35075983141236,52.497297553950155,0.0354'
+START_TIME='2023-01-01'
+STOP_TIME='2023-05-01'
+STEP_SIZE='1 DAYS'
+VEC_TABLE='2x'
+REF_SYSTEM='ICRF'
+REF_PLANE='ECLIPTIC'
+VEC_CORR='NONE'
+CAL_TYPE='M'
+OUT_UNITS='KM-S'
+VEC_LABELS='YES'
+VEC_DELTA_T='NO'
+CSV_FORMAT='YES'
+OBJ_DATA='NO'
\ No newline at end of file
diff --git a/jobs/src/a_job.py b/jobs/src/a_job.py
deleted file mode 100644
index e07abb8..0000000
--- a/jobs/src/a_job.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""
-Just some toy code for demonstration
-"""
-import numpy as np
-
-
-def sigma_x() -> np.ndarray:
-    """
-    Pauli gate X (or NOT gate) 
-    """
-
-    return np.array([[0, 1], [1, 0]])
-
-
-def sigma_y() -> np.ndarray:
-    """
-    Pauli gate Y
-    """
-
-    return np.array([[0, -1j], [1j, 0]])
-
-
-def sigma_z() -> np.ndarray:
-    """
-    Pauli gate Z
-    """
-
-    return np.array([[1, 0], [0, -1]])
diff --git a/jobs/src/integrators.py b/jobs/src/integrators.py
index 35d513a..79f0ec7 100644
--- a/jobs/src/integrators.py
+++ b/jobs/src/integrators.py
@@ -3,6 +3,7 @@ This module contains 2 integrators, which is at least
 of second consistency order and approrimately preserves 
 the energy over long times.
 """
+
 from collections.abc import Callable
 
 import numpy as np
diff --git a/jobs/src/jpl_data_query.py b/jobs/src/jpl_data_query.py
index cfc3f00..6a2713e 100644
--- a/jobs/src/jpl_data_query.py
+++ b/jobs/src/jpl_data_query.py
@@ -1,4 +1,45 @@
 """
-This module contains functions to query JPL Horizons 
-on-line solar system data (see https://ssd.jpl.nasa.gov/horizons).
-"""
\ No newline at end of file
+This module contains functions to query and parse JPL Horizons 
+on-line solar system data (see https://ssd-api.jpl.nasa.gov/doc/horizons.html).
+"""
+
+import re
+import requests
+
+import numpy as np
+
+
+def query_data(input_file: str) -> str:
+    """
+    Send request to the Horizons API with settings for the desired ephemeris results saved in an input file.
+    """
+    with open(input_file, 'r') as f:
+        file = f.read()
+
+    url = "https://ssd.jpl.nasa.gov/api/horizons_file.api"
+    r = requests.post(url, data={'format': 'json'}, files={'input': file})
+
+    return r.json()['result']
+
+
+def parse_output(output: str) -> np.ndarray:
+    """
+    Parse output of API calls for positions and velocities
+    """
+
+    output = list(filter(bool, output.split("\n")))
+    vector_list = [line.split(",") for line in output if line[1:4] == "XYZ"]
+    vector_list = [list(map(float, re.findall(r"[-+]?\d*\.?\d+(?:[Ee][-+]?\d+)?", i[0]))) for i in vector_list]
+
+    r = np.zeros((len(vector_list), 3), dtype=float)
+    v = np.zeros((len(vector_list), 3), dtype=float)
+
+    for i, vector in enumerate(vector_list):
+        r[i][0] = float(vector[0])
+        r[i][1] = float(vector[1])
+        r[i][2] = float(vector[2])
+        v[i][0] = float(vector[3])
+        v[i][1] = float(vector[4])
+        v[i][2] = float(vector[5])
+
+    return r, v
diff --git a/jobs/src/visualization.py b/jobs/src/visualization.py
index e69de29..584d31a 100644
--- a/jobs/src/visualization.py
+++ b/jobs/src/visualization.py
@@ -0,0 +1,35 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+def plot_trajectory(traj: np.ndarray, mass: list, update_interval=10):
+    """
+    Args:
+    - traj: trajectory with shape (no_time_steps, no_bodies * dim)
+    - mass: masses of bodies
+    """
+
+    n_time_step, n_bodies = traj.shape  #
+    n_bodies //= 3
+
+    fig = plt.figure()
+    ax = fig.add_subplot(projection='3d')
+
+    for i in range(0, n_time_step, update_interval):
+        ax.clear()
+        for j in range(n_bodies):
+            start_idx = j * 3
+            end_idx = (j + 1) * 3
+
+            body_traj = traj[i, start_idx:end_idx]
+            ax.scatter(body_traj[0], body_traj[1], body_traj[2], s=10 * mass[j] / min(mass))
+
+        ax.set_xlabel("X")
+        ax.set_ylabel("Y")
+        ax.set_zlabel("Z")
+        ax.set_title(f"Time step: {i}")
+        ax.set_xticks([])
+        ax.set_yticks([])
+        ax.set_zticks([])
+
+        plt.pause(0.01)
diff --git a/jobs/tests/test_a_job.py b/jobs/tests/test_a_job.py
deleted file mode 100644
index 6a846e8..0000000
--- a/jobs/tests/test_a_job.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import logging
-import unittest
-
-import numpy as np
-
-from jobs.src.a_job import sigma_x, sigma_y, sigma_z
-
-logger = logging.getLogger(__name__)
-logging.basicConfig(level=logging.INFO)
-
-
-class AJobTest(unittest.TestCase):
-
-    def test_a_job(self):
-        """
-        Test functionalities of Pauli gates on single qubits
-        """
-        qubit_0 = np.array([1, 0])
-        qubit_1 = np.array([0, 1])
-
-        # ensure input is a single qubit
-        self.assertTrue(qubit_0.shape[0] == 2)
-        self.assertTrue(qubit_1.shape[0] == 2)
-
-        # test Pauli gate X
-        self.assertTrue(np.array_equal(np.dot(sigma_x(), qubit_0), qubit_1))
-        self.assertTrue(np.array_equal(np.dot(sigma_x(), qubit_1), qubit_0))
-
-        # test Pauli gate Y
-        self.assertTrue(np.array_equal(np.dot(sigma_y(), qubit_0), 1j * qubit_1))
-        self.assertTrue(np.array_equal(np.dot(sigma_y(), qubit_1), -1j * qubit_0))
-
-        # test Pauli gate Z
-        self.assertTrue(np.array_equal(np.dot(sigma_z(), qubit_0), qubit_0))
-        self.assertTrue(np.array_equal(np.dot(sigma_z(), qubit_1), -qubit_1))
-
-        logger.info("Logging for fun!")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tasks/src/direct_simulation.py b/tasks/src/direct_simulation.py
index f7645d9..2d54a92 100644
--- a/tasks/src/direct_simulation.py
+++ b/tasks/src/direct_simulation.py
@@ -2,4 +2,56 @@
 This module contains functions for a direct simulation
 of gravitational n-body system using realistic data for various
 initial configurations
-"""
\ No newline at end of file
+"""
+
+import logging
+
+import numpy as np
+
+from jobs.src.integrators import verlet
+from jobs.src.jpl_data_query import (parse_output, query_data)
+from jobs.src.system import GravitationalSystem
+from jobs.src.visualization import plot_trajectory
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+INPUT_PATH = "jobs/inputs/"
+
+r_E, v_E = parse_output(query_data(INPUT_PATH + "earth_ephemeris.txt"))
+r_M, v_M = parse_output(query_data(INPUT_PATH + "moon_ephemeris.txt"))
+
+# system settings
+M_S = 1  # solar mass
+M_E = 3.00e-6 * M_S
+M_L = 3.69e-8 * M_S
+
+G = 4 * np.pi**2  # AU^3 / m / yr^2
+
+# simulation settings
+T = 0.3  # yr
+n_order = 6
+dt = 4**(-n_order)  # yr
+
+
+# force computation
+def force(q: np.ndarray) -> np.ndarray:
+    r1 = q[0:3]
+    r2 = q[3:6]
+
+    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()
+
+
+system = GravitationalSystem(r0=np.hstack((r_E[0], r_M[0])),
+                             v0=np.hstack((v_E[0], v_M[0])),
+                             m=np.array([M_E, M_E, M_E, M_L, M_L, M_L]),
+                             t=np.linspace(0, T, int(T // dt)),
+                             force=force,
+                             solver=verlet)
+
+t, p, q = system.simulation()
+
+plot_trajectory(q, mass=[M_E, M_L])
-- 
GitLab