open-space-toolkit-astrodynamics 9.0.3__py39-none-manylinux2014_aarch64.whl → 13.0.2__py39-none-manylinux2014_aarch64.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 (31) hide show
  1. {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/METADATA +5 -2
  2. {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/RECORD +30 -28
  3. {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/WHEEL +1 -1
  4. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-39-aarch64-linux-gnu.so +0 -0
  5. ostk/astrodynamics/converters.py +36 -93
  6. ostk/astrodynamics/dataframe.py +479 -0
  7. ostk/astrodynamics/display.py +2 -0
  8. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.13 +0 -0
  9. ostk/astrodynamics/pytrajectory/pystate.py +216 -1
  10. ostk/astrodynamics/test/conftest.py +2 -2
  11. ostk/astrodynamics/test/flight/test_maneuver.py +8 -12
  12. ostk/astrodynamics/test/flight/test_profile.py +155 -55
  13. ostk/astrodynamics/test/test_converters.py +43 -140
  14. ostk/astrodynamics/test/test_dataframe.py +875 -0
  15. ostk/astrodynamics/test/test_display.py +2 -3
  16. ostk/astrodynamics/test/test_event_condition.py +27 -7
  17. ostk/astrodynamics/test/test_trajectory.py +116 -38
  18. ostk/astrodynamics/test/test_utilities.py +31 -46
  19. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +13 -0
  20. ostk/astrodynamics/test/trajectory/orbit/test_pass.py +9 -0
  21. ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +3 -0
  22. ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +2 -2
  23. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +48 -4
  24. ostk/astrodynamics/test/trajectory/test_orbit.py +42 -2
  25. ostk/astrodynamics/test/trajectory/test_segment.py +99 -1
  26. ostk/astrodynamics/test/trajectory/test_sequence.py +53 -0
  27. ostk/astrodynamics/test/trajectory/test_state.py +306 -0
  28. ostk/astrodynamics/utilities.py +125 -36
  29. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.9 +0 -0
  30. {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/top_level.txt +0 -0
  31. {open_space_toolkit_astrodynamics-9.0.3.dist-info → open_space_toolkit_astrodynamics-13.0.2.dist-info}/zip-safe +0 -0
@@ -11,8 +11,8 @@ from ostk.physics.time import Duration
11
11
  from ostk.physics.coordinate import Frame
12
12
  from ostk.physics.environment.object.celestial import Earth
13
13
 
14
- from ostk.astrodynamics.flight import Maneuver
15
14
  from ostk.astrodynamics.flight.system import SatelliteSystem
15
+ from ostk.astrodynamics import Dynamics
16
16
  from ostk.astrodynamics.dynamics import CentralBodyGravity
17
17
  from ostk.astrodynamics.dynamics import PositionDerivative
18
18
  from ostk.astrodynamics.dynamics import Thruster
@@ -135,6 +135,104 @@ def thruster_dynamics() -> Thruster:
135
135
  )
136
136
 
137
137
 
138
+ @pytest.fixture
139
+ def segment_solution(dynamics: list[Dynamics], state: State) -> Segment.Solution:
140
+ return Segment.Solution(
141
+ name="A Segment",
142
+ dynamics=dynamics,
143
+ states=[
144
+ state,
145
+ ],
146
+ condition_is_satisfied=True,
147
+ segment_type=Segment.Type.Coast,
148
+ )
149
+
150
+
151
+ class TestSegmentSolution:
152
+ def test_constructors(
153
+ self,
154
+ segment_solution: Segment.Solution,
155
+ ):
156
+ assert segment_solution is not None
157
+ assert segment_solution.name is not None
158
+ assert segment_solution.dynamics is not None
159
+ assert segment_solution.states is not None
160
+ assert segment_solution.condition_is_satisfied is not None
161
+ assert segment_solution.segment_type is not None
162
+
163
+ def test_getters_and_accessors(
164
+ self,
165
+ segment_solution: Segment.Solution,
166
+ ):
167
+ assert segment_solution.access_start_instant() is not None
168
+ assert segment_solution.access_end_instant() is not None
169
+ assert segment_solution.get_interval() is not None
170
+ assert segment_solution.get_initial_mass() is not None
171
+ assert segment_solution.get_final_mass() is not None
172
+ assert segment_solution.get_propagation_duration() is not None
173
+
174
+ def test_compute_delta_v(
175
+ self,
176
+ segment_solution: Segment.Solution,
177
+ ):
178
+ assert segment_solution.compute_delta_v(1500.0) is not None
179
+
180
+ def test_compute_delta_mass(
181
+ self,
182
+ segment_solution: Segment.Solution,
183
+ ):
184
+ assert segment_solution.compute_delta_mass() is not None
185
+
186
+ @pytest.mark.skip(reason="Not implemented yet")
187
+ def test_extract_maneuvers(
188
+ self,
189
+ segment_solution: Segment.Solution,
190
+ ):
191
+ assert segment_solution.extract_maneuvers(Frame.GCRF()) is not None
192
+
193
+ def test_calculate_states_at(
194
+ self,
195
+ segment_solution: Segment.Solution,
196
+ ):
197
+ assert (
198
+ segment_solution.calculate_states_at(
199
+ [
200
+ segment_solution.states[0].get_instant(),
201
+ ],
202
+ NumericalSolver.default_conditional(),
203
+ )
204
+ is not None
205
+ )
206
+
207
+ def get_dynamics_contribution(
208
+ self,
209
+ segment_solution: Segment.Solution,
210
+ ):
211
+ assert (
212
+ segment_solution.get_dynamics_contribution(
213
+ segment_solution.dynamics[0], Frame.GCRF()
214
+ )
215
+ is not None
216
+ )
217
+
218
+ def get_dynamics_acceleration_contribution(
219
+ self,
220
+ segment_solution: Segment.Solution,
221
+ ):
222
+ assert (
223
+ segment_solution.get_dynamics_acceleration_contribution(
224
+ segment_solution.dynamics[0], Frame.GCRF()
225
+ )
226
+ is not None
227
+ )
228
+
229
+ def get_all_dynamics_contributions(
230
+ self,
231
+ segment_solution: Segment.Solution,
232
+ ):
233
+ assert segment_solution.get_all_dynamics_contributions(Frame.GCRF()) is not None
234
+
235
+
138
236
  class TestSegment:
139
237
  def test_get_name(self, coast_duration_segment: Segment, name: str):
140
238
  assert coast_duration_segment.get_name() == name
@@ -339,6 +339,59 @@ def instants(state: State) -> list[Instant]:
339
339
  return [state.get_instant(), state.get_instant() + Duration.minutes(1.0)]
340
340
 
341
341
 
342
+ @pytest.fixture
343
+ def sequence_solution(
344
+ segment_solution: Segment.Solution,
345
+ ):
346
+ return Sequence.Solution(
347
+ segment_solutions=[
348
+ segment_solution,
349
+ ],
350
+ execution_is_complete=True,
351
+ )
352
+
353
+
354
+ class TestSequenceSolution:
355
+ def test_properties(
356
+ self,
357
+ sequence_solution: Sequence.Solution,
358
+ ):
359
+ assert sequence_solution is not None
360
+ assert len(sequence_solution.segment_solutions) == 1
361
+ assert sequence_solution.execution_is_complete
362
+
363
+ def test_getters_and_accessors(
364
+ self,
365
+ sequence_solution: Sequence.Solution,
366
+ ):
367
+ assert sequence_solution.access_start_instant() is not None
368
+ assert sequence_solution.access_end_instant() is not None
369
+
370
+ assert sequence_solution.get_states() is not None
371
+ assert sequence_solution.get_initial_mass() is not None
372
+ assert sequence_solution.get_final_mass() is not None
373
+ assert sequence_solution.get_propagation_duration() is not None
374
+
375
+ assert sequence_solution.compute_delta_mass() is not None
376
+ assert sequence_solution.compute_delta_v(1500.0) is not None
377
+
378
+ def test_calculate_states_at(
379
+ self,
380
+ sequence_solution: Sequence.Solution,
381
+ numerical_solver: NumericalSolver,
382
+ ):
383
+ instants: list[Instant] = sequence_solution.get_interval().generate_grid(
384
+ Duration.seconds(10.0)
385
+ )
386
+ states: list[State] = sequence_solution.calculate_states_at(
387
+ instants,
388
+ numerical_solver,
389
+ )
390
+
391
+ assert states is not None
392
+ assert len(states) == len(instants)
393
+
394
+
342
395
  class TestSequence:
343
396
  def test_get_segments(
344
397
  self,
@@ -2,6 +2,8 @@
2
2
 
3
3
  import pytest
4
4
 
5
+ from datetime import datetime, timezone
6
+
5
7
  import numpy as np
6
8
 
7
9
  from ostk.mathematics.geometry.d3.transformation.rotation import Quaternion
@@ -177,6 +179,310 @@ class TestState:
177
179
  assert custom_state == state
178
180
  assert custom_state is not state
179
181
 
182
+ def test_from_dict_with_eci_coordinates(self):
183
+ data = {
184
+ "timestamp": datetime.now(timezone.utc),
185
+ "rx_eci": 7000.0,
186
+ "ry_eci": 0.0,
187
+ "rz_eci": 0.0,
188
+ "vx_eci": 0.0,
189
+ "vy_eci": 7.5,
190
+ "vz_eci": 0.0,
191
+ }
192
+
193
+ state: State = State.from_dict(data)
194
+
195
+ assert state is not None
196
+ assert isinstance(state, State)
197
+ assert state.get_frame() == Frame.GCRF()
198
+ assert state.get_size() == 6
199
+
200
+ def test_from_dict_with_ecef_coordinates(self):
201
+ data = {
202
+ "timestamp": datetime.now(timezone.utc).isoformat(),
203
+ "rx_ecef": 7000.0,
204
+ "ry_ecef": 0.0,
205
+ "rz_ecef": 0.0,
206
+ "vx_ecef": 0.0,
207
+ "vy_ecef": 7.5,
208
+ "vz_ecef": 0.0,
209
+ }
210
+
211
+ state: State = State.from_dict(data)
212
+
213
+ assert state is not None
214
+ assert isinstance(state, State)
215
+ assert state.get_frame() == Frame.ITRF()
216
+ assert state.get_size() == 6
217
+
218
+ def test_from_dict_with_generic_coordinates(self):
219
+ data = {
220
+ "timestamp": datetime.now(timezone.utc).isoformat(),
221
+ "rx": 7000.0,
222
+ "ry": 0.0,
223
+ "rz": 0.0,
224
+ "vx": 0.0,
225
+ "vy": 7.5,
226
+ "vz": 0.0,
227
+ "frame": "GCRF",
228
+ }
229
+
230
+ state: State = State.from_dict(data)
231
+
232
+ assert state is not None
233
+ assert isinstance(state, State)
234
+ assert state.get_frame() == Frame.GCRF()
235
+ assert state.get_size() == 6
236
+
237
+ with pytest.raises(
238
+ ValueError, match="Frame must be provided for generic columns."
239
+ ):
240
+ data = {
241
+ "timestamp": datetime.now(timezone.utc).isoformat(),
242
+ "rx": 7000.0,
243
+ "ry": 0.0,
244
+ "rz": 0.0,
245
+ "vx": 0.0,
246
+ "vy": 7.5,
247
+ "vz": 0.0,
248
+ }
249
+
250
+ State.from_dict(data)
251
+
252
+ with pytest.raises(ValueError, match="No frame exists with name \\[RANDOM\\]."):
253
+ data = {
254
+ "timestamp": datetime.now(timezone.utc).isoformat(),
255
+ "rx": 7000.0,
256
+ "ry": 0.0,
257
+ "rz": 0.0,
258
+ "vx": 0.0,
259
+ "vy": 7.5,
260
+ "vz": 0.0,
261
+ "frame": "RANDOM",
262
+ }
263
+
264
+ State.from_dict(data)
265
+
266
+ with pytest.raises(ValueError, match="Invalid frame data \\[123\\]"):
267
+ data = {
268
+ "timestamp": datetime.now(timezone.utc).isoformat(),
269
+ "rx": 7000.0,
270
+ "ry": 0.0,
271
+ "rz": 0.0,
272
+ "vx": 0.0,
273
+ "vy": 7.5,
274
+ "vz": 0.0,
275
+ "frame": 123,
276
+ }
277
+
278
+ State.from_dict(data)
279
+
280
+ def test_from_dict_with_attitude_quaternion(self):
281
+ data = {
282
+ "timestamp": datetime.now(timezone.utc).isoformat(),
283
+ "rx_eci": 7000.0,
284
+ "ry_eci": 0.0,
285
+ "rz_eci": 0.0,
286
+ "vx_eci": 0.0,
287
+ "vy_eci": 7.5,
288
+ "vz_eci": 0.0,
289
+ "q_B_ECI_x": 0.0,
290
+ "q_B_ECI_y": 0.0,
291
+ "q_B_ECI_z": 0.0,
292
+ "q_B_ECI_s": 1.0,
293
+ }
294
+
295
+ state: State = State.from_dict(data)
296
+
297
+ assert state is not None
298
+ assert isinstance(state, State)
299
+ assert state.get_frame() == Frame.GCRF()
300
+ assert state.get_size() == 10
301
+
302
+ def test_from_dict_with_angular_velocity(self):
303
+ data = {
304
+ "timestamp": datetime.now(timezone.utc).isoformat(),
305
+ "rx_eci": 7000.0,
306
+ "ry_eci": 0.0,
307
+ "rz_eci": 0.0,
308
+ "vx_eci": 0.0,
309
+ "vy_eci": 7.5,
310
+ "vz_eci": 0.0,
311
+ "w_B_ECI_in_B_x": 0.1,
312
+ "w_B_ECI_in_B_y": 0.2,
313
+ "w_B_ECI_in_B_z": 0.3,
314
+ }
315
+
316
+ state: State = State.from_dict(data)
317
+
318
+ assert state is not None
319
+ assert isinstance(state, State)
320
+ assert state.get_size() == 9
321
+
322
+ def test_from_dict_with_drag_coefficient(self):
323
+ data = {
324
+ "timestamp": datetime.now(timezone.utc).isoformat(),
325
+ "rx_eci": 7000.0,
326
+ "ry_eci": 0.0,
327
+ "rz_eci": 0.0,
328
+ "vx_eci": 0.0,
329
+ "vy_eci": 7.5,
330
+ "vz_eci": 0.0,
331
+ "drag_coefficient": 2.2,
332
+ }
333
+
334
+ state: State = State.from_dict(data)
335
+
336
+ assert state is not None
337
+ assert isinstance(state, State)
338
+ assert state.get_size() == 7
339
+
340
+ data = {
341
+ "timestamp": datetime.now(timezone.utc).isoformat(),
342
+ "rx_eci": 7000.0,
343
+ "ry_eci": 0.0,
344
+ "rz_eci": 0.0,
345
+ "vx_eci": 0.0,
346
+ "vy_eci": 7.5,
347
+ "vz_eci": 0.0,
348
+ "cd": 2.2,
349
+ }
350
+
351
+ state: State = State.from_dict(data)
352
+
353
+ assert state is not None
354
+ assert isinstance(state, State)
355
+ assert state.get_size() == 7
356
+
357
+ def test_from_dict_with_surface_area(self):
358
+ data = {
359
+ "timestamp": datetime.now(timezone.utc).isoformat(),
360
+ "rx_eci": 7000.0,
361
+ "ry_eci": 0.0,
362
+ "rz_eci": 0.0,
363
+ "vx_eci": 0.0,
364
+ "vy_eci": 7.5,
365
+ "vz_eci": 0.0,
366
+ "surface_area": 2.2,
367
+ }
368
+
369
+ state: State = State.from_dict(data)
370
+
371
+ assert state is not None
372
+ assert isinstance(state, State)
373
+ assert state.get_size() == 7
374
+
375
+ data = {
376
+ "timestamp": datetime.now(timezone.utc).isoformat(),
377
+ "rx_eci": 7000.0,
378
+ "ry_eci": 0.0,
379
+ "rz_eci": 0.0,
380
+ "vx_eci": 0.0,
381
+ "vy_eci": 7.5,
382
+ "vz_eci": 0.0,
383
+ "cross_sectional_area": 2.2,
384
+ }
385
+
386
+ state: State = State.from_dict(data)
387
+
388
+ assert state is not None
389
+ assert isinstance(state, State)
390
+ assert state.get_size() == 7
391
+
392
+ def test_from_dict_with_mass(self):
393
+ data = {
394
+ "timestamp": datetime.now(timezone.utc).isoformat(),
395
+ "rx_eci": 7000.0,
396
+ "ry_eci": 0.0,
397
+ "rz_eci": 0.0,
398
+ "vx_eci": 0.0,
399
+ "vy_eci": 7.5,
400
+ "vz_eci": 0.0,
401
+ "mass": 2.2,
402
+ }
403
+
404
+ state: State = State.from_dict(data)
405
+
406
+ assert state is not None
407
+ assert isinstance(state, State)
408
+ assert state.get_size() == 7
409
+
410
+ @pytest.mark.parametrize(
411
+ ("data", "expected_length", "expected_frame"),
412
+ [
413
+ (
414
+ {
415
+ "timestamp": datetime.now(timezone.utc).isoformat(),
416
+ "r_GCRF_x": 1.0,
417
+ "r_GCRF_y": 2.0,
418
+ "r_GCRF_z": 3.0,
419
+ "v_GCRF_x": 4.0,
420
+ "v_GCRF_y": 5.0,
421
+ "v_GCRF_z": 6.0,
422
+ },
423
+ 6,
424
+ Frame.GCRF(),
425
+ ),
426
+ (
427
+ {
428
+ "timestamp": datetime.now(timezone.utc).isoformat(),
429
+ "r_ITRF_x": 1.0,
430
+ "r_ITRF_y": 2.0,
431
+ "r_ITRF_z": 3.0,
432
+ "v_ITRF_x": 4.0,
433
+ "v_ITRF_y": 5.0,
434
+ "v_ITRF_z": 6.0,
435
+ },
436
+ 6,
437
+ Frame.ITRF(),
438
+ ),
439
+ ],
440
+ )
441
+ def test_from_dict_cannonical_success(
442
+ self, data: dict, expected_length: int, expected_frame: Frame
443
+ ):
444
+ state: State = State.from_dict(data)
445
+
446
+ assert state is not None
447
+ assert isinstance(state, State)
448
+
449
+ assert state.get_size() == expected_length
450
+ assert state.get_frame() == expected_frame
451
+
452
+ @pytest.mark.parametrize(
453
+ ("data", "expected_failure_message"),
454
+ [
455
+ (
456
+ {
457
+ "timestamp": datetime.now(timezone.utc).isoformat(),
458
+ "r_GCRF_x": 1.0,
459
+ "r_GCRF_y": 2.0,
460
+ "r_GCRF_z": 3.0,
461
+ "v_GCRF_x": 4.0,
462
+ "v_GCRF_y": 5.0,
463
+ },
464
+ "Invalid state data.",
465
+ ),
466
+ (
467
+ {
468
+ "timestamp": datetime.now(timezone.utc).isoformat(),
469
+ "r_TEST_x": 1.0,
470
+ "r_TEST_y": 2.0,
471
+ "r_TEST_z": 3.0,
472
+ "v_TEST_x": 4.0,
473
+ "v_TEST_y": 5.0,
474
+ "v_TEST_z": 6.0,
475
+ },
476
+ "No frame exists with name \\[TEST\\].",
477
+ ),
478
+ ],
479
+ )
480
+ def test_from_dict_cannonical_failure(
481
+ self, data: dict, expected_failure_message: str
482
+ ):
483
+ with pytest.raises(ValueError, match=expected_failure_message):
484
+ State.from_dict(data)
485
+
180
486
  def test_comparators(self, state: State):
181
487
  assert (state == state) is True
182
488
  assert (state != state) is False