open-space-toolkit-astrodynamics 5.1.5__py310-none-any.whl → 5.2.0__py310-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 (85) hide show
  1. {open_space_toolkit_astrodynamics-5.1.5.dist-info → open_space_toolkit_astrodynamics-5.2.0.dist-info}/METADATA +1 -1
  2. open_space_toolkit_astrodynamics-5.2.0.dist-info/RECORD +96 -0
  3. ostk/__init__.py +1 -0
  4. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-310-x86_64-linux-gnu.so +0 -0
  5. ostk/astrodynamics/converters.py +44 -6
  6. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.5 +0 -0
  7. ostk/astrodynamics/pytrajectory/pystate.py +1 -3
  8. ostk/astrodynamics/test/access/__init__.py +1 -0
  9. ostk/astrodynamics/test/access/test_generator.py +248 -0
  10. ostk/astrodynamics/test/conjunction/messages/ccsds/__init__.py +1 -0
  11. ostk/astrodynamics/test/conjunction/messages/ccsds/conftest.py +325 -0
  12. ostk/astrodynamics/test/conjunction/messages/ccsds/data/cdm.json +303 -0
  13. ostk/astrodynamics/test/conjunction/messages/ccsds/test_cdm.py +416 -0
  14. ostk/astrodynamics/test/dynamics/__init__.py +1 -0
  15. ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
  16. ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
  17. ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
  18. ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
  19. ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
  20. ostk/astrodynamics/test/dynamics/test_thruster.py +142 -0
  21. ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
  22. ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
  23. ostk/astrodynamics/test/event_condition/test_coe_condition.py +87 -0
  24. ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
  25. ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
  26. ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
  27. ostk/astrodynamics/test/flight/__init__.py +1 -0
  28. ostk/astrodynamics/test/flight/profile/__init__.py +1 -0
  29. ostk/astrodynamics/test/flight/profile/test_state.py +144 -0
  30. ostk/astrodynamics/test/flight/system/__init__.py +1 -0
  31. ostk/astrodynamics/test/flight/system/test_propulsion_system.py +46 -0
  32. ostk/astrodynamics/test/flight/system/test_satellite_system.py +91 -0
  33. ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
  34. ostk/astrodynamics/test/flight/test_profile.py +153 -0
  35. ostk/astrodynamics/test/flight/test_system.py +55 -0
  36. ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +91 -0
  37. ostk/astrodynamics/test/guidance_law/test_qlaw.py +139 -0
  38. ostk/astrodynamics/test/solvers/__init__.py +1 -0
  39. ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +181 -0
  40. ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +153 -0
  41. ostk/astrodynamics/test/test_access.py +2 -6
  42. ostk/astrodynamics/test/test_converters.py +213 -6
  43. ostk/astrodynamics/test/test_viewer.py +1 -2
  44. ostk/astrodynamics/test/trajectory/__init__.py +1 -0
  45. ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
  46. ostk/astrodynamics/test/trajectory/orbit/messages/__init__.py +1 -0
  47. ostk/astrodynamics/test/trajectory/orbit/messages/spacex/__init__.py +1 -0
  48. ostk/astrodynamics/test/trajectory/orbit/messages/spacex/conftest.py +18 -0
  49. ostk/astrodynamics/test/trajectory/orbit/messages/spacex/data/opm_1.yaml +44 -0
  50. ostk/astrodynamics/test/trajectory/orbit/messages/spacex/test_opm.py +108 -0
  51. ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
  52. ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
  53. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
  54. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
  55. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
  56. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +167 -0
  57. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
  58. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +331 -0
  59. ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
  60. ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +195 -0
  61. ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
  62. ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
  63. ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
  64. ostk/astrodynamics/test/trajectory/orbit/test_pass.py +55 -0
  65. ostk/astrodynamics/test/trajectory/state/coordinates_subset/test_angular_velocity.py +30 -0
  66. ostk/astrodynamics/test/trajectory/state/coordinates_subset/test_attitude_quaternion.py +18 -0
  67. ostk/astrodynamics/test/trajectory/state/coordinates_subset/test_cartesian_position.py +107 -0
  68. ostk/astrodynamics/test/trajectory/state/coordinates_subset/test_cartesian_velocity.py +115 -0
  69. ostk/astrodynamics/test/trajectory/state/test_coordinates_broker.py +84 -0
  70. ostk/astrodynamics/test/trajectory/state/test_coordinates_subset.py +43 -0
  71. ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +314 -0
  72. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
  73. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +64 -0
  74. ostk/astrodynamics/test/trajectory/test_model.py +1 -0
  75. ostk/astrodynamics/test/trajectory/test_orbit.py +92 -0
  76. ostk/astrodynamics/test/trajectory/test_propagator.py +402 -0
  77. ostk/astrodynamics/test/trajectory/test_segment.py +288 -0
  78. ostk/astrodynamics/test/trajectory/test_sequence.py +459 -0
  79. ostk/astrodynamics/test/trajectory/test_state.py +223 -0
  80. ostk/astrodynamics/test/trajectory/test_state_builder.py +171 -0
  81. ostk/astrodynamics/viewer.py +1 -3
  82. open_space_toolkit_astrodynamics-5.1.5.dist-info/RECORD +0 -25
  83. {open_space_toolkit_astrodynamics-5.1.5.dist-info → open_space_toolkit_astrodynamics-5.2.0.dist-info}/WHEEL +0 -0
  84. {open_space_toolkit_astrodynamics-5.1.5.dist-info → open_space_toolkit_astrodynamics-5.2.0.dist-info}/top_level.txt +0 -0
  85. {open_space_toolkit_astrodynamics-5.1.5.dist-info → open_space_toolkit_astrodynamics-5.2.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,402 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ import numpy as np
6
+
7
+ from ostk.mathematics.geometry.d3.objects import Cuboid
8
+ from ostk.mathematics.geometry.d3.objects import Composite
9
+ from ostk.mathematics.geometry.d3.objects import Point
10
+
11
+ from ostk.physics import Environment
12
+ from ostk.physics.units import Mass
13
+ from ostk.physics.time import Instant
14
+ from ostk.physics.time import DateTime
15
+ from ostk.physics.time import Scale
16
+ from ostk.physics.time import Duration
17
+ from ostk.physics.coordinate import Position
18
+ from ostk.physics.coordinate import Velocity
19
+ from ostk.physics.coordinate import Frame
20
+ from ostk.physics.environment.objects.celestial_bodies import Earth, Sun
21
+ from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
22
+ from ostk.physics.environment.magnetic import Earth as EarthMagneticModel
23
+ from ostk.physics.environment.atmospheric import Earth as EarthAtmosphericModel
24
+
25
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
26
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameDirection
27
+
28
+ from ostk.astrodynamics.trajectory.state import CoordinatesSubset
29
+ from ostk.astrodynamics.trajectory.state.coordinates_subset import CartesianPosition
30
+ from ostk.astrodynamics.trajectory.state.coordinates_subset import CartesianVelocity
31
+ from ostk.astrodynamics.trajectory.state import CoordinatesBroker
32
+ from ostk.astrodynamics.trajectory.state import NumericalSolver
33
+
34
+ from ostk.astrodynamics.flight.system import PropulsionSystem
35
+ from ostk.astrodynamics.flight.system import SatelliteSystem
36
+ from ostk.astrodynamics import Dynamics
37
+ from ostk.astrodynamics.dynamics import Thruster
38
+ from ostk.astrodynamics.dynamics import CentralBodyGravity
39
+ from ostk.astrodynamics.dynamics import PositionDerivative
40
+ from ostk.astrodynamics.dynamics import AtmosphericDrag
41
+ from ostk.astrodynamics.guidance_law import ConstantThrust
42
+ from ostk.astrodynamics.trajectory import State
43
+ from ostk.astrodynamics.trajectory.state import CoordinatesSubset, CoordinatesBroker
44
+ from ostk.astrodynamics.trajectory.state.coordinates_subset import (
45
+ CartesianPosition,
46
+ CartesianVelocity,
47
+ )
48
+ from ostk.astrodynamics.trajectory import Propagator
49
+
50
+ from ostk.astrodynamics.event_condition import InstantCondition
51
+
52
+
53
+ @pytest.fixture
54
+ def propulsion_system() -> PropulsionSystem:
55
+ return PropulsionSystem(
56
+ 1.0,
57
+ 150.0,
58
+ )
59
+
60
+
61
+ @pytest.fixture
62
+ def satellite_system(propulsion_system: PropulsionSystem) -> SatelliteSystem:
63
+ mass = Mass(90.0, Mass.Unit.Kilogram)
64
+ satellite_geometry = Composite(
65
+ Cuboid(
66
+ Point(0.0, 0.0, 0.0),
67
+ [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
68
+ [1.0, 0.0, 0.0],
69
+ )
70
+ )
71
+ inertia_tensor = np.identity(3)
72
+ surface_area = 0.8
73
+ drag_coefficient = 0.0
74
+
75
+ return SatelliteSystem(
76
+ mass,
77
+ satellite_geometry,
78
+ inertia_tensor,
79
+ surface_area,
80
+ drag_coefficient,
81
+ propulsion_system,
82
+ )
83
+
84
+
85
+ @pytest.fixture
86
+ def earth() -> Earth:
87
+ return Earth.from_models(
88
+ EarthGravitationalModel(EarthGravitationalModel.Type.EGM96),
89
+ EarthMagneticModel(EarthMagneticModel.Type.Undefined),
90
+ EarthAtmosphericModel(EarthAtmosphericModel.Type.Exponential),
91
+ )
92
+
93
+
94
+ @pytest.fixture
95
+ def environment(earth) -> Environment:
96
+ sun = Sun.default()
97
+
98
+ return Environment(Instant.J2000(), [earth, sun])
99
+
100
+
101
+ @pytest.fixture
102
+ def coordinates_broker_7d():
103
+ return CoordinatesBroker(
104
+ [
105
+ CartesianPosition.default(),
106
+ CartesianVelocity.default(),
107
+ CoordinatesSubset.mass(),
108
+ ]
109
+ )
110
+
111
+
112
+ @pytest.fixture
113
+ def coordinates_broker_9d():
114
+ return CoordinatesBroker(
115
+ [
116
+ CartesianPosition.default(),
117
+ CartesianVelocity.default(),
118
+ CoordinatesSubset.mass(),
119
+ CoordinatesSubset.surface_area(),
120
+ CoordinatesSubset.drag_coefficient(),
121
+ ]
122
+ )
123
+
124
+
125
+ @pytest.fixture
126
+ def state(
127
+ satellite_system: SatelliteSystem, coordinates_broker_7d: CoordinatesBroker
128
+ ) -> State:
129
+ instant: Instant = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
130
+
131
+ propellant_mass: float = 10.0
132
+
133
+ coordinates: list = [
134
+ 7500000.0,
135
+ 0.0,
136
+ 0.0,
137
+ 0.0,
138
+ 5335.865450622126,
139
+ 5335.865450622126,
140
+ satellite_system.get_mass().in_kilograms() + propellant_mass,
141
+ ]
142
+
143
+ return State(instant, coordinates, Frame.GCRF(), coordinates_broker_7d)
144
+
145
+
146
+ @pytest.fixture
147
+ def state_low_altitude(
148
+ satellite_system: SatelliteSystem, coordinates_broker_9d: CoordinatesBroker
149
+ ) -> State:
150
+ instant: Instant = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
151
+
152
+ propellant_mass: float = 10.0
153
+ area: float = satellite_system.get_cross_sectional_surface_area()
154
+ cd: float = satellite_system.get_drag_coefficient()
155
+
156
+ coordinates: list = [
157
+ 7000000.0,
158
+ 0.0,
159
+ 0.0,
160
+ 0.0,
161
+ 5335.865450622126,
162
+ 5335.865450622126,
163
+ satellite_system.get_mass().in_kilograms() + propellant_mass,
164
+ area,
165
+ cd,
166
+ ]
167
+
168
+ return State(instant, coordinates, Frame.GCRF(), coordinates_broker_9d)
169
+
170
+
171
+ @pytest.fixture
172
+ def central_body_gravity() -> CentralBodyGravity:
173
+ return CentralBodyGravity(Earth.WGS84(20, 0))
174
+
175
+
176
+ @pytest.fixture
177
+ def atmospheric_drag(environment, satellite_system) -> AtmosphericDrag:
178
+ return AtmosphericDrag(environment.access_celestial_object_with_name("Earth"))
179
+
180
+
181
+ @pytest.fixture
182
+ def position_derivative() -> PositionDerivative:
183
+ return PositionDerivative()
184
+
185
+
186
+ @pytest.fixture
187
+ def local_orbital_frame_direction() -> LocalOrbitalFrameDirection:
188
+ return LocalOrbitalFrameDirection(
189
+ [1.0, 0.0, 0.0],
190
+ LocalOrbitalFrameFactory.VNC(Frame.GCRF()),
191
+ )
192
+
193
+
194
+ @pytest.fixture
195
+ def thrust_dynamics(
196
+ satellite_system: SatelliteSystem,
197
+ local_orbital_frame_direction: LocalOrbitalFrameDirection,
198
+ ) -> Thruster:
199
+ return Thruster(
200
+ satellite_system=satellite_system,
201
+ guidance_law=ConstantThrust(local_orbital_frame_direction),
202
+ )
203
+
204
+
205
+ @pytest.fixture
206
+ def dynamics(
207
+ position_derivative: PositionDerivative,
208
+ central_body_gravity: CentralBodyGravity,
209
+ ) -> list:
210
+ return [position_derivative, central_body_gravity]
211
+
212
+
213
+ @pytest.fixture
214
+ def numerical_solver() -> NumericalSolver:
215
+ return NumericalSolver(
216
+ NumericalSolver.LogType.NoLog,
217
+ NumericalSolver.StepperType.RungeKuttaFehlberg78,
218
+ 5.0,
219
+ 1.0e-15,
220
+ 1.0e-15,
221
+ )
222
+
223
+
224
+ @pytest.fixture
225
+ def conditional_numerical_solver() -> NumericalSolver:
226
+ return NumericalSolver(
227
+ NumericalSolver.LogType.NoLog,
228
+ NumericalSolver.StepperType.RungeKuttaDopri5,
229
+ 5.0,
230
+ 1.0e-15,
231
+ 1.0e-15,
232
+ )
233
+
234
+
235
+ @pytest.fixture
236
+ def event_condition(state: State) -> InstantCondition:
237
+ return InstantCondition(
238
+ InstantCondition.Criterion.StrictlyPositive,
239
+ state.get_instant() + Duration.seconds(42.0),
240
+ )
241
+
242
+
243
+ @pytest.fixture
244
+ def propagator(numerical_solver: NumericalSolver, dynamics: list[Dynamics]) -> Propagator:
245
+ return Propagator(numerical_solver, dynamics)
246
+
247
+
248
+ class TestPropagator:
249
+ def test_constructors(self, propagator: Propagator):
250
+ assert propagator is not None
251
+ assert isinstance(propagator, Propagator)
252
+ assert propagator.is_defined()
253
+
254
+ def test_access_numerical_solver(
255
+ self, propagator: Propagator, numerical_solver: NumericalSolver
256
+ ):
257
+ assert propagator.access_numerical_solver() == numerical_solver
258
+
259
+ def test_get_dynamics(self, propagator: Propagator, dynamics: list):
260
+ assert propagator.get_dynamics() == dynamics
261
+
262
+ def test_set_dynamics(self, propagator: Propagator, dynamics: list):
263
+ assert len(propagator.get_dynamics()) == 2
264
+
265
+ propagator.set_dynamics(dynamics + dynamics)
266
+
267
+ assert len(propagator.get_dynamics()) == 4
268
+
269
+ def test_add_dynamics(
270
+ self, propagator: Propagator, central_body_gravity: CentralBodyGravity
271
+ ):
272
+ assert len(propagator.get_dynamics()) == 2
273
+
274
+ propagator.add_dynamics(central_body_gravity)
275
+ propagator.add_dynamics(central_body_gravity)
276
+
277
+ assert len(propagator.get_dynamics()) == 4
278
+
279
+ def test_clear_dynamics(self, propagator: Propagator):
280
+ assert len(propagator.get_dynamics()) >= 1
281
+
282
+ propagator.clear_dynamics()
283
+
284
+ assert len(propagator.get_dynamics()) == 0
285
+
286
+ def test_calculate_state_at(self, propagator: Propagator, state: State):
287
+ instant: Instant = Instant.date_time(DateTime(2018, 1, 1, 0, 10, 0), Scale.UTC)
288
+
289
+ propagator_state = propagator.calculate_state_at(state, instant)
290
+
291
+ propagator_state_position_ref = np.array(
292
+ [6265892.25765909, 3024770.94961259, 3024359.72137468]
293
+ )
294
+ propagator_state_velocity_ref = np.array(
295
+ [-3974.49168221, 4468.16996776, 4466.19232746]
296
+ )
297
+
298
+ propagator_state_position = propagator_state.get_position().get_coordinates()
299
+ propagator_state_velocity = propagator_state.get_velocity().get_coordinates()
300
+
301
+ assert all(
302
+ [
303
+ round(propagator_state_position[i], 8)
304
+ == round(propagator_state_position_ref[i], 8)
305
+ for i in range(0, len(propagator_state_position_ref))
306
+ ]
307
+ )
308
+ assert all(
309
+ [
310
+ round(propagator_state_velocity[i], 8)
311
+ == round(propagator_state_velocity_ref[i], 8)
312
+ for i in range(0, len(propagator_state_velocity_ref))
313
+ ]
314
+ )
315
+ assert propagator_state.get_instant() == instant
316
+
317
+ def test_calculate_state_to_condition(
318
+ self,
319
+ conditional_numerical_solver: NumericalSolver,
320
+ dynamics: list[Dynamics],
321
+ state: State,
322
+ event_condition: InstantCondition,
323
+ ):
324
+ propagator: Propagator = Propagator(conditional_numerical_solver, dynamics)
325
+
326
+ instant: Instant = Instant.date_time(DateTime(2018, 1, 1, 0, 10, 0), Scale.UTC)
327
+
328
+ solution = propagator.calculate_state_to_condition(
329
+ state=state,
330
+ instant=instant,
331
+ event_condition=event_condition,
332
+ )
333
+
334
+ assert solution.condition_is_satisfied
335
+ assert pytest.approx(42.0, abs=1e-3) == float(
336
+ (solution.state.get_instant() - state.get_instant()).in_seconds()
337
+ )
338
+
339
+ def test_calculate_states_at(self, propagator: Propagator, state: State):
340
+ instant_array = [
341
+ Instant.date_time(DateTime(2018, 1, 1, 0, 10, 0), Scale.UTC),
342
+ Instant.date_time(DateTime(2018, 1, 1, 0, 20, 0), Scale.UTC),
343
+ ]
344
+
345
+ _ = propagator.calculate_states_at(state, instant_array)
346
+
347
+ with pytest.raises(RuntimeError):
348
+ instant_array.reverse()
349
+ propagator.calculate_states_at(state, instant_array)
350
+
351
+ def test_calculate_states_at_with_drag(
352
+ self,
353
+ numerical_solver: NumericalSolver,
354
+ dynamics: list[Dynamics],
355
+ atmospheric_drag: AtmosphericDrag,
356
+ state_low_altitude: State,
357
+ ):
358
+ propagator: Propagator = Propagator(
359
+ numerical_solver,
360
+ dynamics + [atmospheric_drag],
361
+ )
362
+
363
+ instant_array = [
364
+ Instant.date_time(DateTime(2018, 1, 1, 0, 10, 0), Scale.UTC),
365
+ Instant.date_time(DateTime(2018, 1, 1, 0, 20, 0), Scale.UTC),
366
+ Instant.date_time(DateTime(2018, 1, 1, 0, 30, 0), Scale.UTC),
367
+ Instant.date_time(DateTime(2018, 1, 1, 0, 40, 0), Scale.UTC),
368
+ ]
369
+
370
+ _ = propagator.calculate_states_at(state_low_altitude, instant_array)
371
+
372
+ def test_calculate_states_at_with_thrust(
373
+ self,
374
+ numerical_solver: NumericalSolver,
375
+ dynamics: list[Dynamics],
376
+ thrust_dynamics: Thruster,
377
+ state: State,
378
+ ):
379
+ propagator: Propagator = Propagator(
380
+ numerical_solver,
381
+ dynamics + [thrust_dynamics],
382
+ )
383
+
384
+ instant_array = [
385
+ Instant.date_time(DateTime(2018, 1, 1, 0, 10, 0), Scale.UTC),
386
+ Instant.date_time(DateTime(2018, 1, 1, 0, 20, 0), Scale.UTC),
387
+ Instant.date_time(DateTime(2018, 1, 1, 0, 30, 0), Scale.UTC),
388
+ Instant.date_time(DateTime(2018, 1, 1, 0, 40, 0), Scale.UTC),
389
+ ]
390
+
391
+ _ = propagator.calculate_states_at(state, instant_array)
392
+
393
+ def test_from_environment(
394
+ self,
395
+ numerical_solver: NumericalSolver,
396
+ environment: Environment,
397
+ ):
398
+ assert Propagator.from_environment(numerical_solver, environment) is not None
399
+
400
+ def test_default(self, environment: Environment):
401
+ assert Propagator.default()
402
+ assert Propagator.default(environment) is not None
@@ -0,0 +1,288 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+ import numpy as np
5
+
6
+ from ostk.physics import Environment
7
+ from ostk.physics.time import Instant
8
+ from ostk.physics.time import DateTime
9
+ from ostk.physics.time import Scale
10
+ from ostk.physics.time import Duration
11
+ from ostk.physics.coordinate import Frame
12
+ from ostk.physics.environment.objects.celestial_bodies import Earth
13
+
14
+ from ostk.astrodynamics.trajectory.state import NumericalSolver
15
+ from ostk.astrodynamics.flight.system import SatelliteSystem
16
+ from ostk.astrodynamics.dynamics import CentralBodyGravity
17
+ from ostk.astrodynamics.dynamics import PositionDerivative
18
+ from ostk.astrodynamics.dynamics import Thruster
19
+ from ostk.astrodynamics.guidance_law import ConstantThrust
20
+ from ostk.astrodynamics.trajectory import State
21
+ from ostk.astrodynamics.trajectory import Segment
22
+ from ostk.astrodynamics.event_condition import InstantCondition
23
+ from ostk.astrodynamics.trajectory.state import CoordinatesSubset
24
+ from ostk.astrodynamics.trajectory.state import CoordinatesBroker
25
+ from ostk.astrodynamics.trajectory.state.coordinates_subset import CartesianPosition
26
+ from ostk.astrodynamics.trajectory.state.coordinates_subset import CartesianVelocity
27
+
28
+
29
+ @pytest.fixture
30
+ def environment() -> Environment:
31
+ return Environment.default()
32
+
33
+
34
+ @pytest.fixture
35
+ def state() -> State:
36
+ instant: Instant = Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC)
37
+ coordinates: list[float] = [
38
+ 7500000.0,
39
+ 0.0,
40
+ 0.0,
41
+ 0.0,
42
+ 5335.865450622126,
43
+ 5335.865450622126,
44
+ 300.0,
45
+ ]
46
+ frame: Frame = Frame.GCRF()
47
+ coordinates_broker: CoordinatesBroker = CoordinatesBroker(
48
+ [
49
+ CartesianPosition.default(),
50
+ CartesianVelocity.default(),
51
+ CoordinatesSubset.mass(),
52
+ ]
53
+ )
54
+ return State(instant, coordinates, frame, coordinates_broker)
55
+
56
+
57
+ @pytest.fixture
58
+ def central_body_gravity() -> CentralBodyGravity:
59
+ return CentralBodyGravity(Earth.spherical())
60
+
61
+
62
+ @pytest.fixture
63
+ def position_derivative() -> PositionDerivative:
64
+ return PositionDerivative()
65
+
66
+
67
+ @pytest.fixture
68
+ def dynamics(
69
+ position_derivative: PositionDerivative, central_body_gravity: CentralBodyGravity
70
+ ) -> list:
71
+ return [
72
+ position_derivative,
73
+ central_body_gravity,
74
+ ]
75
+
76
+
77
+ @pytest.fixture
78
+ def numerical_solver() -> NumericalSolver:
79
+ return NumericalSolver.default_conditional()
80
+
81
+
82
+ @pytest.fixture
83
+ def end_instant(state: State) -> Instant:
84
+ return state.get_instant() + Duration.minutes(15.0)
85
+
86
+
87
+ @pytest.fixture
88
+ def instant_condition(end_instant: Instant) -> InstantCondition:
89
+ return InstantCondition(InstantCondition.Criterion.AnyCrossing, end_instant)
90
+
91
+
92
+ @pytest.fixture
93
+ def name() -> str:
94
+ return "A Segment"
95
+
96
+
97
+ @pytest.fixture
98
+ def coast_duration_segment(
99
+ name: str,
100
+ instant_condition: InstantCondition,
101
+ dynamics: list,
102
+ numerical_solver: NumericalSolver,
103
+ ) -> Segment:
104
+ return Segment.coast(name, instant_condition, dynamics, numerical_solver)
105
+
106
+
107
+ @pytest.fixture
108
+ def maneuver_segment(
109
+ name: str,
110
+ instant_condition: InstantCondition,
111
+ dynamics: list,
112
+ numerical_solver: NumericalSolver,
113
+ thruster_dynamics: Thruster,
114
+ ) -> Segment:
115
+ return Segment.maneuver(
116
+ name=name,
117
+ event_condition=instant_condition,
118
+ thruster_dynamics=thruster_dynamics,
119
+ dynamics=dynamics,
120
+ numerical_solver=numerical_solver,
121
+ )
122
+
123
+
124
+ @pytest.fixture
125
+ def thruster_dynamics() -> Thruster:
126
+ return Thruster(
127
+ satellite_system=SatelliteSystem.default(),
128
+ guidance_law=ConstantThrust.intrack(),
129
+ )
130
+
131
+
132
+ class TestSegment:
133
+ def test_get_name(self, coast_duration_segment: Segment, name: str):
134
+ assert coast_duration_segment.get_name() == name
135
+
136
+ def test_get_event_condition(
137
+ self,
138
+ instant_condition: InstantCondition,
139
+ coast_duration_segment: Segment,
140
+ ):
141
+ assert coast_duration_segment.get_event_condition() == instant_condition
142
+
143
+ def test_get_dynamics(
144
+ self,
145
+ dynamics: list,
146
+ coast_duration_segment: Segment,
147
+ ):
148
+ assert len(coast_duration_segment.get_dynamics()) == len(dynamics)
149
+
150
+ def test_get_numerical_solver(
151
+ self,
152
+ numerical_solver: NumericalSolver,
153
+ coast_duration_segment: Segment,
154
+ ):
155
+ assert coast_duration_segment.get_numerical_solver() == numerical_solver
156
+
157
+ def test_get_type(
158
+ self,
159
+ coast_duration_segment: Segment,
160
+ ):
161
+ assert coast_duration_segment.get_type() == Segment.Type.Coast
162
+
163
+ def test_coast(
164
+ self,
165
+ name: str,
166
+ instant_condition: InstantCondition,
167
+ dynamics: list,
168
+ numerical_solver: NumericalSolver,
169
+ ):
170
+ assert (
171
+ Segment.coast(name, instant_condition, dynamics, numerical_solver) is not None
172
+ )
173
+
174
+ def test_maneuver(
175
+ self,
176
+ name: str,
177
+ instant_condition: InstantCondition,
178
+ thruster_dynamics: ConstantThrust,
179
+ dynamics: list,
180
+ numerical_solver: NumericalSolver,
181
+ ):
182
+ assert (
183
+ Segment.maneuver(
184
+ name=name,
185
+ event_condition=instant_condition,
186
+ thruster_dynamics=thruster_dynamics,
187
+ dynamics=dynamics,
188
+ numerical_solver=numerical_solver,
189
+ )
190
+ is not None
191
+ )
192
+
193
+ def test_create_solution(
194
+ self,
195
+ dynamics: list,
196
+ state: State,
197
+ ):
198
+ segment_solution: Segment.Solution = Segment.Solution(
199
+ name="A Segment",
200
+ dynamics=dynamics,
201
+ states=[
202
+ state,
203
+ ],
204
+ condition_is_satisfied=True,
205
+ segment_type=Segment.Type.Coast,
206
+ )
207
+
208
+ assert segment_solution is not None
209
+
210
+ def test_solve(
211
+ self,
212
+ state: State,
213
+ end_instant: Instant,
214
+ maneuver_segment: Segment,
215
+ ):
216
+ solution = maneuver_segment.solve(state)
217
+
218
+ assert (
219
+ pytest.approx(
220
+ float((solution.states[-1].get_instant() - end_instant).in_seconds()),
221
+ abs=1e-7,
222
+ )
223
+ == 0.0
224
+ )
225
+
226
+ assert solution.name is not None
227
+ assert solution.dynamics is not None
228
+ assert len(solution.states) > 0
229
+ assert solution.condition_is_satisfied is True
230
+ assert solution.segment_type is not None
231
+
232
+ assert solution.access_start_instant() is not None
233
+ assert solution.access_end_instant() is not None
234
+
235
+ assert solution.get_initial_mass() is not None
236
+ assert solution.get_final_mass() is not None
237
+ assert solution.get_propagation_duration() is not None
238
+
239
+ assert solution.compute_delta_mass() is not None
240
+ assert solution.compute_delta_v(1500.0) is not None
241
+
242
+ state_frame: Frame = state.get_frame()
243
+
244
+ first_dynamics_contribution = solution.get_dynamics_contribution(
245
+ solution.dynamics[0], state_frame
246
+ )
247
+
248
+ second_dynamics_contribution = solution.get_dynamics_contribution(
249
+ solution.dynamics[1], state_frame
250
+ )
251
+
252
+ third_dynamics_contribution = solution.get_dynamics_contribution(
253
+ solution.dynamics[2], state_frame
254
+ )
255
+
256
+ assert first_dynamics_contribution is not None
257
+ assert second_dynamics_contribution is not None
258
+ assert third_dynamics_contribution is not None
259
+ assert first_dynamics_contribution.shape == (len(solution.states), 3)
260
+ assert second_dynamics_contribution.shape == (len(solution.states), 3)
261
+ assert third_dynamics_contribution.shape == (len(solution.states), 4)
262
+
263
+ all_contributions = solution.get_all_dynamics_contributions(state_frame)
264
+ assert len(all_contributions) == len(solution.dynamics)
265
+
266
+ assert all_contributions is not None
267
+ assert isinstance(all_contributions, dict)
268
+ assert np.array_equal(
269
+ all_contributions[solution.dynamics[0]], first_dynamics_contribution
270
+ )
271
+ assert np.array_equal(
272
+ all_contributions[solution.dynamics[1]], second_dynamics_contribution
273
+ )
274
+ assert np.array_equal(
275
+ all_contributions[solution.dynamics[2]], third_dynamics_contribution
276
+ )
277
+
278
+ acceleration_contribution = solution.get_dynamics_acceleration_contribution(
279
+ solution.dynamics[1], state_frame
280
+ )
281
+ assert acceleration_contribution is not None
282
+ assert acceleration_contribution.shape == (len(solution.states), 3)
283
+ assert isinstance(acceleration_contribution, np.ndarray)
284
+
285
+ with pytest.raises(Exception):
286
+ solution.get_dynamics_acceleration_contribution(
287
+ solution.dynamics[0], state_frame
288
+ )