adam-core 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. adam_core/__init__.py +0 -0
  2. adam_core/constants.py +60 -0
  3. adam_core/coordinates/__init__.py +35 -0
  4. adam_core/coordinates/cartesian.py +360 -0
  5. adam_core/coordinates/cometary.py +374 -0
  6. adam_core/coordinates/conversions.py +176 -0
  7. adam_core/coordinates/covariances.py +548 -0
  8. adam_core/coordinates/io.py +120 -0
  9. adam_core/coordinates/jacobian.py +69 -0
  10. adam_core/coordinates/keplerian.py +351 -0
  11. adam_core/coordinates/origin.py +111 -0
  12. adam_core/coordinates/residuals.py +214 -0
  13. adam_core/coordinates/spherical.py +301 -0
  14. adam_core/coordinates/tests/__init__.py +0 -0
  15. adam_core/coordinates/tests/test_benchmarks.py +39 -0
  16. adam_core/coordinates/tests/test_cartesian.py +32 -0
  17. adam_core/coordinates/tests/test_covariances.py +105 -0
  18. adam_core/coordinates/tests/test_origin.py +54 -0
  19. adam_core/coordinates/tests/test_residuals.py +194 -0
  20. adam_core/coordinates/tests/test_spherical.py +38 -0
  21. adam_core/coordinates/tests/test_transforms_spherical.py +93 -0
  22. adam_core/coordinates/times.py +28 -0
  23. adam_core/coordinates/transform.py +1324 -0
  24. adam_core/dynamics/__init__.py +4 -0
  25. adam_core/dynamics/barker.py +33 -0
  26. adam_core/dynamics/kepler.py +204 -0
  27. adam_core/dynamics/tests/__init__.py +0 -0
  28. adam_core/dynamics/tests/test_kepler.py +194 -0
  29. adam_core/dynamics/tests/test_tisserand.py +50 -0
  30. adam_core/dynamics/tisserand.py +65 -0
  31. adam_core/orbits/__init__.py +3 -0
  32. adam_core/orbits/classification.py +51 -0
  33. adam_core/orbits/orbits.py +70 -0
  34. adam_core/orbits/query/__init__.py +3 -0
  35. adam_core/orbits/query/horizons.py +238 -0
  36. adam_core/orbits/query/sbdb.py +237 -0
  37. adam_core/orbits/query/tests/__init__.py +43 -0
  38. adam_core/orbits/query/tests/test_sbdb.py +107 -0
  39. adam_core/orbits/tests/__init__.py +0 -0
  40. adam_core/orbits/tests/test_benchmarks.py +21 -0
  41. adam_core/orbits/tests/test_orbits.py +43 -0
  42. adam_core/propagator/__init__.py +4 -0
  43. adam_core/propagator/propagator.py +176 -0
  44. adam_core/propagator/pyoorb.py +288 -0
  45. adam_core/propagator/tests/__init__.py +0 -0
  46. adam_core/propagator/tests/test_utils.py +36 -0
  47. adam_core/propagator/utils.py +72 -0
  48. adam_core/utils/__init__.py +1 -0
  49. adam_core/utils/helpers/__init__.py +2 -0
  50. adam_core/utils/helpers/data/__init__.py +0 -0
  51. adam_core/utils/helpers/data/sample_orbits.csv +28 -0
  52. adam_core/utils/helpers/orbits.py +97 -0
  53. adam_core/utils/helpers/tests/__init__.py +0 -0
  54. adam_core/utils/helpers/tests/test_orbits.py +25 -0
  55. adam_core/utils/mpc.py +473 -0
  56. adam_core/utils/spice.py +105 -0
  57. adam_core/utils/tests/__init__.py +0 -0
  58. adam_core/utils/tests/test_mpc.py +213 -0
  59. adam_core-0.1.0.dist-info/LICENSE.md +60 -0
  60. adam_core-0.1.0.dist-info/METADATA +262 -0
  61. adam_core-0.1.0.dist-info/RECORD +63 -0
  62. adam_core-0.1.0.dist-info/WHEEL +5 -0
  63. adam_core-0.1.0.dist-info/top_level.txt +1 -0
adam_core/__init__.py ADDED
File without changes
adam_core/constants.py ADDED
@@ -0,0 +1,60 @@
1
+ import numpy as np
2
+
3
+ __all__ = [
4
+ "KM_P_AU",
5
+ "S_P_DAY",
6
+ "Constants",
7
+ "DE43X",
8
+ "DE44X",
9
+ ]
10
+
11
+ # km in an au
12
+ KM_P_AU = 149597870.700
13
+ # seconds in a day
14
+ S_P_DAY = 86400.0
15
+
16
+
17
+ class _Constants:
18
+ def __init__(self, C=None, MU=None, R_Earth=None, Obliquity=None):
19
+ self.C = C
20
+ self.MU = MU
21
+ self.R_EARTH = R_Earth
22
+ self.OBLIQUITY = Obliquity
23
+
24
+ # Transformation matrix from Equatorial J2000 to Ecliptic J2000
25
+ self.TRANSFORM_EQ2EC = np.array(
26
+ [
27
+ [1, 0, 0],
28
+ [0, np.cos(self.OBLIQUITY), np.sin(self.OBLIQUITY)],
29
+ [0, -np.sin(self.OBLIQUITY), np.cos(self.OBLIQUITY)],
30
+ ]
31
+ )
32
+
33
+ # Transformation matrix from Ecliptic J2000 to Equatorial J2000
34
+ self.TRANSFORM_EC2EQ = self.TRANSFORM_EQ2EC.T
35
+ return
36
+
37
+
38
+ DE43X_CONSTANTS = {
39
+ # Speed of Light : au / d (299792.458 km / s -- DE430/DE431)
40
+ "C": 299792.458 / KM_P_AU * S_P_DAY,
41
+ # Standard Gravitational Parameter -- Sun : au**3 / d**2 (0.295912208285591100E-3 -- DE431/DE430)
42
+ "MU": 0.295912208285591100e-3,
43
+ # Earth Equatorial Radius: au (6378.1363 km -- DE431/DE430)
44
+ "R_Earth": 6378.1363 / KM_P_AU,
45
+ # Mean Obliquity at J2000: radians (84381.448 arcseconds -- DE431/DE430)
46
+ "Obliquity": 84381.448 * np.pi / (180.0 * 3600.0),
47
+ }
48
+ Constants = DE43X = _Constants(**DE43X_CONSTANTS)
49
+
50
+ DE44X_CONSTANTS = {
51
+ # Speed of Light : au / d (299792.458 km / s -- DE430/DE431)
52
+ "C": 299792.458 / KM_P_AU * S_P_DAY,
53
+ # Standard Gravitational Parameter -- Sun : au**3 / d**2 (0.29591220828411956E-03 -- DE441/DE440)
54
+ "MU": 0.29591220828411956e-03,
55
+ # Earth Equatorial Radius: au (6378.1363 km -- DE431/DE430)
56
+ "R_Earth": 6378.1363 / KM_P_AU,
57
+ # Mean Obliquity at J2000: radians (84381.448 arcseconds -- DE431/DE430)
58
+ "Obliquity": 84381.448 * np.pi / (180.0 * 3600.0),
59
+ }
60
+ DE44X = _Constants(**DE44X_CONSTANTS)
@@ -0,0 +1,35 @@
1
+ # flake8: noqa: F401
2
+ from .cartesian import CARTESIAN_COLS, CARTESIAN_UNITS, CartesianCoordinates
3
+ from .cometary import COMETARY_COLS, COMETARY_UNITS, CometaryCoordinates
4
+ from .conversions import convert_coordinates
5
+ from .covariances import (
6
+ CoordinateCovariances,
7
+ covariances_from_df,
8
+ covariances_to_df,
9
+ covariances_to_table,
10
+ sample_covariance,
11
+ sigmas_from_df,
12
+ sigmas_to_df,
13
+ transform_covariances_jacobian,
14
+ transform_covariances_sampling,
15
+ )
16
+ from .io import coords_from_dataframe, coords_to_dataframe
17
+ from .jacobian import calc_jacobian
18
+ from .keplerian import KEPLERIAN_COLS, KEPLERIAN_UNITS, KeplerianCoordinates
19
+ from .origin import Origin
20
+ from .residuals import Residuals
21
+ from .spherical import SPHERICAL_COLS, SPHERICAL_UNITS, SphericalCoordinates
22
+ from .times import Times
23
+ from .transform import (
24
+ _cartesian_to_cometary,
25
+ _cartesian_to_keplerian,
26
+ _cartesian_to_keplerian6,
27
+ _cometary_to_cartesian,
28
+ _keplerian_to_cartesian_a,
29
+ _keplerian_to_cartesian_p,
30
+ _keplerian_to_cartesian_q,
31
+ cartesian_to_cometary,
32
+ cartesian_to_keplerian,
33
+ cometary_to_cartesian,
34
+ transform_coordinates,
35
+ )
@@ -0,0 +1,360 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING, Literal
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from astropy import units as u
7
+ from quivr import Float64Column, StringAttribute, Table
8
+
9
+ from .covariances import CoordinateCovariances
10
+ from .io import coords_from_dataframe, coords_to_dataframe
11
+ from .origin import Origin
12
+ from .times import Times
13
+
14
+ if TYPE_CHECKING:
15
+ from .cometary import CometaryCoordinates
16
+ from .keplerian import KeplerianCoordinates
17
+ from .spherical import SphericalCoordinates
18
+
19
+ __all__ = ["CartesianCoordinates", "CARTESIAN_COLS", "CARTESIAN_UNITS"]
20
+
21
+ CARTESIAN_COLS = {}
22
+ CARTESIAN_UNITS = {}
23
+ for i in ["x", "y", "z"]:
24
+ CARTESIAN_COLS[i] = i
25
+ CARTESIAN_UNITS[i] = u.au
26
+ for i in ["vx", "vy", "vz"]:
27
+ CARTESIAN_COLS[i] = i
28
+ CARTESIAN_UNITS[i] = u.au / u.d
29
+
30
+ COVARIANCE_ROTATION_TOLERANCE = 1e-25
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class CartesianCoordinates(Table):
35
+
36
+ x = Float64Column(nullable=True)
37
+ y = Float64Column(nullable=True)
38
+ z = Float64Column(nullable=True)
39
+ vx = Float64Column(nullable=True)
40
+ vy = Float64Column(nullable=True)
41
+ vz = Float64Column(nullable=True)
42
+ times = Times.as_column(nullable=True)
43
+ covariances = CoordinateCovariances.as_column(nullable=True)
44
+ origin = Origin.as_column(nullable=False)
45
+ frame = StringAttribute()
46
+
47
+ @property
48
+ def values(self) -> np.ndarray:
49
+ return np.array(self.table.select(["x", "y", "z", "vx", "vy", "vz"])).T
50
+
51
+ @property
52
+ def r(self) -> np.ndarray:
53
+ """
54
+ Position vector.
55
+ """
56
+ return np.array(self.table.select(["x", "y", "z"]))
57
+
58
+ @property
59
+ def r_mag(self) -> np.ndarray:
60
+ """
61
+ Magnitude of the position vector.
62
+ """
63
+ return np.linalg.norm(self.r, axis=1)
64
+
65
+ @property
66
+ def r_hat(self) -> np.ndarray:
67
+ """
68
+ Unit vector in the direction of the position vector.
69
+ """
70
+ return self.r / self.r_mag[:, None]
71
+
72
+ @property
73
+ def v(self) -> np.ndarray:
74
+ """
75
+ Velocity vector.
76
+ """
77
+ return np.array(self.table.select(["vx", "vy", "vz"]))
78
+
79
+ @property
80
+ def v_mag(self) -> np.ndarray:
81
+ """
82
+ Magnitude of the velocity vector.
83
+ """
84
+ return np.linalg.norm(self.v, axis=1)
85
+
86
+ @property
87
+ def v_hat(self) -> np.ndarray:
88
+ """
89
+ Unit vector in the direction of the velocity vector.
90
+ """
91
+ return self.v / self.v_mag[:, None]
92
+
93
+ @property
94
+ def sigma_x(self) -> np.ndarray:
95
+ """
96
+ 1-sigma uncertainty in the X-coordinate.
97
+ """
98
+ return self.covariances.sigmas[:, 0]
99
+
100
+ @property
101
+ def sigma_y(self) -> np.ndarray:
102
+ """
103
+ 1-sigma uncertainty in the Y-coordinate.
104
+ """
105
+ return self.covariances.sigmas[:, 1]
106
+
107
+ @property
108
+ def sigma_z(self) -> np.ndarray:
109
+ """
110
+ 1-sigma uncertainty in the Z-coordinate.
111
+ """
112
+ return self.covariances.sigmas[:, 2]
113
+
114
+ @property
115
+ def sigma_vx(self) -> np.ndarray:
116
+ """
117
+ 1-sigma uncertainty in the X-coordinate velocity.
118
+ """
119
+ return self.covariances.sigmas[:, 3]
120
+
121
+ @property
122
+ def sigma_vy(self) -> np.ndarray:
123
+ """
124
+ 1-sigma uncertainty in the Y-coordinate velocity.
125
+ """
126
+ return self.covariances.sigmas[:, 4]
127
+
128
+ @property
129
+ def sigma_vz(self) -> np.ndarray:
130
+ """
131
+ 1-sigma uncertainty in the Z-coordinate velocity.
132
+ """
133
+ return self.covariances.sigmas[:, 5]
134
+
135
+ @property
136
+ def sigma_r(self) -> np.ndarray:
137
+ """
138
+ 1-sigma uncertainties in the position vector.
139
+ """
140
+ return np.sqrt(self.covariances.sigmas[:, 0:3])
141
+
142
+ @property
143
+ def sigma_r_mag(self) -> np.ndarray:
144
+ """
145
+ 1-sigma uncertainty in the position.
146
+ """
147
+ return np.sqrt(np.sum(self.covariances.sigmas[:, 0:3] ** 2, axis=1))
148
+
149
+ @property
150
+ def sigma_v(self) -> np.ndarray:
151
+ """
152
+ 1-sigma uncertainties in the velocity vector.
153
+ """
154
+ return np.sqrt(self.covariances.sigmas[:, 3:6])
155
+
156
+ @property
157
+ def sigma_v_mag(self) -> np.ndarray:
158
+ """
159
+ 1-sigma uncertainty in the velocity vector.
160
+ """
161
+ return np.sqrt(np.sum(self.covariances.sigmas[:, 3:6] ** 2, axis=1))
162
+
163
+ def rotate(
164
+ self, rotation_matrix: np.ndarray, frame_out: str
165
+ ) -> "CartesianCoordinates":
166
+ """
167
+ Rotate Cartesian coordinates and their covariances by the given rotation matrix.
168
+
169
+ Covariance matrices are also rotated. Rotations will sometimes result
170
+ in covariance matrix elements very near zero but not exactly zero. Any
171
+ elements that are smaller than +-1e-25 are rounded down to 0.
172
+
173
+ Parameters
174
+ ----------
175
+ matrix : `~numpy.ndarray` (6, 6)
176
+ Rotation matrix.
177
+ frame_out : str
178
+ Name of the frame to which coordinates are being rotated.
179
+
180
+ Returns
181
+ -------
182
+ CartesianCoordinates : `~adam_core.coordinates.cartesian.CartesianCoordinates`
183
+ Rotated Cartesian coordinates and their covariances.
184
+ """
185
+ # Extract coordinate values into a masked array and mask NaNss
186
+ masked_coords = np.ma.masked_array(self.values, fill_value=np.NaN)
187
+ masked_coords.mask = np.isnan(masked_coords.data)
188
+
189
+ # Rotate coordinates
190
+ coords_rotated = np.ma.dot(masked_coords, rotation_matrix.T, strict=False)
191
+
192
+ # Extract covariances
193
+ masked_covariances = np.ma.masked_array(
194
+ self.covariances.to_matrix(), fill_value=0.0
195
+ )
196
+ masked_covariances.mask = np.isnan(masked_covariances.data)
197
+
198
+ # Rotate covariances
199
+ covariances_rotated = (
200
+ rotation_matrix @ masked_covariances.filled() @ rotation_matrix.T
201
+ )
202
+ # Reset the mask to the original mask
203
+ covariances_rotated[masked_covariances.mask] = np.NaN
204
+
205
+ # Check if any covariance elements are near zero, if so set them to zero
206
+ near_zero = len(
207
+ covariances_rotated[
208
+ np.abs(covariances_rotated) < COVARIANCE_ROTATION_TOLERANCE
209
+ ]
210
+ )
211
+ if near_zero > 0:
212
+ logger.debug(
213
+ f"{near_zero} covariance elements are within {COVARIANCE_ROTATION_TOLERANCE:.0e}"
214
+ " of zero after rotation, setting these elements to 0."
215
+ )
216
+ covariances_rotated = np.where(
217
+ np.abs(covariances_rotated) < COVARIANCE_ROTATION_TOLERANCE,
218
+ 0,
219
+ covariances_rotated,
220
+ )
221
+
222
+ coords = self.from_kwargs(
223
+ x=coords_rotated[:, 0],
224
+ y=coords_rotated[:, 1],
225
+ z=coords_rotated[:, 2],
226
+ vx=coords_rotated[:, 3],
227
+ vy=coords_rotated[:, 4],
228
+ vz=coords_rotated[:, 5],
229
+ times=self.times,
230
+ covariances=CoordinateCovariances.from_matrix(covariances_rotated),
231
+ origin=self.origin,
232
+ frame=frame_out,
233
+ )
234
+ return coords
235
+
236
+ def translate(self, vector: np.ndarray, origin_out: str) -> "CartesianCoordinates":
237
+ """
238
+ Translate Cartesian coordinates by the given vector.
239
+
240
+ Parameters
241
+ ----------
242
+ vector : `~numpy.ndarray` (6,) or (N, 6)
243
+ Translation vector. If a single vector is given, it is applied to all coordinates.
244
+ If an array of vectors is given, each vector is applied to the corresponding coordinate.
245
+ origin_out : str
246
+ Name of the origin to which coordinates are being translated.
247
+
248
+ Returns
249
+ -------
250
+ CartesianCoordinates : `~adam_core.coordinates.cartesian.CartesianCoordinates`
251
+ Translated Cartesian coordinates and their covariances.
252
+ """
253
+ N = len(self)
254
+ if vector.shape == (6,):
255
+ vector_ = vector.reshape(1, 6)
256
+ elif vector.shape == (N, 6):
257
+ vector_ = vector
258
+ else:
259
+ raise ValueError(f"Expected vector to have shape (6,) or ({N}, 6).")
260
+
261
+ # Extract coordinate values into a masked array and mask NaNss
262
+ masked_coords = np.ma.masked_array(self.values, fill_value=np.NaN)
263
+ masked_coords.mask = np.isnan(masked_coords.data)
264
+
265
+ # Translate coordinates
266
+ coords_translated = (masked_coords + vector_).filled()
267
+
268
+ coords = self.from_kwargs(
269
+ x=coords_translated[:, 0],
270
+ y=coords_translated[:, 1],
271
+ z=coords_translated[:, 2],
272
+ vx=coords_translated[:, 3],
273
+ vy=coords_translated[:, 4],
274
+ vz=coords_translated[:, 5],
275
+ times=self.times,
276
+ covariances=self.covariances,
277
+ origin=Origin.from_kwargs(code=[origin_out] * len(self)),
278
+ frame=self.frame,
279
+ )
280
+ return coords
281
+
282
+ def to_cometary(self) -> "CometaryCoordinates":
283
+ from .cometary import CometaryCoordinates
284
+
285
+ return CometaryCoordinates.from_cartesian(self)
286
+
287
+ @classmethod
288
+ def from_cometary(cls, cometary: "CometaryCoordinates") -> "CartesianCoordinates":
289
+ return cometary.to_cartesian()
290
+
291
+ def to_keplerian(self) -> "KeplerianCoordinates":
292
+ from .keplerian import KeplerianCoordinates
293
+
294
+ return KeplerianCoordinates.from_cartesian(self)
295
+
296
+ @classmethod
297
+ def from_keplerian(
298
+ cls, keplerian: "KeplerianCoordinates"
299
+ ) -> "CartesianCoordinates":
300
+ return keplerian.to_cartesian()
301
+
302
+ def to_spherical(self) -> "SphericalCoordinates":
303
+ from .spherical import SphericalCoordinates
304
+
305
+ return SphericalCoordinates.from_cartesian(self)
306
+
307
+ @classmethod
308
+ def from_spherical(
309
+ cls, spherical: "SphericalCoordinates"
310
+ ) -> "CartesianCoordinates":
311
+ return spherical.to_cartesian()
312
+
313
+ def to_dataframe(
314
+ self, sigmas: bool = False, covariances: bool = True
315
+ ) -> pd.DataFrame:
316
+ """
317
+ Convert coordinates to a pandas DataFrame.
318
+
319
+ Parameters
320
+ ----------
321
+ sigmas : bool, optional
322
+ If True, include 1-sigma uncertainties in the DataFrame.
323
+ covariances : bool, optional
324
+ If True, include covariance matrices in the DataFrame. Covariance matrices
325
+ will be split into 21 columns, with the lower triangular elements stored.
326
+
327
+ Returns
328
+ -------
329
+ df : `~pandas.Dataframe`
330
+ DataFrame containing coordinates.
331
+ """
332
+ return coords_to_dataframe(
333
+ self,
334
+ ["x", "y", "z", "vx", "vy", "vz"],
335
+ sigmas=sigmas,
336
+ covariances=covariances,
337
+ )
338
+
339
+ @classmethod
340
+ def from_dataframe(
341
+ cls, df: pd.DataFrame, frame: Literal["ecliptic", "equatorial"]
342
+ ) -> "CartesianCoordinates":
343
+ """
344
+ Create coordinates from a pandas DataFrame.
345
+
346
+ Parameters
347
+ ----------
348
+ df : `~pandas.Dataframe`
349
+ DataFrame containing coordinates.
350
+ frame : {"ecliptic", "equatorial"}
351
+ Frame in which coordinates are defined.
352
+
353
+ Returns
354
+ -------
355
+ coords : `~adam_core.coordinates.cartesian.CartesianCoordinates`
356
+ Cartesian coordinates.
357
+ """
358
+ return coords_from_dataframe(
359
+ cls, df, coord_names=["x", "y", "z", "vx", "vy", "vz"], frame=frame
360
+ )