ssapy-toolkit 1.0.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 (170) hide show
  1. ssapy_toolkit/Accelerations/__init__.py +18 -0
  2. ssapy_toolkit/Accelerations/accel_add.py +26 -0
  3. ssapy_toolkit/Accelerations/accel_circularize.py +88 -0
  4. ssapy_toolkit/Accelerations/accel_deltav.py +50 -0
  5. ssapy_toolkit/Accelerations/accel_deltav_vector.py +39 -0
  6. ssapy_toolkit/Accelerations/accel_earth_harmonics.py +172 -0
  7. ssapy_toolkit/Accelerations/accel_equitorial.py +41 -0
  8. ssapy_toolkit/Accelerations/accel_inclination.py +40 -0
  9. ssapy_toolkit/Accelerations/accel_moon.py +46 -0
  10. ssapy_toolkit/Accelerations/accel_plane.py +44 -0
  11. ssapy_toolkit/Accelerations/accel_point_earth.py +9 -0
  12. ssapy_toolkit/Accelerations/accel_radial.py +27 -0
  13. ssapy_toolkit/Accelerations/accel_sun.py +38 -0
  14. ssapy_toolkit/Accelerations/accel_uniform_earth.py +31 -0
  15. ssapy_toolkit/Accelerations/accel_velocity.py +25 -0
  16. ssapy_toolkit/Compute/__init__.py +18 -0
  17. ssapy_toolkit/Compute/calculate_errors.py +22 -0
  18. ssapy_toolkit/Compute/fft.py +43 -0
  19. ssapy_toolkit/Compute/find_bounding_cube.py +24 -0
  20. ssapy_toolkit/Compute/generate_sphere_of_vectors.py +102 -0
  21. ssapy_toolkit/Compute/lambertian_magnitude.py +250 -0
  22. ssapy_toolkit/Compute/lyapunov_exponent.py +190 -0
  23. ssapy_toolkit/Compute/mengos.py +31 -0
  24. ssapy_toolkit/Compute/proper_motions.py +107 -0
  25. ssapy_toolkit/Compute/sampling.py +537 -0
  26. ssapy_toolkit/Compute/segment_intersection.py +46 -0
  27. ssapy_toolkit/Coordinates/__init__.py +18 -0
  28. ssapy_toolkit/Coordinates/cartesian_to_cylindrical.py +20 -0
  29. ssapy_toolkit/Coordinates/cartesian_to_spherical.py +22 -0
  30. ssapy_toolkit/Coordinates/earth_trojan_sim.py +59 -0
  31. ssapy_toolkit/Coordinates/equitorial_and_ecliptic.py +167 -0
  32. ssapy_toolkit/Coordinates/gcrf_to_itrf.py +75 -0
  33. ssapy_toolkit/Coordinates/gcrf_to_llh.py +55 -0
  34. ssapy_toolkit/Coordinates/gcrf_to_lonlat.py +25 -0
  35. ssapy_toolkit/Coordinates/gcrf_to_lunar.py +76 -0
  36. ssapy_toolkit/Coordinates/gcrf_to_ntw.py +23 -0
  37. ssapy_toolkit/Coordinates/itrf_to_gcrf.py +67 -0
  38. ssapy_toolkit/Coordinates/j2000_to_gcrf.py +80 -0
  39. ssapy_toolkit/Coordinates/llh_to_gcrf.py +45 -0
  40. ssapy_toolkit/Coordinates/local_and_equitorial.py +130 -0
  41. ssapy_toolkit/Coordinates/lon_lat_bbox.py +47 -0
  42. ssapy_toolkit/Coordinates/lonlat_perigee.py +70 -0
  43. ssapy_toolkit/Coordinates/lunar_position.py +35 -0
  44. ssapy_toolkit/Coordinates/ntw_to_gcrf.py +35 -0
  45. ssapy_toolkit/Coordinates/on_sky_distance.py +23 -0
  46. ssapy_toolkit/Coordinates/sky_angles.py +155 -0
  47. ssapy_toolkit/Coordinates/surface_rv.py +60 -0
  48. ssapy_toolkit/Coordinates/unit_conversions.py +137 -0
  49. ssapy_toolkit/Coordinates/v_from_r.py +51 -0
  50. ssapy_toolkit/IO/__init__.py +18 -0
  51. ssapy_toolkit/IO/converter_json_hdf5.py +219 -0
  52. ssapy_toolkit/IO/csv_utils.py +340 -0
  53. ssapy_toolkit/IO/dict_to_from_hdf5.py +315 -0
  54. ssapy_toolkit/IO/get_memory.py +15 -0
  55. ssapy_toolkit/IO/guess_delimiter.py +24 -0
  56. ssapy_toolkit/IO/hdf5_to_csv.py +170 -0
  57. ssapy_toolkit/IO/hdf5_utils.py +207 -0
  58. ssapy_toolkit/IO/html_to_gif.py +158 -0
  59. ssapy_toolkit/IO/io_utils.py +184 -0
  60. ssapy_toolkit/IO/json_utils.py +99 -0
  61. ssapy_toolkit/IO/listfiles.py +56 -0
  62. ssapy_toolkit/IO/pickle_utils.py +13 -0
  63. ssapy_toolkit/IO/pprint_utils.py +447 -0
  64. ssapy_toolkit/IO/read_3le.py +351 -0
  65. ssapy_toolkit/IO/read_3le_by_bit.py +231 -0
  66. ssapy_toolkit/IO/tle_iter_pairs.py +41 -0
  67. ssapy_toolkit/IO/tle_prop_to_time.py +67 -0
  68. ssapy_toolkit/IO/workspace_io.py +306 -0
  69. ssapy_toolkit/IO/xml_utils.py +265 -0
  70. ssapy_toolkit/IO/yu_logging.py +68 -0
  71. ssapy_toolkit/IO/yuchaching.py +344 -0
  72. ssapy_toolkit/IO/yudata.py +89 -0
  73. ssapy_toolkit/Integrators/__init__.py +18 -0
  74. ssapy_toolkit/Integrators/fuel.py +70 -0
  75. ssapy_toolkit/Integrators/gravity_turn.py +29 -0
  76. ssapy_toolkit/Integrators/int_utils.py +126 -0
  77. ssapy_toolkit/Integrators/leap_frog.py +148 -0
  78. ssapy_toolkit/Integrators/quick_int.py +182 -0
  79. ssapy_toolkit/Integrators/rk4.py +69 -0
  80. ssapy_toolkit/Orbital_Mechanics/__init__.py +18 -0
  81. ssapy_toolkit/Orbital_Mechanics/all_orbit_quanities.py +165 -0
  82. ssapy_toolkit/Orbital_Mechanics/burn_to_deltav.py +80 -0
  83. ssapy_toolkit/Orbital_Mechanics/calculate_finite_burn_acceleration.py +38 -0
  84. ssapy_toolkit/Orbital_Mechanics/deltav_to_burn.py +82 -0
  85. ssapy_toolkit/Orbital_Mechanics/ellipse_fit.py +734 -0
  86. ssapy_toolkit/Orbital_Mechanics/ellipse_from_rv.py +177 -0
  87. ssapy_toolkit/Orbital_Mechanics/ellipse_inject.py +9 -0
  88. ssapy_toolkit/Orbital_Mechanics/equally_spaced_ta.py +135 -0
  89. ssapy_toolkit/Orbital_Mechanics/gamma_and_heading.py +230 -0
  90. ssapy_toolkit/Orbital_Mechanics/keplerian.py +1118 -0
  91. ssapy_toolkit/Orbital_Mechanics/lagrange_points.py +215 -0
  92. ssapy_toolkit/Orbital_Mechanics/launch_pads.py +83 -0
  93. ssapy_toolkit/Orbital_Mechanics/misc.py +173 -0
  94. ssapy_toolkit/Orbital_Mechanics/orbital_accel_model_comparisons.py +724 -0
  95. ssapy_toolkit/Orbital_Mechanics/orbital_comparison_stats.py +1000 -0
  96. ssapy_toolkit/Orbital_Mechanics/rv_to_ellipse.py +154 -0
  97. ssapy_toolkit/Orbital_Mechanics/synthetic_orbit_population.py +209 -0
  98. ssapy_toolkit/Orbital_Mechanics/transfer_coplanar.py +144 -0
  99. ssapy_toolkit/Orbital_Mechanics/transfer_coplanar_continuous.py +120 -0
  100. ssapy_toolkit/Orbital_Mechanics/transfer_hohmann.py +250 -0
  101. ssapy_toolkit/Orbital_Mechanics/transfer_inclination_continuous.py +169 -0
  102. ssapy_toolkit/Orbital_Mechanics/transfer_lambertian.py +216 -0
  103. ssapy_toolkit/Orbital_Mechanics/transfer_rendezvous.py +141 -0
  104. ssapy_toolkit/Orbital_Mechanics/transfer_shooter.py +240 -0
  105. ssapy_toolkit/Orbital_Mechanics/transfer_shooter_continuous.py +0 -0
  106. ssapy_toolkit/Orbital_Mechanics/transfer_velocity_and_inclination_continuous.py +184 -0
  107. ssapy_toolkit/Orbital_Mechanics/transfer_velocity_continuous.py +212 -0
  108. ssapy_toolkit/Plots/__init__.py +18 -0
  109. ssapy_toolkit/Plots/break_plot_lines.py +115 -0
  110. ssapy_toolkit/Plots/build_dashboard.py +74 -0
  111. ssapy_toolkit/Plots/cislunar_plot.py +213 -0
  112. ssapy_toolkit/Plots/cislunar_plot_3d.py +172 -0
  113. ssapy_toolkit/Plots/cislunar_plot_xy.py +199 -0
  114. ssapy_toolkit/Plots/divergence_gif.py +215 -0
  115. ssapy_toolkit/Plots/divergence_plot.py +107 -0
  116. ssapy_toolkit/Plots/figpath.py +87 -0
  117. ssapy_toolkit/Plots/gifify.py +307 -0
  118. ssapy_toolkit/Plots/globe_plot.py +277 -0
  119. ssapy_toolkit/Plots/groundtrack_dashboard.py +264 -0
  120. ssapy_toolkit/Plots/groundtrack_dashboard_gamma_heading.py +247 -0
  121. ssapy_toolkit/Plots/groundtrack_plot.py +352 -0
  122. ssapy_toolkit/Plots/groundtrack_video.py +292 -0
  123. ssapy_toolkit/Plots/misc_plotting.py +371 -0
  124. ssapy_toolkit/Plots/orbit_plot.py +139 -0
  125. ssapy_toolkit/Plots/orbit_plot_rv.py +126 -0
  126. ssapy_toolkit/Plots/orbit_plot_xy.py +223 -0
  127. ssapy_toolkit/Plots/orbit_plot_xyxz.py +249 -0
  128. ssapy_toolkit/Plots/plotutils.py +653 -0
  129. ssapy_toolkit/Plots/rendezvous_plot.py +98 -0
  130. ssapy_toolkit/Plots/set_axes_equal.py +32 -0
  131. ssapy_toolkit/Plots/tracking_plot.py +179 -0
  132. ssapy_toolkit/Plots/transfer_plot.py +147 -0
  133. ssapy_toolkit/Plots/write_gifs.py +222 -0
  134. ssapy_toolkit/Plots/write_videos.py +117 -0
  135. ssapy_toolkit/Rockets/__init__.py +18 -0
  136. ssapy_toolkit/Rockets/fuel.py +162 -0
  137. ssapy_toolkit/Rockets/rescale_burn.py +59 -0
  138. ssapy_toolkit/SSAPy_wrappers/__init__.py +18 -0
  139. ssapy_toolkit/SSAPy_wrappers/accel_ladder.py +80 -0
  140. ssapy_toolkit/SSAPy_wrappers/sat_kwargs.py +9 -0
  141. ssapy_toolkit/SSAPy_wrappers/ssapy_orbits.py +246 -0
  142. ssapy_toolkit/SSAPy_wrappers/ssapy_props.py +141 -0
  143. ssapy_toolkit/Time_Functions/__init__.py +18 -0
  144. ssapy_toolkit/Time_Functions/absolute_times_to_relative.py +68 -0
  145. ssapy_toolkit/Time_Functions/convert_dd_and_dms.py +55 -0
  146. ssapy_toolkit/Time_Functions/convert_dd_and_hms.py +65 -0
  147. ssapy_toolkit/Time_Functions/convert_gps_to_TT.py +17 -0
  148. ssapy_toolkit/Time_Functions/convert_to_gps.py +32 -0
  149. ssapy_toolkit/Time_Functions/get_times.py +163 -0
  150. ssapy_toolkit/Time_Functions/now_time.py +15 -0
  151. ssapy_toolkit/Time_Functions/relative_times_to_absolute.py +72 -0
  152. ssapy_toolkit/Yastropy/__init__.py +18 -0
  153. ssapy_toolkit/Yastropy/astropy_gcrf_to_llh.py +50 -0
  154. ssapy_toolkit/Yastropy/astropy_llh_to_gcrf.py +42 -0
  155. ssapy_toolkit/Yastropy/astropy_surface_rv.py +39 -0
  156. ssapy_toolkit/Yastropy/astropy_test.py +43 -0
  157. ssapy_toolkit/__init__.py +43 -0
  158. ssapy_toolkit/asteroids.py +168 -0
  159. ssapy_toolkit/constants.py +123 -0
  160. ssapy_toolkit/engines.py +195 -0
  161. ssapy_toolkit/hpc.py +213 -0
  162. ssapy_toolkit/launch_pads.py +215 -0
  163. ssapy_toolkit/orbit_initializer.py +72 -0
  164. ssapy_toolkit/utils.py +662 -0
  165. ssapy_toolkit/vectors.py +241 -0
  166. ssapy_toolkit-1.0.0.dist-info/METADATA +182 -0
  167. ssapy_toolkit-1.0.0.dist-info/RECORD +170 -0
  168. ssapy_toolkit-1.0.0.dist-info/WHEEL +5 -0
  169. ssapy_toolkit-1.0.0.dist-info/licenses/LICENSE +28 -0
  170. ssapy_toolkit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,18 @@
1
+ """Acceleration models and utilities for orbit propagation."""
2
+
3
+ import importlib
4
+ import os
5
+
6
+ # Get the directory of the current file
7
+ sub_dir = os.path.dirname(__file__)
8
+
9
+ # Loop through all Python files in this directory and import their functions
10
+ for filename in os.listdir(sub_dir):
11
+ if filename.endswith(".py") and filename != "__init__.py":
12
+ module_name = f"{__name__}.{filename[:-3]}"
13
+ module = importlib.import_module(module_name)
14
+
15
+ # Add all attributes of the module to the package namespace
16
+ for attr in dir(module):
17
+ if not attr.startswith("_"): # Avoid internal attributes
18
+ globals()[attr] = getattr(module, attr)
@@ -0,0 +1,26 @@
1
+ import numpy as np
2
+
3
+
4
+ def accel_add(*accel_funcs):
5
+ """
6
+ Returns a function that sums multiple acceleration functions.
7
+
8
+ Parameters
9
+ ----------
10
+ accel_funcs : list of functions
11
+ Each must take (r) or (r, t) depending on your usage.
12
+
13
+ Returns
14
+ -------
15
+ combined : function
16
+ A function that evaluates and sums all input accelerations.
17
+ """
18
+ def combined(r, t=None):
19
+ total = np.zeros(3)
20
+ for f in accel_funcs:
21
+ try:
22
+ total += f(r, t)
23
+ except TypeError:
24
+ total += f(r)
25
+ return total
26
+ return combined
@@ -0,0 +1,88 @@
1
+ # ssapy_toolkit/Accelerations/accel_circularize.py
2
+
3
+ import numpy as np
4
+ from ..constants import EARTH_MU, EARTH_RADIUS # [56]
5
+
6
+ _orbit_achieved = False # global variable to track circularization
7
+
8
+
9
+ def reset_orbit_status():
10
+ """Reset the internal latch so circularization thrust can be used again."""
11
+ global _orbit_achieved
12
+ _orbit_achieved = False
13
+
14
+
15
+ def accel_to_circular(r, v, t=None, *, thrust, tol=10.0, min_altitude=100e3):
16
+ """
17
+ Acceleration command that steers (r, v) toward a circular orbit at radius |r|.
18
+
19
+ Designed to be passed into leapfrog(..., accels=[...]) where leapfrog may call
20
+ accelerations as f(r,v,t) (t is accepted but not required here).
21
+
22
+ Parameters
23
+ ----------
24
+ r : array_like, shape (3,)
25
+ Position vector (m).
26
+ v : array_like, shape (3,)
27
+ Velocity vector (m/s).
28
+ t : optional
29
+ Time (ignored; present for integrator compatibility).
30
+ thrust : float
31
+ Magnitude of available acceleration (m/s^2).
32
+ tol : float, optional
33
+ Convergence tolerance on ||v_target - v|| (m/s). Default 10.
34
+ min_altitude : float, optional
35
+ Minimum altitude above Earth's surface to allow control (m). Default 100 km.
36
+
37
+ Returns
38
+ -------
39
+ a : ndarray, shape (3,)
40
+ Acceleration command (m/s^2), or zeros if converged/disabled.
41
+ """
42
+ global _orbit_achieved
43
+
44
+ if _orbit_achieved:
45
+ return np.zeros(3, dtype=float)
46
+
47
+ r = np.asarray(r, dtype=float).reshape(3)
48
+ v = np.asarray(v, dtype=float).reshape(3)
49
+
50
+ r_norm = np.linalg.norm(r)
51
+ if r_norm == 0.0 or thrust == 0.0:
52
+ return np.zeros(3, dtype=float)
53
+
54
+ if r_norm < EARTH_RADIUS + float(min_altitude):
55
+ return np.zeros(3, dtype=float)
56
+
57
+ # Unit radial direction
58
+ r_hat = r / r_norm
59
+
60
+ # Tangential velocity component
61
+ v_r = float(np.dot(v, r_hat))
62
+ v_t_vec = v - v_r * r_hat
63
+ v_t = np.linalg.norm(v_t_vec)
64
+
65
+ if v_t > 0.0:
66
+ t_hat = v_t_vec / v_t
67
+ else:
68
+ # Fallback if velocity is purely radial: pick a consistent tangential direction
69
+ t_hat = np.cross(np.array([0.0, 0.0, 1.0]), r_hat)
70
+ n = np.linalg.norm(t_hat)
71
+ if n == 0.0:
72
+ return np.zeros(3, dtype=float)
73
+ t_hat /= n
74
+
75
+ # Circular speed at current radius
76
+ v_circ = np.sqrt(EARTH_MU / r_norm)
77
+
78
+ # Desired circular velocity vector (purely tangential)
79
+ v_target = v_circ * t_hat
80
+
81
+ dv = v_target - v
82
+ dv_norm = np.linalg.norm(dv)
83
+
84
+ if dv_norm <= float(tol) or dv_norm == 0.0:
85
+ _orbit_achieved = True
86
+ return np.zeros(3, dtype=float)
87
+
88
+ return float(thrust) * (dv / dv_norm)
@@ -0,0 +1,50 @@
1
+ import numpy as np
2
+
3
+ def accel_deltav(dv: float, thrust_accel: float, center_idx: int, dt: float = 1.0) -> dict:
4
+ """
5
+ Build a Δv-based along-track accel profile centered at a given step.
6
+
7
+ Parameters
8
+ ----------
9
+ dv : float
10
+ Signed total Δv to deliver (m/s). (Only its sign matters.)
11
+ thrust_accel : float
12
+ Available constant acceleration magnitude (m/s²). Must be > 0.
13
+ center_idx : int
14
+ Index in your time array where the burn is centered.
15
+ dt : float, optional
16
+ Uniform timestep between your indices (s). Defaults to 1 s.
17
+
18
+ Returns
19
+ -------
20
+ profile : dict
21
+ A dict with keys:
22
+ - 'thrust' : signed acceleration (m/s²)
23
+ - 'start' : start index (int)
24
+ - 'end' : end index (int)
25
+ which you can pass directly to `leapfrog(..., velocity=profile)`.
26
+ """
27
+ if thrust_accel <= 0:
28
+ raise ValueError("`thrust_accel` must be positive")
29
+ if dt <= 0:
30
+ raise ValueError("`dt` must be positive")
31
+
32
+ # total burn time (s) required for |Δv|
33
+ burn_time = abs(dv) / thrust_accel
34
+
35
+ # number of steps (round to nearest int, minimum 1)
36
+ steps = max(1, int(round(burn_time / dt)))
37
+
38
+ # center the burn on center_idx
39
+ half = steps // 2
40
+ start = center_idx - half
41
+ end = start + steps # ensures end - start == steps
42
+
43
+ # signed acceleration to match dv's sign
44
+ signed_accel = thrust_accel * np.sign(dv)
45
+
46
+ return {
47
+ "thrust": signed_accel,
48
+ "start": start,
49
+ "end": end,
50
+ }
@@ -0,0 +1,39 @@
1
+ import numpy as np
2
+
3
+
4
+ def accel_deltav_vector(dv, duration):
5
+ """
6
+ Compute the constant acceleration vector needed to achieve a given Δv in a set time.
7
+
8
+ Parameters
9
+ ----------
10
+ dv : array_like, shape (3,)
11
+ Desired change in velocity vector (m/s).
12
+ duration : float
13
+ Time over which to apply the acceleration (s).
14
+
15
+ Returns
16
+ -------
17
+ thrust : float
18
+ Magnitude of the required acceleration (m/s²).
19
+ direction : ndarray, shape (3,)
20
+ Unit vector indicating acceleration direction.
21
+ a_vec : ndarray, shape (3,)
22
+ Acceleration vector (m/s²) that, when applied constantly over `duration`, yields `dv`.
23
+
24
+ Raises
25
+ ------
26
+ ValueError
27
+ If `duration` is zero or negative.
28
+ """
29
+ dv = np.asarray(dv, dtype=float)
30
+ if duration <= 0:
31
+ raise ValueError("Duration must be positive and non-zero.")
32
+ dv_norm = np.linalg.norm(dv)
33
+ if dv_norm == 0:
34
+ return 0.0, np.zeros(3), np.zeros(3)
35
+
36
+ thrust = dv_norm / duration
37
+ direction = dv / dv_norm
38
+ a_vec = thrust * direction
39
+ return thrust, direction, a_vec
@@ -0,0 +1,172 @@
1
+ # ssapy_toolkit/Accelerations/accel_earth_harmonics.py
2
+
3
+ import numpy as np
4
+ from ..constants import EARTH_MU, EARTH_RADIUS
5
+
6
+
7
+ def _check_r_safe(r2: float) -> None:
8
+ """Raise error if r is below Earth's surface."""
9
+ r_mag = float(np.sqrt(r2))
10
+ if r_mag < EARTH_RADIUS:
11
+ raise ValueError(f"r magnitude ({r_mag:.2f} m) is below Earth's surface.")
12
+
13
+
14
+ def accel_J2(r: np.ndarray) -> np.ndarray:
15
+ J2 = 1.08262668e-3
16
+ mu = EARTH_MU
17
+ Re = EARTH_RADIUS
18
+
19
+ r = np.asarray(r, dtype=float).reshape(3)
20
+ r2 = float(np.dot(r, r))
21
+ _check_r_safe(r2)
22
+ r_mag = np.sqrt(r2)
23
+
24
+ inv_r5 = 1.0 / (r2 * r_mag**3) # = 1/r^5
25
+ z = r[2]
26
+ u = z / r_mag
27
+ k2 = 5.0 * u**2 - 1.0
28
+
29
+ factor2 = 1.5 * J2 * mu * Re**2 * inv_r5
30
+ return factor2 * np.array([r[0] * k2, r[1] * k2, r[2] * (5.0 * u**2 - 3.0)])
31
+
32
+
33
+ def accel_J3(r: np.ndarray) -> np.ndarray:
34
+ J3 = -2.5324105e-6
35
+ mu = EARTH_MU
36
+ Re = EARTH_RADIUS
37
+
38
+ r = np.asarray(r, dtype=float).reshape(3)
39
+ r2 = float(np.dot(r, r))
40
+ _check_r_safe(r2)
41
+ r_mag = np.sqrt(r2)
42
+
43
+ inv_r6 = 1.0 / (r2**3) # = 1/r^6
44
+ z = r[2]
45
+ u = z / r_mag
46
+
47
+ P3 = 0.5 * (5.0 * u**3 - 3.0 * u)
48
+ dP3 = 0.5 * (15.0 * u**2 - 3.0)
49
+ factor3 = -mu * J3 * Re**3 * inv_r6
50
+ return factor3 * (4.0 * P3 * r - dP3 * r * u * np.array([0.0, 0.0, 1.0]))
51
+
52
+
53
+ def accel_J4(r: np.ndarray) -> np.ndarray:
54
+ J4 = -1.6198976e-6
55
+ mu = EARTH_MU
56
+ Re = EARTH_RADIUS
57
+
58
+ r = np.asarray(r, dtype=float).reshape(3)
59
+ r2 = float(np.dot(r, r))
60
+ _check_r_safe(r2)
61
+ r_mag = np.sqrt(r2)
62
+
63
+ inv_r7 = 1.0 / (r2**2 * r_mag**3) # = 1/r^7
64
+ z = r[2]
65
+ u = z / r_mag
66
+
67
+ P4 = (1.0 / 8.0) * (35.0 * u**4 - 30.0 * u**2 + 3.0)
68
+ dP4 = (1.0 / 8.0) * (140.0 * u**3 - 60.0 * u)
69
+ factor4 = -mu * J4 * Re**4 * inv_r7
70
+ return factor4 * (5.0 * P4 * r - dP4 * r * u * np.array([0.0, 0.0, 1.0]))
71
+
72
+
73
+ def accel_J5(r: np.ndarray) -> np.ndarray:
74
+ J5 = -2.272960828e-7
75
+ mu = EARTH_MU
76
+ Re = EARTH_RADIUS
77
+
78
+ r = np.asarray(r, dtype=float).reshape(3)
79
+ r2 = float(np.dot(r, r))
80
+ _check_r_safe(r2)
81
+ r_mag = np.sqrt(r2)
82
+
83
+ inv_r9 = 1.0 / (r2**4 * r_mag) # = 1/r^9
84
+ z = r[2]
85
+ u = z / r_mag
86
+
87
+ P5 = (1.0 / 8.0) * (63.0 * u**5 - 70.0 * u**3 + 15.0 * u)
88
+ dP5 = (1.0 / 8.0) * (315.0 * u**4 - 210.0 * u**2 + 15.0)
89
+ factor5 = -mu * J5 * Re**5 * inv_r9
90
+ return factor5 * (6.0 * P5 * r - dP5 * r * u * np.array([0.0, 0.0, 1.0]))
91
+
92
+
93
+ def accel_J6(r: np.ndarray) -> np.ndarray:
94
+ J6 = 5.406812391e-7
95
+ mu = EARTH_MU
96
+ Re = EARTH_RADIUS
97
+
98
+ r = np.asarray(r, dtype=float).reshape(3)
99
+ r2 = float(np.dot(r, r))
100
+ _check_r_safe(r2)
101
+ r_mag = np.sqrt(r2)
102
+
103
+ inv_r10 = 1.0 / (r2**4 * r_mag**2) # = 1/r^10
104
+ z = r[2]
105
+ u = z / r_mag
106
+
107
+ P6 = (1.0 / 16.0) * (231.0 * u**6 - 315.0 * u**4 + 105.0 * u**2 - 5.0)
108
+ dP6 = (1.0 / 16.0) * (1386.0 * u**5 - 1260.0 * u**3 + 210.0 * u)
109
+ factor6 = -mu * J6 * Re**6 * inv_r10
110
+ return factor6 * (7.0 * P6 * r - dP6 * r * u * np.array([0.0, 0.0, 1.0]))
111
+
112
+
113
+ def accel_J7(r: np.ndarray) -> np.ndarray:
114
+ J7 = -3.529000898e-7
115
+ mu = EARTH_MU
116
+ Re = EARTH_RADIUS
117
+
118
+ r = np.asarray(r, dtype=float).reshape(3)
119
+ r2 = float(np.dot(r, r))
120
+ _check_r_safe(r2)
121
+ r_mag = np.sqrt(r2)
122
+
123
+ inv_r11 = 1.0 / (r2**4 * r_mag**3) # = 1/r^11
124
+ z = r[2]
125
+ u = z / r_mag
126
+
127
+ P7 = (1.0 / 16.0) * (429.0 * u**7 - 693.0 * u**5 + 315.0 * u**3 - 35.0 * u)
128
+ dP7 = (1.0 / 16.0) * (3003.0 * u**6 - 3465.0 * u**4 + 945.0 * u**2 - 35.0)
129
+ factor7 = -mu * J7 * Re**7 * inv_r11
130
+ return factor7 * (8.0 * P7 * r - dP7 * r * u * np.array([0.0, 0.0, 1.0]))
131
+
132
+
133
+ def accel_J8(r: np.ndarray) -> np.ndarray:
134
+ J8 = 2.532000898e-7
135
+ mu = EARTH_MU
136
+ Re = EARTH_RADIUS
137
+
138
+ r = np.asarray(r, dtype=float).reshape(3)
139
+ r2 = float(np.dot(r, r))
140
+ _check_r_safe(r2)
141
+ r_mag = np.sqrt(r2)
142
+
143
+ inv_r12 = 1.0 / (r2**5 * r_mag**2) # = 1/r^12
144
+ z = r[2]
145
+ u = z / r_mag
146
+
147
+ P8 = (1.0 / 128.0) * (6435.0 * u**8 - 12012.0 * u**6 + 6930.0 * u**4 - 1260.0 * u**2 + 35.0)
148
+ dP8 = (1.0 / 128.0) * (51480.0 * u**7 - 72072.0 * u**5 + 27720.0 * u**3 - 2520.0 * u)
149
+ factor8 = -mu * J8 * Re**8 * inv_r12
150
+ return factor8 * (9.0 * P8 * r - dP8 * r * u * np.array([0.0, 0.0, 1.0]))
151
+
152
+
153
+ def accel_earth_harmonics(r: np.ndarray) -> np.ndarray:
154
+ """
155
+ Central gravity + J2..J8 zonal harmonics.
156
+ """
157
+ r = np.asarray(r, dtype=float).reshape(3)
158
+ r2 = float(np.dot(r, r))
159
+ _check_r_safe(r2)
160
+ r_mag = np.sqrt(r2)
161
+
162
+ a_central = -EARTH_MU * r / (r_mag**3) # [59]
163
+ return (
164
+ a_central
165
+ + accel_J2(r)
166
+ + accel_J3(r)
167
+ + accel_J4(r)
168
+ + accel_J5(r)
169
+ + accel_J6(r)
170
+ + accel_J7(r)
171
+ + accel_J8(r)
172
+ )
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+
3
+
4
+ def accel_equatorial(r, v, magnitude):
5
+ """
6
+ Acceleration vector always in the equatorial (east/west) direction,
7
+ i.e. perpendicular to the Earth's spin axis and radial direction,
8
+ positive -> counter-clockwise (eastward), negative -> clockwise.
9
+
10
+ Parameters
11
+ ----------
12
+ r : array_like, shape (3,)
13
+ Position (radial) vector in meters.
14
+ v : array_like, shape (3,)
15
+ (Unused; kept for signature consistency.)
16
+ magnitude : float
17
+ Desired magnitude of the equatorial-plane acceleration in m/s^2.
18
+
19
+ Returns
20
+ -------
21
+ a : ndarray, shape (3,)
22
+ Equatorial-plane acceleration vector in m/s^2.
23
+ """
24
+ r = np.asarray(r, dtype=float).reshape(3)
25
+
26
+ if magnitude == 0:
27
+ return np.zeros(3, dtype=float)
28
+
29
+ norm_r = np.linalg.norm(r)
30
+ if norm_r == 0:
31
+ return np.zeros(3, dtype=float)
32
+
33
+ r_hat = r / norm_r
34
+ z_hat = np.array([0.0, 0.0, 1.0], dtype=float)
35
+
36
+ equ_dir = np.cross(z_hat, r_hat)
37
+ norm_eq = np.linalg.norm(equ_dir)
38
+ if norm_eq == 0:
39
+ return np.zeros(3, dtype=float)
40
+
41
+ return float(magnitude) * equ_dir / norm_eq
@@ -0,0 +1,40 @@
1
+ # ssapy_toolkit/Accelerations/accel_inclination.py
2
+
3
+ import numpy as np
4
+
5
+
6
+ def accel_inclination(r, v, magnitude):
7
+ """
8
+ Acceleration vector always in the local north/south direction,
9
+ i.e. perpendicular to the radial direction but aimed toward
10
+ the Earth's North pole (or South if magnitude < 0).
11
+
12
+ Parameters
13
+ ----------
14
+ r : array_like, shape (3,)
15
+ Position (radial) vector in meters.
16
+ v : array_like, shape (3,)
17
+ (Unused; kept for signature consistency.)
18
+ magnitude : float
19
+ Desired magnitude of the inclination-change acceleration in m/s^2.
20
+ Positive -> northward; negative -> southward.
21
+
22
+ Returns
23
+ -------
24
+ a : ndarray, shape (3,)
25
+ Inclination-change acceleration vector in m/s^2.
26
+ """
27
+ r = np.asarray(r, dtype=float).reshape(3)
28
+ norm_r = np.linalg.norm(r)
29
+ if norm_r == 0.0 or magnitude == 0.0:
30
+ return np.zeros(3, dtype=float)
31
+
32
+ r_hat = r / norm_r
33
+ z_hat = np.array([0.0, 0.0, 1.0], dtype=float)
34
+
35
+ north_dir = z_hat - np.dot(z_hat, r_hat) * r_hat
36
+ norm_nd = np.linalg.norm(north_dir)
37
+ if norm_nd == 0.0:
38
+ return np.zeros(3, dtype=float)
39
+
40
+ return float(magnitude) * north_dir / norm_nd
@@ -0,0 +1,46 @@
1
+ # ssapy_toolkit/Accelerations/accel_moon.py
2
+
3
+ import numpy as np
4
+ from astropy.time import Time
5
+ from astropy import units as u
6
+ from astropy.coordinates import get_body, GCRS, solar_system_ephemeris
7
+
8
+ from ..constants import MOON_MU
9
+ from ..Time_Functions import to_gps
10
+
11
+
12
+ def accel_point_moon(r: np.ndarray, time) -> np.ndarray:
13
+ """
14
+ Point-mass lunar gravity in GCRF.
15
+
16
+ Parameters
17
+ ----------
18
+ r : array_like, shape (3,)
19
+ Satellite position vector in GCRF (m), Earth-centered.
20
+ time : float | datetime-like | astropy.time.Time | array_like
21
+ Time corresponding to the state (anything supported by to_gps()).
22
+
23
+ Returns
24
+ -------
25
+ a_moon : ndarray, shape (3,)
26
+ Acceleration from the Moon (m/s^2).
27
+ """
28
+ r = np.asarray(r, dtype=float).reshape(3)
29
+
30
+ # 1) Convert time to GPS seconds since 1980-01-06
31
+ time_gps = to_gps(time) # [176]
32
+ t = Time(time_gps, format="gps", scale="utc")
33
+
34
+ # 2) Get Moon position in GCRS at this time using JPL ephemeris
35
+ with solar_system_ephemeris.set("jpl"):
36
+ moon_gcrs = get_body("moon", t).transform_to(GCRS(obstime=t))
37
+
38
+ r_moon = moon_gcrs.cartesian.xyz.to(u.m).value # (3,)
39
+
40
+ # 3) Compute delta and gravitational acceleration
41
+ delta = r_moon - r
42
+ d = np.linalg.norm(delta)
43
+ if d == 0.0:
44
+ return np.zeros(3, dtype=float)
45
+
46
+ return MOON_MU * delta / d**3
@@ -0,0 +1,44 @@
1
+ # ssapy_toolkit/Accelerations/accel_plane.py
2
+
3
+ import numpy as np
4
+
5
+
6
+ def accel_plane(r, v, magnitude):
7
+ """
8
+ Tangential acceleration in the orbital plane, perpendicular to r̂.
9
+
10
+ Parameters
11
+ ----------
12
+ r : array_like, shape (3,)
13
+ Position vector in meters.
14
+ v : array_like, shape (3,)
15
+ Velocity vector in meters per second.
16
+ magnitude : float
17
+ Desired acceleration magnitude in m/s^2.
18
+ Positive → along the direction of motion;
19
+ negative → opposite direction.
20
+
21
+ Returns
22
+ -------
23
+ a : ndarray, shape (3,)
24
+ Tangential acceleration vector in m/s^2.
25
+ """
26
+ r = np.asarray(r, dtype=float).reshape(3)
27
+ v = np.asarray(v, dtype=float).reshape(3)
28
+
29
+ if magnitude == 0:
30
+ return np.zeros(3, dtype=float)
31
+
32
+ norm_r = np.linalg.norm(r)
33
+ if norm_r == 0:
34
+ return np.zeros(3, dtype=float)
35
+
36
+ r_hat = r / norm_r
37
+
38
+ # Remove radial component of v to get purely tangential direction
39
+ tangential = v - np.dot(v, r_hat) * r_hat
40
+ norm_tan = np.linalg.norm(tangential)
41
+ if norm_tan == 0:
42
+ return np.zeros(3, dtype=float)
43
+
44
+ return float(magnitude) * tangential / norm_tan
@@ -0,0 +1,9 @@
1
+ import numpy as np
2
+ from ..constants import EARTH_MU
3
+
4
+
5
+ def accel_point_earth(r):
6
+ x, y, z = r
7
+ r_mag = np.sqrt(x**2 + y**2 + z**2)
8
+ factor = -EARTH_MU / (r_mag**3)
9
+ return np.array([factor * x, factor * y, factor * z])
@@ -0,0 +1,27 @@
1
+ import numpy as np
2
+
3
+
4
+ def accel_radial(r, magnitude):
5
+ """
6
+ Acceleration vector pointing radially outward from Earth's center
7
+ with user-specified magnitude.
8
+
9
+ Parameters
10
+ ----------
11
+ r : array_like, shape (3,)
12
+ Position vector in meters.
13
+ magnitude : float
14
+ Acceleration magnitude in m/s^2.
15
+
16
+ Returns
17
+ -------
18
+ a : ndarray, shape (3,)
19
+ Acceleration vector in m/s^2.
20
+
21
+ Author: Travis Yeager
22
+ """
23
+ r = np.asarray(r)
24
+ norm_r = np.linalg.norm(r)
25
+ if norm_r == 0.0:
26
+ return np.zeros(3)
27
+ return magnitude * r / norm_r
@@ -0,0 +1,38 @@
1
+ import numpy as np
2
+ from astropy.time import Time
3
+ from astropy import units as u
4
+ from astropy.coordinates import get_body, GCRS, solar_system_ephemeris
5
+
6
+ from ..constants import SUN_MU
7
+ from ..Time_Functions import to_gps
8
+
9
+
10
+ def accel_point_sun(r: np.ndarray, time) -> np.ndarray:
11
+ """
12
+ Point-mass solar gravity in GCRF, given raw_time (same input as leapfrog).
13
+
14
+ Parameters
15
+ ----------
16
+ r : array_like, shape (3,)
17
+ Satellite position vector in GCRF (m), Earth-centered.
18
+ time : float or datetime-like or array_like
19
+ The time array element from leapfrog (same type you passed into leapfrog).
20
+
21
+ Returns
22
+ -------
23
+ a_sun : ndarray, shape (3,)
24
+ Acceleration from the Sun (m/s²).
25
+ """
26
+ # 1) Convert time to GPS seconds
27
+ time_gps = to_gps(time)
28
+ t = Time(time_gps, format="gps", scale="utc")
29
+
30
+ # 2) Get Sun position in GCRS at this time using JPL ephemeris
31
+ with solar_system_ephemeris.set('jpl'):
32
+ sun_gcrs = get_body("sun", t).transform_to(GCRS(obstime=t))
33
+
34
+ r_sun = sun_gcrs.cartesian.xyz.to(u.m).value
35
+
36
+ # 3) Compute delta and gravitational acceleration
37
+ delta = r_sun - r
38
+ return SUN_MU * delta / np.linalg.norm(delta) ** 3
@@ -0,0 +1,31 @@
1
+ # ssapy_toolkit/Accelerations/accel_uniform_earth.py
2
+
3
+ import numpy as np
4
+ from ..constants import EARTH_RADIUS, EARTH_MU
5
+
6
+
7
+ def accel_uniform_earth(r):
8
+ """
9
+ Piecewise gravity model:
10
+ - Outside Earth: point-mass gravity ~ -mu r / |r|^3
11
+ - Inside Earth (uniform-density sphere): linear gravity ~ -mu r / R^3
12
+
13
+ Parameters
14
+ ----------
15
+ r : array_like, shape (3,)
16
+ Position vector (m).
17
+
18
+ Returns
19
+ -------
20
+ a : ndarray, shape (3,)
21
+ Acceleration (m/s^2).
22
+ """
23
+ x, y, z = r
24
+ r_mag = np.sqrt(x**2 + y**2 + z**2)
25
+
26
+ if r_mag < EARTH_RADIUS:
27
+ factor = -EARTH_MU / (EARTH_RADIUS**3)
28
+ else:
29
+ factor = -EARTH_MU / (r_mag**3)
30
+
31
+ return np.array([factor * x, factor * y, factor * z])