open-space-toolkit-astrodynamics 17.2.0__py312-none-manylinux2014_x86_64.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 (151) hide show
  1. open_space_toolkit_astrodynamics-17.2.0.dist-info/METADATA +30 -0
  2. open_space_toolkit_astrodynamics-17.2.0.dist-info/RECORD +151 -0
  3. open_space_toolkit_astrodynamics-17.2.0.dist-info/WHEEL +5 -0
  4. open_space_toolkit_astrodynamics-17.2.0.dist-info/top_level.txt +1 -0
  5. open_space_toolkit_astrodynamics-17.2.0.dist-info/zip-safe +1 -0
  6. ostk/__init__.py +1 -0
  7. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-312-x86_64-linux-gnu.so +0 -0
  8. ostk/astrodynamics/__init__.py +11 -0
  9. ostk/astrodynamics/__init__.pyi +720 -0
  10. ostk/astrodynamics/access.pyi +577 -0
  11. ostk/astrodynamics/conjunction/__init__.pyi +121 -0
  12. ostk/astrodynamics/conjunction/close_approach.pyi +89 -0
  13. ostk/astrodynamics/conjunction/message/__init__.pyi +3 -0
  14. ostk/astrodynamics/conjunction/message/ccsds.pyi +705 -0
  15. ostk/astrodynamics/converters.py +130 -0
  16. ostk/astrodynamics/converters.pyi +58 -0
  17. ostk/astrodynamics/data/__init__.pyi +3 -0
  18. ostk/astrodynamics/data/provider.pyi +22 -0
  19. ostk/astrodynamics/dataframe.py +597 -0
  20. ostk/astrodynamics/display.py +281 -0
  21. ostk/astrodynamics/dynamics.pyi +311 -0
  22. ostk/astrodynamics/eclipse.pyi +70 -0
  23. ostk/astrodynamics/estimator.pyi +268 -0
  24. ostk/astrodynamics/event_condition.pyi +910 -0
  25. ostk/astrodynamics/flight/__init__.pyi +626 -0
  26. ostk/astrodynamics/flight/profile/__init__.pyi +99 -0
  27. ostk/astrodynamics/flight/profile/model.pyi +179 -0
  28. ostk/astrodynamics/flight/system.pyi +268 -0
  29. ostk/astrodynamics/guidance_law.pyi +416 -0
  30. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.17 +0 -0
  31. ostk/astrodynamics/pytrajectory/__init__.py +1 -0
  32. ostk/astrodynamics/pytrajectory/__init__.pyi +3 -0
  33. ostk/astrodynamics/pytrajectory/pystate.py +263 -0
  34. ostk/astrodynamics/pytrajectory/pystate.pyi +66 -0
  35. ostk/astrodynamics/solver.pyi +432 -0
  36. ostk/astrodynamics/test/__init__.py +1 -0
  37. ostk/astrodynamics/test/access/__init__.py +1 -0
  38. ostk/astrodynamics/test/access/test_generator.py +319 -0
  39. ostk/astrodynamics/test/access/test_visibility_criterion.py +201 -0
  40. ostk/astrodynamics/test/conftest.py +119 -0
  41. ostk/astrodynamics/test/conjunction/close_approach/__init__.py +0 -0
  42. ostk/astrodynamics/test/conjunction/close_approach/test_generator.py +228 -0
  43. ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py +1 -0
  44. ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py +325 -0
  45. ostk/astrodynamics/test/conjunction/message/ccsds/data/cdm.json +303 -0
  46. ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py +416 -0
  47. ostk/astrodynamics/test/conjunction/test_close_approach.py +244 -0
  48. ostk/astrodynamics/test/data/provider/test_off_nadir.py +58 -0
  49. ostk/astrodynamics/test/dynamics/__init__.py +1 -0
  50. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv +565 -0
  51. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv +100 -0
  52. ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
  53. ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
  54. ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
  55. ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
  56. ostk/astrodynamics/test/dynamics/test_tabulated.py +138 -0
  57. ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
  58. ostk/astrodynamics/test/dynamics/test_thruster.py +157 -0
  59. ostk/astrodynamics/test/eclipse/__init__.py +1 -0
  60. ostk/astrodynamics/test/eclipse/test_generator.py +138 -0
  61. ostk/astrodynamics/test/estimator/test_orbit_determination_solver.py +261 -0
  62. ostk/astrodynamics/test/estimator/test_tle_solver.py +216 -0
  63. ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
  64. ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
  65. ostk/astrodynamics/test/event_condition/test_brouwer_lyddane_mean_long_condition.py +135 -0
  66. ostk/astrodynamics/test/event_condition/test_coe_condition.py +135 -0
  67. ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
  68. ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
  69. ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
  70. ostk/astrodynamics/test/flight/__init__.py +1 -0
  71. ostk/astrodynamics/test/flight/profile/model/test_tabulated_profile.py +115 -0
  72. ostk/astrodynamics/test/flight/system/__init__.py +1 -0
  73. ostk/astrodynamics/test/flight/system/test_propulsion_system.py +64 -0
  74. ostk/astrodynamics/test/flight/system/test_satellite_system.py +83 -0
  75. ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
  76. ostk/astrodynamics/test/flight/test_maneuver.py +231 -0
  77. ostk/astrodynamics/test/flight/test_profile.py +293 -0
  78. ostk/astrodynamics/test/flight/test_system.py +45 -0
  79. ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +177 -0
  80. ostk/astrodynamics/test/guidance_law/test_guidance_law.py +60 -0
  81. ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py +164 -0
  82. ostk/astrodynamics/test/guidance_law/test_qlaw.py +209 -0
  83. ostk/astrodynamics/test/solvers/__init__.py +1 -0
  84. ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +196 -0
  85. ostk/astrodynamics/test/solvers/test_least_squares_solver.py +334 -0
  86. ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +161 -0
  87. ostk/astrodynamics/test/test_access.py +128 -0
  88. ostk/astrodynamics/test/test_converters.py +290 -0
  89. ostk/astrodynamics/test/test_dataframe.py +1355 -0
  90. ostk/astrodynamics/test/test_display.py +184 -0
  91. ostk/astrodynamics/test/test_event_condition.py +80 -0
  92. ostk/astrodynamics/test/test_import.py +26 -0
  93. ostk/astrodynamics/test/test_root_solver.py +70 -0
  94. ostk/astrodynamics/test/test_trajectory.py +126 -0
  95. ostk/astrodynamics/test/test_utilities.py +338 -0
  96. ostk/astrodynamics/test/test_viewer.py +318 -0
  97. ostk/astrodynamics/test/trajectory/__init__.py +1 -0
  98. ostk/astrodynamics/test/trajectory/model/test_nadir_trajectory.py +87 -0
  99. ostk/astrodynamics/test/trajectory/model/test_tabulated_trajectory.py +303 -0
  100. ostk/astrodynamics/test/trajectory/model/test_target_scan_trajectory.py +126 -0
  101. ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
  102. ostk/astrodynamics/test/trajectory/orbit/message/__init__.py +1 -0
  103. ostk/astrodynamics/test/trajectory/orbit/message/spacex/__init__.py +1 -0
  104. ostk/astrodynamics/test/trajectory/orbit/message/spacex/conftest.py +18 -0
  105. ostk/astrodynamics/test/trajectory/orbit/message/spacex/data/opm_1.yaml +44 -0
  106. ostk/astrodynamics/test/trajectory/orbit/message/spacex/test_opm.py +108 -0
  107. ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
  108. ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
  109. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
  110. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
  111. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
  112. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +305 -0
  113. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
  114. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +337 -0
  115. ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
  116. ostk/astrodynamics/test/trajectory/orbit/models/test_modified_equinoctial.py +142 -0
  117. ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +234 -0
  118. ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
  119. ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
  120. ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
  121. ostk/astrodynamics/test/trajectory/orbit/test_pass.py +75 -0
  122. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity.py +30 -0
  123. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py +18 -0
  124. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_acceleration.py +136 -0
  125. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py +107 -0
  126. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py +115 -0
  127. ostk/astrodynamics/test/trajectory/state/test_coordinate_broker.py +84 -0
  128. ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +58 -0
  129. ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +316 -0
  130. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
  131. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +119 -0
  132. ostk/astrodynamics/test/trajectory/test_model.py +1 -0
  133. ostk/astrodynamics/test/trajectory/test_orbit.py +212 -0
  134. ostk/astrodynamics/test/trajectory/test_propagator.py +452 -0
  135. ostk/astrodynamics/test/trajectory/test_segment.py +694 -0
  136. ostk/astrodynamics/test/trajectory/test_sequence.py +550 -0
  137. ostk/astrodynamics/test/trajectory/test_state.py +629 -0
  138. ostk/astrodynamics/test/trajectory/test_state_builder.py +172 -0
  139. ostk/astrodynamics/trajectory/__init__.pyi +1982 -0
  140. ostk/astrodynamics/trajectory/model.pyi +259 -0
  141. ostk/astrodynamics/trajectory/orbit/__init__.pyi +349 -0
  142. ostk/astrodynamics/trajectory/orbit/message/__init__.pyi +3 -0
  143. ostk/astrodynamics/trajectory/orbit/message/spacex.pyi +264 -0
  144. ostk/astrodynamics/trajectory/orbit/model/__init__.pyi +648 -0
  145. ostk/astrodynamics/trajectory/orbit/model/brouwerLyddaneMean.pyi +121 -0
  146. ostk/astrodynamics/trajectory/orbit/model/kepler.pyi +709 -0
  147. ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi +330 -0
  148. ostk/astrodynamics/trajectory/state/__init__.pyi +402 -0
  149. ostk/astrodynamics/trajectory/state/coordinate_subset.pyi +208 -0
  150. ostk/astrodynamics/utilities.py +396 -0
  151. ostk/astrodynamics/viewer.py +851 -0
@@ -0,0 +1,1355 @@
1
+ # Copyright © Loft Orbital Solutions Inc.
2
+
3
+ import pytest
4
+
5
+ import numpy as np
6
+
7
+ import pandas as pd
8
+
9
+ from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
10
+
11
+ from ostk.physics import Environment
12
+ from ostk.physics.time import Instant
13
+ from ostk.physics.time import DateTime
14
+ from ostk.physics.time import Duration
15
+ from ostk.physics.time import Scale
16
+ from ostk.physics.coordinate import Frame
17
+ from ostk.physics.coordinate import Position
18
+ from ostk.physics.coordinate import Velocity
19
+ from ostk.physics.coordinate.frame.provider.iau import Theory
20
+
21
+ from ostk.astrodynamics.converters import coerce_to_datetime
22
+ from ostk.astrodynamics.trajectory import State
23
+ from ostk.astrodynamics.trajectory.state import CoordinateSubset
24
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianPosition
25
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianVelocity
26
+ from ostk.astrodynamics.trajectory import StateBuilder
27
+ from ostk.astrodynamics.flight import Profile
28
+
29
+ from ostk.astrodynamics.dataframe import generate_states_from_dataframe
30
+ from ostk.astrodynamics.dataframe import generate_dataframe_from_states
31
+ from ostk.astrodynamics.dataframe import generate_profile_from_dataframe
32
+ from ostk.astrodynamics.dataframe import generate_dataframe_from_profile
33
+
34
+
35
+ class TestOrbitDataframe:
36
+ @pytest.fixture
37
+ def instant(self) -> Instant:
38
+ return Instant.date_time(DateTime.parse("2024-01-29T00:00:00"), Scale.UTC)
39
+
40
+ @pytest.fixture
41
+ def frame(self) -> Frame:
42
+ return Frame.GCRF()
43
+
44
+ @pytest.fixture
45
+ def position(self, frame: Frame) -> Position:
46
+ return Position.meters(
47
+ [755972.142139276024, -3390511.949699319433, 5955672.751532567665],
48
+ frame,
49
+ )
50
+
51
+ @pytest.fixture
52
+ def velocity(self, frame: Frame) -> Velocity:
53
+ return Velocity.meters_per_second(
54
+ [-563.764594800880, -6619.592151780337, -3685.668514834143],
55
+ frame,
56
+ )
57
+
58
+ @pytest.fixture
59
+ def attitude(self) -> Quaternion:
60
+ return Quaternion.xyzs(
61
+ -0.638160707740, -0.163520830523, 0.726693549038, 0.194751982966
62
+ )
63
+
64
+ @pytest.fixture
65
+ def angular_velocity(self) -> np.ndarray:
66
+ return np.array([0.0, 0.0, 0.0])
67
+
68
+ @pytest.fixture
69
+ def orbit_state(
70
+ self,
71
+ instant: Instant,
72
+ position: Position,
73
+ velocity: Velocity,
74
+ ) -> State:
75
+ return State(
76
+ instant=instant,
77
+ position=position,
78
+ velocity=velocity,
79
+ )
80
+
81
+ @pytest.fixture
82
+ def orbit_states(self, orbit_state: State) -> list[State]:
83
+ return [orbit_state, orbit_state, orbit_state, orbit_state, orbit_state]
84
+
85
+ @pytest.fixture
86
+ def profile_state(
87
+ self,
88
+ instant: Instant,
89
+ position: Position,
90
+ velocity: Velocity,
91
+ attitude: Quaternion,
92
+ angular_velocity: np.ndarray,
93
+ frame: Frame,
94
+ ) -> State:
95
+ return State(instant, position, velocity, attitude, angular_velocity, frame)
96
+
97
+ @pytest.fixture
98
+ def profile_states(self, profile_state: State) -> list[State]:
99
+ return [profile_state, profile_state, profile_state, profile_state, profile_state]
100
+
101
+ @pytest.fixture
102
+ def profile_dataframe_position_columns(self) -> list[str]:
103
+ return ["r_J2000 (IAU 2006)_x", "r_J2000 (IAU 2006)_y", "r_J2000 (IAU 2006)_z"]
104
+
105
+ @pytest.fixture
106
+ def profile_dataframe_velocity_columns(self) -> list[str]:
107
+ return ["v_J2000 (IAU 2006)_x", "v_J2000 (IAU 2006)_y", "v_J2000 (IAU 2006)_z"]
108
+
109
+ @pytest.fixture
110
+ def profile_dataframe_attitude_columns(self) -> list[str]:
111
+ return [
112
+ "q_B_J2000 (IAU 2006)_x",
113
+ "q_B_J2000 (IAU 2006)_y",
114
+ "q_B_J2000 (IAU 2006)_z",
115
+ "q_B_J2000 (IAU 2006)_s",
116
+ ]
117
+
118
+ @pytest.fixture
119
+ def profile_dataframe_angular_velocity_columns(self) -> list[str]:
120
+ return [
121
+ "w_B_J2000 (IAU 2006)_in_B_x",
122
+ "w_B_J2000 (IAU 2006)_in_B_y",
123
+ "w_B_J2000 (IAU 2006)_in_B_z",
124
+ ]
125
+
126
+ @pytest.fixture
127
+ def profile_dataframe(
128
+ self,
129
+ instant: Instant,
130
+ profile_dataframe_position_columns: list[str],
131
+ profile_dataframe_velocity_columns: list[str],
132
+ profile_dataframe_attitude_columns: list[str],
133
+ profile_dataframe_angular_velocity_columns: list[str],
134
+ ) -> pd.DataFrame:
135
+ return pd.DataFrame(
136
+ [
137
+ {
138
+ "Timestamp": coerce_to_datetime(instant),
139
+ **dict(zip(profile_dataframe_position_columns, [1.0, 2.0, 3.0])),
140
+ **dict(zip(profile_dataframe_velocity_columns, [4.0, 5.0, 6.0])),
141
+ **dict(zip(profile_dataframe_attitude_columns, [0.0, 0.0, 0.0, 1.0])),
142
+ **dict(
143
+ zip(profile_dataframe_angular_velocity_columns, [0.0, 0.0, 0.0])
144
+ ),
145
+ },
146
+ {
147
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(1.0)),
148
+ **dict(zip(profile_dataframe_position_columns, [11.0, 12.0, 13.0])),
149
+ **dict(zip(profile_dataframe_velocity_columns, [14.0, 15.0, 16.0])),
150
+ **dict(zip(profile_dataframe_attitude_columns, [0.0, 0.0, 1.0, 0.0])),
151
+ **dict(
152
+ zip(profile_dataframe_angular_velocity_columns, [1.0, 1.0, 1.0])
153
+ ),
154
+ },
155
+ {
156
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(2.0)),
157
+ **dict(zip(profile_dataframe_position_columns, [21.0, 22.0, 23.0])),
158
+ **dict(zip(profile_dataframe_velocity_columns, [24.0, 25.0, 26.0])),
159
+ **dict(zip(profile_dataframe_attitude_columns, [0.0, 0.0, 1.0, 0.0])),
160
+ **dict(
161
+ zip(profile_dataframe_angular_velocity_columns, [1.0, 1.0, 1.0])
162
+ ),
163
+ },
164
+ {
165
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(3.0)),
166
+ **dict(zip(profile_dataframe_position_columns, [31.0, 32.0, 33.0])),
167
+ **dict(zip(profile_dataframe_velocity_columns, [34.0, 35.0, 36.0])),
168
+ **dict(zip(profile_dataframe_attitude_columns, [0.0, 0.0, 1.0, 0.0])),
169
+ **dict(
170
+ zip(profile_dataframe_angular_velocity_columns, [1.0, 1.0, 1.0])
171
+ ),
172
+ },
173
+ {
174
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(4.0)),
175
+ **dict(zip(profile_dataframe_position_columns, [41.0, 42.0, 43.0])),
176
+ **dict(zip(profile_dataframe_velocity_columns, [44.0, 45.0, 46.0])),
177
+ **dict(zip(profile_dataframe_attitude_columns, [0.0, 0.0, 1.0, 0.0])),
178
+ **dict(
179
+ zip(profile_dataframe_angular_velocity_columns, [1.0, 1.0, 1.0])
180
+ ),
181
+ },
182
+ ]
183
+ )
184
+
185
+ @pytest.fixture
186
+ def profile_dataframe_indexed_timestamp(
187
+ self,
188
+ profile_dataframe: pd.DataFrame,
189
+ ) -> pd.DataFrame:
190
+ profile_dataframe.set_index("Timestamp", inplace=True)
191
+ return profile_dataframe
192
+
193
+ @pytest.fixture
194
+ def orbit_dataframe(self, instant: Instant) -> pd.DataFrame:
195
+ return pd.DataFrame(
196
+ [
197
+ {
198
+ "Timestamp": coerce_to_datetime(instant),
199
+ "r_GCRF_x": 1.0,
200
+ "r_GCRF_y": 2.0,
201
+ "r_GCRF_z": 3.0,
202
+ "v_GCRF_x": 4.0,
203
+ "v_GCRF_y": 5.0,
204
+ "v_GCRF_z": 6.0,
205
+ },
206
+ {
207
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(1.0)),
208
+ "r_GCRF_x": 11.0,
209
+ "r_GCRF_y": 12.0,
210
+ "r_GCRF_z": 13.0,
211
+ "v_GCRF_x": 14.0,
212
+ "v_GCRF_y": 15.0,
213
+ "v_GCRF_z": 16.0,
214
+ },
215
+ {
216
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(2.0)),
217
+ "r_GCRF_x": 21.0,
218
+ "r_GCRF_y": 22.0,
219
+ "r_GCRF_z": 23.0,
220
+ "v_GCRF_x": 24.0,
221
+ "v_GCRF_y": 25.0,
222
+ "v_GCRF_z": 26.0,
223
+ },
224
+ {
225
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(3.0)),
226
+ "r_GCRF_x": 31.0,
227
+ "r_GCRF_y": 32.0,
228
+ "r_GCRF_z": 33.0,
229
+ "v_GCRF_x": 34.0,
230
+ "v_GCRF_y": 35.0,
231
+ "v_GCRF_z": 36.0,
232
+ },
233
+ {
234
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(4.0)),
235
+ "r_GCRF_x": 41.0,
236
+ "r_GCRF_y": 42.0,
237
+ "r_GCRF_z": 43.0,
238
+ "v_GCRF_x": 44.0,
239
+ "v_GCRF_y": 45.0,
240
+ "v_GCRF_z": 46.0,
241
+ },
242
+ ]
243
+ )
244
+
245
+ @pytest.fixture
246
+ def orbit_dataframe_indexed_timestamp(
247
+ self, orbit_dataframe: pd.DataFrame
248
+ ) -> pd.DataFrame:
249
+ orbit_dataframe.set_index("Timestamp", inplace=True)
250
+ return orbit_dataframe
251
+
252
+ @pytest.fixture
253
+ def orbit_state_with_properties(
254
+ self,
255
+ instant: Instant,
256
+ position: Position,
257
+ velocity: Velocity,
258
+ ) -> State:
259
+ state_builder = StateBuilder(
260
+ frame=Frame.GCRF(),
261
+ coordinate_subsets=[
262
+ CartesianPosition.default(),
263
+ CartesianVelocity.default(),
264
+ CoordinateSubset.mass(),
265
+ CoordinateSubset.drag_coefficient(),
266
+ CoordinateSubset.surface_area(),
267
+ ],
268
+ )
269
+
270
+ return state_builder.build(
271
+ instant=instant,
272
+ coordinates=[
273
+ *position.get_coordinates(),
274
+ *velocity.get_coordinates(),
275
+ 100.0, # mass in kg
276
+ 2.2, # drag coefficient
277
+ 10.5, # surface area in m²
278
+ ],
279
+ )
280
+
281
+ @pytest.fixture
282
+ def orbit_states_with_properties(
283
+ self, orbit_state_with_properties: State
284
+ ) -> list[State]:
285
+ return [
286
+ orbit_state_with_properties,
287
+ orbit_state_with_properties,
288
+ orbit_state_with_properties,
289
+ orbit_state_with_properties,
290
+ orbit_state_with_properties,
291
+ ]
292
+
293
+ @pytest.fixture
294
+ def orbit_dataframe_with_properties(self, instant: Instant) -> pd.DataFrame:
295
+ return pd.DataFrame(
296
+ [
297
+ {
298
+ "Timestamp": coerce_to_datetime(instant),
299
+ "r_GCRF_x": 1.0,
300
+ "r_GCRF_y": 2.0,
301
+ "r_GCRF_z": 3.0,
302
+ "v_GCRF_x": 4.0,
303
+ "v_GCRF_y": 5.0,
304
+ "v_GCRF_z": 6.0,
305
+ "mass": 100.0,
306
+ "drag_coefficient": 2.2,
307
+ "surface_area": 10.5,
308
+ },
309
+ {
310
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(1.0)),
311
+ "r_GCRF_x": 11.0,
312
+ "r_GCRF_y": 12.0,
313
+ "r_GCRF_z": 13.0,
314
+ "v_GCRF_x": 14.0,
315
+ "v_GCRF_y": 15.0,
316
+ "v_GCRF_z": 16.0,
317
+ "mass": 99.5,
318
+ "drag_coefficient": 2.2,
319
+ "surface_area": 10.5,
320
+ },
321
+ {
322
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(2.0)),
323
+ "r_GCRF_x": 21.0,
324
+ "r_GCRF_y": 22.0,
325
+ "r_GCRF_z": 23.0,
326
+ "v_GCRF_x": 24.0,
327
+ "v_GCRF_y": 25.0,
328
+ "v_GCRF_z": 26.0,
329
+ "mass": 99.5,
330
+ "drag_coefficient": 2.2,
331
+ "surface_area": 10.5,
332
+ },
333
+ {
334
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(3.0)),
335
+ "r_GCRF_x": 31.0,
336
+ "r_GCRF_y": 32.0,
337
+ "r_GCRF_z": 33.0,
338
+ "v_GCRF_x": 34.0,
339
+ "v_GCRF_y": 35.0,
340
+ "v_GCRF_z": 36.0,
341
+ "mass": 99.5,
342
+ "drag_coefficient": 2.2,
343
+ "surface_area": 10.5,
344
+ },
345
+ {
346
+ "Timestamp": coerce_to_datetime(instant + Duration.minutes(4.0)),
347
+ "r_GCRF_x": 41.0,
348
+ "r_GCRF_y": 42.0,
349
+ "r_GCRF_z": 43.0,
350
+ "v_GCRF_x": 44.0,
351
+ "v_GCRF_y": 45.0,
352
+ "v_GCRF_z": 46.0,
353
+ "mass": 99.5,
354
+ "drag_coefficient": 2.2,
355
+ "surface_area": 10.5,
356
+ },
357
+ ]
358
+ )
359
+
360
+ def test_generate_orbit_states_from_dataframe_defaults_success(
361
+ self,
362
+ orbit_dataframe: pd.DataFrame,
363
+ ):
364
+ states: list[State] = generate_states_from_dataframe(orbit_dataframe)
365
+
366
+ for state in states:
367
+ assert len(state.get_coordinates()) == len(orbit_dataframe.columns) - 1
368
+
369
+ def test_generate_profile_states_from_dataframe_defaults_success(
370
+ self,
371
+ profile_dataframe: pd.DataFrame,
372
+ ):
373
+ states: list[State] = generate_states_from_dataframe(
374
+ profile_dataframe,
375
+ reference_frame=Frame.J2000(Theory.IAU_2006),
376
+ )
377
+
378
+ for state in states:
379
+ assert len(state.get_coordinates()) == len(profile_dataframe.columns) - 1
380
+
381
+ def test_generate_states_from_profile_dataframe_success(
382
+ self,
383
+ profile_dataframe: pd.DataFrame,
384
+ profile_dataframe_position_columns: list[str],
385
+ profile_dataframe_velocity_columns: list[str],
386
+ profile_dataframe_attitude_columns: list[str],
387
+ profile_dataframe_angular_velocity_columns: list[str],
388
+ ):
389
+ states: list[State] = generate_states_from_dataframe(
390
+ dataframe=profile_dataframe,
391
+ reference_frame=Frame.J2000(Theory.IAU_2006),
392
+ time_column="Timestamp",
393
+ position_columns=profile_dataframe_position_columns,
394
+ velocity_columns=profile_dataframe_velocity_columns,
395
+ attitude_columns=profile_dataframe_attitude_columns,
396
+ angular_velocity_columns=profile_dataframe_angular_velocity_columns,
397
+ )
398
+
399
+ for state in states:
400
+ assert len(state.get_coordinates()) == len(profile_dataframe.columns) - 1
401
+
402
+ def test_generate_states_from_orbit_dataframe_success(
403
+ self,
404
+ orbit_dataframe: pd.DataFrame,
405
+ ):
406
+ states: list[State] = generate_states_from_dataframe(
407
+ dataframe=orbit_dataframe,
408
+ reference_frame=Frame.GCRF(),
409
+ time_column="Timestamp",
410
+ position_columns=["r_GCRF_x", "r_GCRF_y", "r_GCRF_z"],
411
+ velocity_columns=["v_GCRF_x", "v_GCRF_y", "v_GCRF_z"],
412
+ )
413
+
414
+ for state in states:
415
+ assert len(state.get_coordinates()) == len(orbit_dataframe.columns) - 1
416
+
417
+ def test_generate_states_from_profile_dataframe_success_defined_columns_without_time(
418
+ self,
419
+ profile_dataframe_indexed_timestamp: pd.DataFrame,
420
+ profile_dataframe_position_columns: list[str],
421
+ profile_dataframe_velocity_columns: list[str],
422
+ profile_dataframe_attitude_columns: list[str],
423
+ profile_dataframe_angular_velocity_columns: list[str],
424
+ ):
425
+ states: list[State] = generate_states_from_dataframe(
426
+ dataframe=profile_dataframe_indexed_timestamp,
427
+ reference_frame=Frame.J2000(Theory.IAU_2006),
428
+ position_columns=profile_dataframe_position_columns,
429
+ velocity_columns=profile_dataframe_velocity_columns,
430
+ attitude_columns=profile_dataframe_attitude_columns,
431
+ angular_velocity_columns=profile_dataframe_angular_velocity_columns,
432
+ )
433
+
434
+ for state in states:
435
+ assert len(state.get_coordinates()) == len(
436
+ profile_dataframe_indexed_timestamp.columns
437
+ )
438
+
439
+ def test_generate_states_from_orbit_dataframe_success_defined_columnsout_with_time(
440
+ self,
441
+ orbit_dataframe_indexed_timestamp: pd.DataFrame,
442
+ ):
443
+ states: list[State] = generate_states_from_dataframe(
444
+ dataframe=orbit_dataframe_indexed_timestamp,
445
+ reference_frame=Frame.GCRF(),
446
+ position_columns=["r_GCRF_x", "r_GCRF_y", "r_GCRF_z"],
447
+ velocity_columns=["v_GCRF_x", "v_GCRF_y", "v_GCRF_z"],
448
+ )
449
+
450
+ for state in states:
451
+ assert len(state.get_coordinates()) == len(
452
+ orbit_dataframe_indexed_timestamp.columns
453
+ )
454
+
455
+ def test_generate_dataframe_from_profile_states_success_custom_columns(
456
+ self,
457
+ profile_states: list[State],
458
+ ):
459
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
460
+ states=profile_states,
461
+ time_column="t",
462
+ position_columns=["r_1", "r_2", "r_3"],
463
+ velocity_columns=["v_1", "v_2", "v_3"],
464
+ attitude_columns=["q_1", "q_2", "q_3", "q_4"],
465
+ angular_velocity_columns=["w_1", "w_2", "w_3"],
466
+ )
467
+
468
+ assert list(generated_dataframe.columns) == [
469
+ "r_1",
470
+ "r_2",
471
+ "r_3",
472
+ "v_1",
473
+ "v_2",
474
+ "v_3",
475
+ "q_1",
476
+ "q_2",
477
+ "q_3",
478
+ "q_4",
479
+ "w_1",
480
+ "w_2",
481
+ "w_3",
482
+ ]
483
+
484
+ def test_generate_dataframe_from_orbit_states_success_custom_columns(
485
+ self,
486
+ orbit_states: list[State],
487
+ ):
488
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
489
+ states=orbit_states,
490
+ time_column="t",
491
+ position_columns=["r_1", "r_2", "r_3"],
492
+ velocity_columns=["v_1", "v_2", "v_3"],
493
+ )
494
+
495
+ assert list(generated_dataframe.columns) == [
496
+ "r_1",
497
+ "r_2",
498
+ "r_3",
499
+ "v_1",
500
+ "v_2",
501
+ "v_3",
502
+ ]
503
+
504
+ def test_generate_dataframe_from_profile_states_success_custom_reference_frame(
505
+ self,
506
+ profile_states: list[State],
507
+ ):
508
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
509
+ states=profile_states,
510
+ reference_frame=Frame.ITRF(),
511
+ )
512
+
513
+ assert list(generated_dataframe.columns) == [
514
+ "r_ITRF_x",
515
+ "r_ITRF_y",
516
+ "r_ITRF_z",
517
+ "v_ITRF_x",
518
+ "v_ITRF_y",
519
+ "v_ITRF_z",
520
+ "q_B_ITRF_x",
521
+ "q_B_ITRF_y",
522
+ "q_B_ITRF_z",
523
+ "q_B_ITRF_s",
524
+ "w_B_ITRF_in_B_x",
525
+ "w_B_ITRF_in_B_y",
526
+ "w_B_ITRF_in_B_z",
527
+ ]
528
+
529
+ def test_generate_dataframe_from_orbit_states_success_set_time_index_disabled(
530
+ self,
531
+ orbit_states: list[State],
532
+ ):
533
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
534
+ states=orbit_states,
535
+ time_column="t",
536
+ position_columns=["r_1", "r_2", "r_3"],
537
+ velocity_columns=["v_1", "v_2", "v_3"],
538
+ set_time_index=False,
539
+ )
540
+
541
+ assert list(generated_dataframe.columns) == [
542
+ "t",
543
+ "r_1",
544
+ "r_2",
545
+ "r_3",
546
+ "v_1",
547
+ "v_2",
548
+ "v_3",
549
+ ]
550
+
551
+ def test_generate_dataframe_from_profile_states_success_set_time_index_disabled(
552
+ self,
553
+ profile_states: list[State],
554
+ ):
555
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
556
+ states=profile_states,
557
+ time_column="t",
558
+ position_columns=["r_1", "r_2", "r_3"],
559
+ velocity_columns=["v_1", "v_2", "v_3"],
560
+ attitude_columns=["q_1", "q_2", "q_3", "q_4"],
561
+ angular_velocity_columns=["w_1", "w_2", "w_3"],
562
+ set_time_index=False,
563
+ )
564
+
565
+ assert list(generated_dataframe.columns) == [
566
+ "t",
567
+ "r_1",
568
+ "r_2",
569
+ "r_3",
570
+ "v_1",
571
+ "v_2",
572
+ "v_3",
573
+ "q_1",
574
+ "q_2",
575
+ "q_3",
576
+ "q_4",
577
+ "w_1",
578
+ "w_2",
579
+ "w_3",
580
+ ]
581
+
582
+ def test_generate_states_from_dataframe_with_properties_success(
583
+ self,
584
+ orbit_dataframe_with_properties: pd.DataFrame,
585
+ ):
586
+ states: list[State] = generate_states_from_dataframe(
587
+ dataframe=orbit_dataframe_with_properties,
588
+ reference_frame=Frame.GCRF(),
589
+ )
590
+
591
+ for state in states:
592
+ assert (
593
+ len(state.get_coordinates()) == 9
594
+ ) # 3 position + 3 velocity + mass + drag_coefficient + surface_area
595
+ assert state.has_subset(CoordinateSubset.mass())
596
+ assert state.has_subset(CoordinateSubset.drag_coefficient())
597
+ assert state.has_subset(CoordinateSubset.surface_area())
598
+
599
+ def test_generate_dataframe_from_states_with_properties_success(
600
+ self,
601
+ orbit_states_with_properties: list[State],
602
+ ):
603
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
604
+ states=orbit_states_with_properties,
605
+ )
606
+
607
+ assert "mass" in generated_dataframe.columns
608
+ assert "drag_coefficient" in generated_dataframe.columns
609
+ assert "surface_area" in generated_dataframe.columns
610
+
611
+ # Verify the values are correct
612
+ assert generated_dataframe["mass"].iloc[0] == 100.0
613
+ assert generated_dataframe["drag_coefficient"].iloc[0] == 2.2
614
+ assert generated_dataframe["surface_area"].iloc[0] == 10.5
615
+
616
+ def test_generate_dataframe_from_states_with_custom_property_columns(
617
+ self,
618
+ orbit_states_with_properties: list[State],
619
+ ):
620
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_states(
621
+ states=orbit_states_with_properties,
622
+ time_column="t",
623
+ position_columns=["r_1", "r_2", "r_3"],
624
+ velocity_columns=["v_1", "v_2", "v_3"],
625
+ mass_column="spacecraft_mass",
626
+ drag_coefficient_column="cd",
627
+ surface_area_column="area",
628
+ )
629
+
630
+ assert "spacecraft_mass" in generated_dataframe.columns
631
+ assert "cd" in generated_dataframe.columns
632
+ assert "area" in generated_dataframe.columns
633
+
634
+ # Verify the values are correct
635
+ assert generated_dataframe["spacecraft_mass"].iloc[0] == 100.0
636
+ assert generated_dataframe["cd"].iloc[0] == 2.2
637
+ assert generated_dataframe["area"].iloc[0] == 10.5
638
+
639
+
640
+ class TestProfileDataframe:
641
+ @pytest.fixture
642
+ def environment(self) -> Environment:
643
+ return Environment.default()
644
+
645
+ @pytest.fixture
646
+ def epoch(self) -> Instant:
647
+ return Instant.date_time(DateTime(2020, 1, 1, 0, 0, 0), Scale.UTC)
648
+
649
+ @pytest.fixture
650
+ def dataframe(
651
+ self,
652
+ epoch: Instant,
653
+ ) -> pd.DataFrame:
654
+ return pd.DataFrame(
655
+ [
656
+ {
657
+ "Timestamp": coerce_to_datetime(epoch),
658
+ "r_GCRF_x": 1.0,
659
+ "r_GCRF_y": 2.0,
660
+ "r_GCRF_z": 3.0,
661
+ "v_GCRF_x": 4.0,
662
+ "v_GCRF_y": 5.0,
663
+ "v_GCRF_z": 6.0,
664
+ "q_B_GCRF_x": 0.0,
665
+ "q_B_GCRF_y": 0.0,
666
+ "q_B_GCRF_z": 0.0,
667
+ "q_B_GCRF_s": 1.0,
668
+ "w_B_GCRF_in_B_x": 0.0,
669
+ "w_B_GCRF_in_B_y": 0.0,
670
+ "w_B_GCRF_in_B_z": 0.0,
671
+ },
672
+ {
673
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(1.0)),
674
+ "r_GCRF_x": 11.0,
675
+ "r_GCRF_y": 12.0,
676
+ "r_GCRF_z": 13.0,
677
+ "v_GCRF_x": 14.0,
678
+ "v_GCRF_y": 15.0,
679
+ "v_GCRF_z": 16.0,
680
+ "q_B_GCRF_x": 0.0,
681
+ "q_B_GCRF_y": 0.0,
682
+ "q_B_GCRF_z": 1.0,
683
+ "q_B_GCRF_s": 0.0,
684
+ "w_B_GCRF_in_B_x": 1.0,
685
+ "w_B_GCRF_in_B_y": 1.0,
686
+ "w_B_GCRF_in_B_z": 1.0,
687
+ },
688
+ {
689
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(2.0)),
690
+ "r_GCRF_x": 21.0,
691
+ "r_GCRF_y": 22.0,
692
+ "r_GCRF_z": 23.0,
693
+ "v_GCRF_x": 24.0,
694
+ "v_GCRF_y": 25.0,
695
+ "v_GCRF_z": 26.0,
696
+ "q_B_GCRF_x": 0.0,
697
+ "q_B_GCRF_y": 0.0,
698
+ "q_B_GCRF_z": 1.0,
699
+ "q_B_GCRF_s": 0.0,
700
+ "w_B_GCRF_in_B_x": 1.0,
701
+ "w_B_GCRF_in_B_y": 1.0,
702
+ "w_B_GCRF_in_B_z": 1.0,
703
+ },
704
+ {
705
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(3.0)),
706
+ "r_GCRF_x": 31.0,
707
+ "r_GCRF_y": 32.0,
708
+ "r_GCRF_z": 33.0,
709
+ "v_GCRF_x": 34.0,
710
+ "v_GCRF_y": 35.0,
711
+ "v_GCRF_z": 36.0,
712
+ "q_B_GCRF_x": 0.0,
713
+ "q_B_GCRF_y": 0.0,
714
+ "q_B_GCRF_z": 1.0,
715
+ "q_B_GCRF_s": 0.0,
716
+ "w_B_GCRF_in_B_x": 1.0,
717
+ "w_B_GCRF_in_B_y": 1.0,
718
+ "w_B_GCRF_in_B_z": 1.0,
719
+ },
720
+ {
721
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(4.0)),
722
+ "r_GCRF_x": 41.0,
723
+ "r_GCRF_y": 42.0,
724
+ "r_GCRF_z": 43.0,
725
+ "v_GCRF_x": 44.0,
726
+ "v_GCRF_y": 45.0,
727
+ "v_GCRF_z": 46.0,
728
+ "q_B_GCRF_x": 0.0,
729
+ "q_B_GCRF_y": 0.0,
730
+ "q_B_GCRF_z": 1.0,
731
+ "q_B_GCRF_s": 0.0,
732
+ "w_B_GCRF_in_B_x": 1.0,
733
+ "w_B_GCRF_in_B_y": 1.0,
734
+ "w_B_GCRF_in_B_z": 1.0,
735
+ },
736
+ ]
737
+ )
738
+
739
+ @pytest.fixture
740
+ def profile(
741
+ self,
742
+ dataframe: pd.DataFrame,
743
+ ) -> Profile:
744
+ return generate_profile_from_dataframe(
745
+ dataframe=dataframe,
746
+ )
747
+
748
+ @pytest.fixture
749
+ def dataframe_indexed_timestamp(
750
+ self,
751
+ dataframe: pd.DataFrame,
752
+ ) -> pd.DataFrame:
753
+ dataframe.set_index("Timestamp", inplace=True)
754
+ return dataframe
755
+
756
+ @pytest.fixture
757
+ def dataframe_with_properties(
758
+ self,
759
+ epoch: Instant,
760
+ ) -> pd.DataFrame:
761
+ return pd.DataFrame(
762
+ [
763
+ {
764
+ "Timestamp": coerce_to_datetime(epoch),
765
+ "r_GCRF_x": 1.0,
766
+ "r_GCRF_y": 2.0,
767
+ "r_GCRF_z": 3.0,
768
+ "v_GCRF_x": 4.0,
769
+ "v_GCRF_y": 5.0,
770
+ "v_GCRF_z": 6.0,
771
+ "q_B_GCRF_x": 0.0,
772
+ "q_B_GCRF_y": 0.0,
773
+ "q_B_GCRF_z": 0.0,
774
+ "q_B_GCRF_s": 1.0,
775
+ "w_B_GCRF_in_B_x": 0.0,
776
+ "w_B_GCRF_in_B_y": 0.0,
777
+ "w_B_GCRF_in_B_z": 0.0,
778
+ "mass": 100.0,
779
+ "drag_coefficient": 2.2,
780
+ "surface_area": 10.5,
781
+ },
782
+ {
783
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(1.0)),
784
+ "r_GCRF_x": 11.0,
785
+ "r_GCRF_y": 12.0,
786
+ "r_GCRF_z": 13.0,
787
+ "v_GCRF_x": 14.0,
788
+ "v_GCRF_y": 15.0,
789
+ "v_GCRF_z": 16.0,
790
+ "q_B_GCRF_x": 0.0,
791
+ "q_B_GCRF_y": 0.0,
792
+ "q_B_GCRF_z": 1.0,
793
+ "q_B_GCRF_s": 0.0,
794
+ "w_B_GCRF_in_B_x": 1.0,
795
+ "w_B_GCRF_in_B_y": 1.0,
796
+ "w_B_GCRF_in_B_z": 1.0,
797
+ "mass": 99.5,
798
+ "drag_coefficient": 2.2,
799
+ "surface_area": 10.5,
800
+ },
801
+ {
802
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(2.0)),
803
+ "r_GCRF_x": 21.0,
804
+ "r_GCRF_y": 22.0,
805
+ "r_GCRF_z": 23.0,
806
+ "v_GCRF_x": 24.0,
807
+ "v_GCRF_y": 25.0,
808
+ "v_GCRF_z": 26.0,
809
+ "q_B_GCRF_x": 0.0,
810
+ "q_B_GCRF_y": 0.0,
811
+ "q_B_GCRF_z": 1.0,
812
+ "q_B_GCRF_s": 0.0,
813
+ "w_B_GCRF_in_B_x": 1.0,
814
+ "w_B_GCRF_in_B_y": 1.0,
815
+ "w_B_GCRF_in_B_z": 1.0,
816
+ "mass": 99.5,
817
+ "drag_coefficient": 2.2,
818
+ "surface_area": 10.5,
819
+ },
820
+ {
821
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(3.0)),
822
+ "r_GCRF_x": 31.0,
823
+ "r_GCRF_y": 32.0,
824
+ "r_GCRF_z": 33.0,
825
+ "v_GCRF_x": 34.0,
826
+ "v_GCRF_y": 35.0,
827
+ "v_GCRF_z": 36.0,
828
+ "q_B_GCRF_x": 0.0,
829
+ "q_B_GCRF_y": 0.0,
830
+ "q_B_GCRF_z": 1.0,
831
+ "q_B_GCRF_s": 0.0,
832
+ "w_B_GCRF_in_B_x": 1.0,
833
+ "w_B_GCRF_in_B_y": 1.0,
834
+ "w_B_GCRF_in_B_z": 1.0,
835
+ "mass": 99.5,
836
+ "drag_coefficient": 2.2,
837
+ "surface_area": 10.5,
838
+ },
839
+ {
840
+ "Timestamp": coerce_to_datetime(epoch + Duration.minutes(4.0)),
841
+ "r_GCRF_x": 41.0,
842
+ "r_GCRF_y": 42.0,
843
+ "r_GCRF_z": 43.0,
844
+ "v_GCRF_x": 44.0,
845
+ "v_GCRF_y": 45.0,
846
+ "v_GCRF_z": 46.0,
847
+ "q_B_GCRF_x": 0.0,
848
+ "q_B_GCRF_y": 0.0,
849
+ "q_B_GCRF_z": 1.0,
850
+ "q_B_GCRF_s": 0.0,
851
+ "w_B_GCRF_in_B_x": 1.0,
852
+ "w_B_GCRF_in_B_y": 1.0,
853
+ "w_B_GCRF_in_B_z": 1.0,
854
+ "mass": 99.5,
855
+ "drag_coefficient": 2.2,
856
+ "surface_area": 10.5,
857
+ },
858
+ ]
859
+ )
860
+
861
+ @pytest.fixture
862
+ def dataframe_with_properties_indexed_timestamp(
863
+ self,
864
+ dataframe_with_properties: pd.DataFrame,
865
+ ) -> pd.DataFrame:
866
+ dataframe_with_properties.set_index("Timestamp", inplace=True)
867
+ return dataframe_with_properties
868
+
869
+ def test_generate_profile_from_dataframe_success(
870
+ self,
871
+ epoch: Instant,
872
+ dataframe: pd.DataFrame,
873
+ ):
874
+ profile: Profile = generate_profile_from_dataframe(
875
+ dataframe=dataframe,
876
+ )
877
+
878
+ assert profile is not None
879
+
880
+ np.testing.assert_allclose(
881
+ profile.get_state_at(epoch).get_position().get_coordinates(),
882
+ np.array((1.0, 2.0, 3.0)),
883
+ atol=1e-8,
884
+ )
885
+
886
+ np.testing.assert_allclose(
887
+ profile.get_state_at(epoch).get_velocity().get_coordinates(),
888
+ np.array((4.0, 5.0, 6.0)),
889
+ atol=1e-8,
890
+ )
891
+
892
+ np.testing.assert_allclose(
893
+ profile.get_state_at(epoch).get_attitude().to_vector(Quaternion.Format.XYZS),
894
+ np.array((0.0, 0.0, 0.0, 1.0)),
895
+ atol=1e-8,
896
+ )
897
+
898
+ np.testing.assert_allclose(
899
+ profile.get_state_at(epoch).get_angular_velocity(),
900
+ np.array((0.0, 0.0, 0.0)),
901
+ atol=1e-8,
902
+ )
903
+
904
+ np.testing.assert_allclose(
905
+ profile.get_state_at(epoch + Duration.minutes(1.0))
906
+ .get_position()
907
+ .get_coordinates(),
908
+ np.array((11.0, 12.0, 13.0)),
909
+ atol=1e-8,
910
+ )
911
+
912
+ np.testing.assert_allclose(
913
+ profile.get_state_at(epoch + Duration.minutes(1.0))
914
+ .get_velocity()
915
+ .get_coordinates(),
916
+ np.array((14.0, 15.0, 16.0)),
917
+ atol=1e-8,
918
+ )
919
+
920
+ np.testing.assert_allclose(
921
+ profile.get_state_at(epoch + Duration.minutes(1.0))
922
+ .get_attitude()
923
+ .to_vector(Quaternion.Format.XYZS),
924
+ np.array((0.0, 0.0, 1.0, 0.0)),
925
+ atol=1e-8,
926
+ )
927
+
928
+ np.testing.assert_allclose(
929
+ profile.get_state_at(epoch + Duration.minutes(1.0)).get_angular_velocity(),
930
+ np.array((1.0, 1.0, 1.0)),
931
+ atol=1e-8,
932
+ )
933
+
934
+ assert profile.get_state_at(epoch).get_frame() == Frame.GCRF()
935
+
936
+ def test_generate_profile_from_dataframe_success_defined_columns_with_time(
937
+ self,
938
+ epoch: Instant,
939
+ dataframe: pd.DataFrame,
940
+ ):
941
+ profile: Profile = generate_profile_from_dataframe(
942
+ dataframe=dataframe,
943
+ time_column="Timestamp",
944
+ position_columns=["r_GCRF_x", "r_GCRF_y", "r_GCRF_z"],
945
+ velocity_columns=["v_GCRF_x", "v_GCRF_y", "v_GCRF_z"],
946
+ attitude_columns=["q_B_GCRF_x", "q_B_GCRF_y", "q_B_GCRF_z", "q_B_GCRF_s"],
947
+ angular_velocity_columns=[
948
+ "w_B_GCRF_in_B_x",
949
+ "w_B_GCRF_in_B_y",
950
+ "w_B_GCRF_in_B_z",
951
+ ],
952
+ )
953
+
954
+ assert profile is not None
955
+
956
+ np.testing.assert_allclose(
957
+ profile.get_state_at(epoch).get_position().get_coordinates(),
958
+ np.array((1.0, 2.0, 3.0)),
959
+ atol=1e-8,
960
+ )
961
+
962
+ np.testing.assert_allclose(
963
+ profile.get_state_at(epoch).get_velocity().get_coordinates(),
964
+ np.array((4.0, 5.0, 6.0)),
965
+ atol=1e-8,
966
+ )
967
+
968
+ np.testing.assert_allclose(
969
+ profile.get_state_at(epoch).get_attitude().to_vector(Quaternion.Format.XYZS),
970
+ np.array((0.0, 0.0, 0.0, 1.0)),
971
+ atol=1e-8,
972
+ )
973
+
974
+ np.testing.assert_allclose(
975
+ profile.get_state_at(epoch).get_angular_velocity(),
976
+ np.array((0.0, 0.0, 0.0)),
977
+ atol=1e-8,
978
+ )
979
+
980
+ np.testing.assert_allclose(
981
+ profile.get_state_at(epoch + Duration.minutes(1.0))
982
+ .get_position()
983
+ .get_coordinates(),
984
+ np.array((11.0, 12.0, 13.0)),
985
+ atol=1e-8,
986
+ )
987
+
988
+ np.testing.assert_allclose(
989
+ profile.get_state_at(epoch + Duration.minutes(1.0))
990
+ .get_velocity()
991
+ .get_coordinates(),
992
+ np.array((14.0, 15.0, 16.0)),
993
+ atol=1e-8,
994
+ )
995
+
996
+ np.testing.assert_allclose(
997
+ profile.get_state_at(epoch + Duration.minutes(1.0))
998
+ .get_attitude()
999
+ .to_vector(Quaternion.Format.XYZS),
1000
+ np.array((0.0, 0.0, 1.0, 0.0)),
1001
+ atol=1e-8,
1002
+ )
1003
+
1004
+ np.testing.assert_allclose(
1005
+ profile.get_state_at(epoch + Duration.minutes(1.0)).get_angular_velocity(),
1006
+ np.array((1.0, 1.0, 1.0)),
1007
+ atol=1e-8,
1008
+ )
1009
+
1010
+ assert profile.get_state_at(epoch).get_frame() == Frame.GCRF()
1011
+
1012
+ def test_generate_profile_from_dataframe_success_defined_columns_without_time(
1013
+ self,
1014
+ epoch: Instant,
1015
+ dataframe_indexed_timestamp: pd.DataFrame,
1016
+ ):
1017
+ profile: Profile = generate_profile_from_dataframe(
1018
+ dataframe=dataframe_indexed_timestamp,
1019
+ position_columns=["r_GCRF_x", "r_GCRF_y", "r_GCRF_z"],
1020
+ velocity_columns=["v_GCRF_x", "v_GCRF_y", "v_GCRF_z"],
1021
+ attitude_columns=["q_B_GCRF_x", "q_B_GCRF_y", "q_B_GCRF_z", "q_B_GCRF_s"],
1022
+ angular_velocity_columns=[
1023
+ "w_B_GCRF_in_B_x",
1024
+ "w_B_GCRF_in_B_y",
1025
+ "w_B_GCRF_in_B_z",
1026
+ ],
1027
+ )
1028
+
1029
+ assert profile is not None
1030
+
1031
+ np.testing.assert_allclose(
1032
+ profile.get_state_at(epoch).get_position().get_coordinates(),
1033
+ np.array((1.0, 2.0, 3.0)),
1034
+ atol=1e-8,
1035
+ )
1036
+
1037
+ np.testing.assert_allclose(
1038
+ profile.get_state_at(epoch).get_velocity().get_coordinates(),
1039
+ np.array((4.0, 5.0, 6.0)),
1040
+ atol=1e-8,
1041
+ )
1042
+
1043
+ np.testing.assert_allclose(
1044
+ profile.get_state_at(epoch).get_attitude().to_vector(Quaternion.Format.XYZS),
1045
+ np.array((0.0, 0.0, 0.0, 1.0)),
1046
+ atol=1e-8,
1047
+ )
1048
+
1049
+ np.testing.assert_allclose(
1050
+ profile.get_state_at(epoch).get_angular_velocity(),
1051
+ np.array((0.0, 0.0, 0.0)),
1052
+ atol=1e-8,
1053
+ )
1054
+
1055
+ np.testing.assert_allclose(
1056
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1057
+ .get_position()
1058
+ .get_coordinates(),
1059
+ np.array((11.0, 12.0, 13.0)),
1060
+ atol=1e-8,
1061
+ )
1062
+
1063
+ np.testing.assert_allclose(
1064
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1065
+ .get_velocity()
1066
+ .get_coordinates(),
1067
+ np.array((14.0, 15.0, 16.0)),
1068
+ atol=1e-8,
1069
+ )
1070
+
1071
+ np.testing.assert_allclose(
1072
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1073
+ .get_attitude()
1074
+ .to_vector(Quaternion.Format.XYZS),
1075
+ np.array((0.0, 0.0, 1.0, 0.0)),
1076
+ atol=1e-8,
1077
+ )
1078
+
1079
+ np.testing.assert_allclose(
1080
+ profile.get_state_at(epoch + Duration.minutes(1.0)).get_angular_velocity(),
1081
+ np.array((1.0, 1.0, 1.0)),
1082
+ atol=1e-8,
1083
+ )
1084
+
1085
+ assert profile.get_state_at(epoch).get_frame() == Frame.GCRF()
1086
+
1087
+ def test_generate_profile_from_dataframe_success_no_angular_velocity_columns(
1088
+ self,
1089
+ epoch: Instant,
1090
+ dataframe_indexed_timestamp: pd.DataFrame,
1091
+ ):
1092
+ dataframe_indexed_timestamp.drop(
1093
+ ["w_B_GCRF_in_B_x", "w_B_GCRF_in_B_y", "w_B_GCRF_in_B_z"],
1094
+ axis=1,
1095
+ inplace=True,
1096
+ )
1097
+
1098
+ profile: Profile = generate_profile_from_dataframe(
1099
+ dataframe=dataframe_indexed_timestamp,
1100
+ position_columns=["r_GCRF_x", "r_GCRF_y", "r_GCRF_z"],
1101
+ velocity_columns=["v_GCRF_x", "v_GCRF_y", "v_GCRF_z"],
1102
+ attitude_columns=["q_B_GCRF_x", "q_B_GCRF_y", "q_B_GCRF_z", "q_B_GCRF_s"],
1103
+ )
1104
+
1105
+ assert profile is not None
1106
+
1107
+ np.testing.assert_allclose(
1108
+ profile.get_state_at(epoch).get_position().get_coordinates(),
1109
+ np.array((1.0, 2.0, 3.0)),
1110
+ atol=1e-8,
1111
+ )
1112
+
1113
+ np.testing.assert_allclose(
1114
+ profile.get_state_at(epoch).get_velocity().get_coordinates(),
1115
+ np.array((4.0, 5.0, 6.0)),
1116
+ atol=1e-8,
1117
+ )
1118
+
1119
+ np.testing.assert_allclose(
1120
+ profile.get_state_at(epoch).get_attitude().to_vector(Quaternion.Format.XYZS),
1121
+ np.array((0.0, 0.0, 0.0, 1.0)),
1122
+ atol=1e-8,
1123
+ )
1124
+
1125
+ np.testing.assert_allclose(
1126
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1127
+ .get_position()
1128
+ .get_coordinates(),
1129
+ np.array((11.0, 12.0, 13.0)),
1130
+ atol=1e-8,
1131
+ )
1132
+
1133
+ np.testing.assert_allclose(
1134
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1135
+ .get_velocity()
1136
+ .get_coordinates(),
1137
+ np.array((14.0, 15.0, 16.0)),
1138
+ atol=1e-8,
1139
+ )
1140
+
1141
+ np.testing.assert_allclose(
1142
+ profile.get_state_at(epoch + Duration.minutes(1.0))
1143
+ .get_attitude()
1144
+ .to_vector(Quaternion.Format.XYZS),
1145
+ np.array((0.0, 0.0, 1.0, 0.0)),
1146
+ atol=1e-8,
1147
+ )
1148
+
1149
+ assert profile.get_state_at(epoch).get_frame() == Frame.GCRF()
1150
+
1151
+ def test_generate_dataframe_from_profile_success(
1152
+ self,
1153
+ epoch: Instant,
1154
+ profile: Profile,
1155
+ dataframe_indexed_timestamp: pd.DataFrame,
1156
+ ):
1157
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1158
+ profile=profile,
1159
+ instants=[
1160
+ epoch,
1161
+ epoch + Duration.minutes(1.0),
1162
+ epoch + Duration.minutes(2.0),
1163
+ epoch + Duration.minutes(3.0),
1164
+ epoch + Duration.minutes(4.0),
1165
+ ],
1166
+ )
1167
+
1168
+ pd.testing.assert_frame_equal(generated_dataframe, dataframe_indexed_timestamp)
1169
+
1170
+ def test_generate_dataframe_from_profile_success_custom_columns(
1171
+ self,
1172
+ epoch: Instant,
1173
+ profile: Profile,
1174
+ ):
1175
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1176
+ profile=profile,
1177
+ instants=[
1178
+ epoch,
1179
+ epoch + Duration.minutes(1.0),
1180
+ epoch + Duration.minutes(2.0),
1181
+ epoch + Duration.minutes(3.0),
1182
+ epoch + Duration.minutes(4.0),
1183
+ ],
1184
+ time_column="t",
1185
+ position_columns=["r_1", "r_2", "r_3"],
1186
+ velocity_columns=["v_1", "v_2", "v_3"],
1187
+ attitude_columns=["q_1", "q_2", "q_3", "q_4"],
1188
+ angular_velocity_columns=["w_1", "w_2", "w_3"],
1189
+ )
1190
+
1191
+ assert list(generated_dataframe.columns) == [
1192
+ "r_1",
1193
+ "r_2",
1194
+ "r_3",
1195
+ "v_1",
1196
+ "v_2",
1197
+ "v_3",
1198
+ "q_1",
1199
+ "q_2",
1200
+ "q_3",
1201
+ "q_4",
1202
+ "w_1",
1203
+ "w_2",
1204
+ "w_3",
1205
+ ]
1206
+
1207
+ def test_generate_dataframe_from_profile_success_custom_reference_frame(
1208
+ self,
1209
+ epoch: Instant,
1210
+ profile: Profile,
1211
+ ):
1212
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1213
+ profile=profile,
1214
+ instants=[
1215
+ epoch,
1216
+ epoch + Duration.minutes(1.0),
1217
+ ],
1218
+ reference_frame=Frame.ITRF(),
1219
+ )
1220
+
1221
+ assert list(generated_dataframe.columns) == [
1222
+ "r_ITRF_x",
1223
+ "r_ITRF_y",
1224
+ "r_ITRF_z",
1225
+ "v_ITRF_x",
1226
+ "v_ITRF_y",
1227
+ "v_ITRF_z",
1228
+ "q_B_ITRF_x",
1229
+ "q_B_ITRF_y",
1230
+ "q_B_ITRF_z",
1231
+ "q_B_ITRF_s",
1232
+ "w_B_ITRF_in_B_x",
1233
+ "w_B_ITRF_in_B_y",
1234
+ "w_B_ITRF_in_B_z",
1235
+ ]
1236
+
1237
+ def test_generate_dataframe_from_profile_success_set_time_index_disabled(
1238
+ self,
1239
+ epoch: Instant,
1240
+ profile: Profile,
1241
+ ):
1242
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1243
+ profile=profile,
1244
+ instants=[
1245
+ epoch,
1246
+ epoch + Duration.minutes(1.0),
1247
+ ],
1248
+ time_column="t",
1249
+ position_columns=["r_1", "r_2", "r_3"],
1250
+ velocity_columns=["v_1", "v_2", "v_3"],
1251
+ attitude_columns=["q_1", "q_2", "q_3", "q_4"],
1252
+ angular_velocity_columns=["w_1", "w_2", "w_3"],
1253
+ set_time_index=False,
1254
+ )
1255
+
1256
+ assert list(generated_dataframe.columns) == [
1257
+ "t",
1258
+ "r_1",
1259
+ "r_2",
1260
+ "r_3",
1261
+ "v_1",
1262
+ "v_2",
1263
+ "v_3",
1264
+ "q_1",
1265
+ "q_2",
1266
+ "q_3",
1267
+ "q_4",
1268
+ "w_1",
1269
+ "w_2",
1270
+ "w_3",
1271
+ ]
1272
+
1273
+ def test_generate_profile_from_dataframe_with_properties_success(
1274
+ self,
1275
+ epoch: Instant,
1276
+ dataframe_with_properties: pd.DataFrame,
1277
+ ):
1278
+ profile: Profile = generate_profile_from_dataframe(
1279
+ dataframe=dataframe_with_properties,
1280
+ )
1281
+
1282
+ assert profile is not None
1283
+ state = profile.get_state_at(epoch)
1284
+
1285
+ assert state.has_subset(CoordinateSubset.mass())
1286
+ assert state.has_subset(CoordinateSubset.drag_coefficient())
1287
+ assert state.has_subset(CoordinateSubset.surface_area())
1288
+
1289
+ # Verify extracted coordinates are correct
1290
+ assert state.extract_coordinate(CoordinateSubset.mass())[0] == 100.0
1291
+ assert state.extract_coordinate(CoordinateSubset.drag_coefficient())[0] == 2.2
1292
+ assert state.extract_coordinate(CoordinateSubset.surface_area())[0] == 10.5
1293
+
1294
+ def test_generate_dataframe_from_profile_with_properties_success(
1295
+ self,
1296
+ epoch: Instant,
1297
+ dataframe_with_properties_indexed_timestamp: pd.DataFrame,
1298
+ ):
1299
+ # Create a profile with properties
1300
+ profile_with_props = generate_profile_from_dataframe(
1301
+ dataframe=dataframe_with_properties_indexed_timestamp,
1302
+ )
1303
+
1304
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1305
+ profile=profile_with_props,
1306
+ instants=[
1307
+ epoch,
1308
+ epoch + Duration.minutes(1.0),
1309
+ ],
1310
+ )
1311
+
1312
+ assert "mass" in generated_dataframe.columns
1313
+ assert "drag_coefficient" in generated_dataframe.columns
1314
+ assert "surface_area" in generated_dataframe.columns
1315
+
1316
+ # Verify the values
1317
+ assert generated_dataframe["mass"].iloc[0] == 100.0
1318
+ assert generated_dataframe["drag_coefficient"].iloc[0] == 2.2
1319
+ assert generated_dataframe["surface_area"].iloc[0] == 10.5
1320
+
1321
+ def test_generate_dataframe_from_profile_with_custom_property_columns(
1322
+ self,
1323
+ epoch: Instant,
1324
+ dataframe_with_properties_indexed_timestamp: pd.DataFrame,
1325
+ ):
1326
+ # Create a profile with properties
1327
+ profile_with_props = generate_profile_from_dataframe(
1328
+ dataframe=dataframe_with_properties_indexed_timestamp,
1329
+ )
1330
+
1331
+ generated_dataframe: pd.DataFrame = generate_dataframe_from_profile(
1332
+ profile=profile_with_props,
1333
+ instants=[
1334
+ epoch,
1335
+ epoch + Duration.minutes(1.0),
1336
+ ],
1337
+ time_column="t",
1338
+ position_columns=["r_1", "r_2", "r_3"],
1339
+ velocity_columns=["v_1", "v_2", "v_3"],
1340
+ attitude_columns=["q_1", "q_2", "q_3", "q_4"],
1341
+ angular_velocity_columns=["w_1", "w_2", "w_3"],
1342
+ mass_column="spacecraft_mass",
1343
+ drag_coefficient_column="cd",
1344
+ surface_area_column="area",
1345
+ set_time_index=False,
1346
+ )
1347
+
1348
+ assert "spacecraft_mass" in generated_dataframe.columns
1349
+ assert "cd" in generated_dataframe.columns
1350
+ assert "area" in generated_dataframe.columns
1351
+
1352
+ # Verify the values
1353
+ assert generated_dataframe["spacecraft_mass"].iloc[0] == 100.0
1354
+ assert generated_dataframe["cd"].iloc[0] == 2.2
1355
+ assert generated_dataframe["area"].iloc[0] == 10.5