open-space-toolkit-astrodynamics 16.8.0__py312-none-manylinux2014_aarch64.whl → 16.10.0__py312-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 (22) hide show
  1. {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/METADATA +1 -1
  2. {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/RECORD +22 -17
  3. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-312-aarch64-linux-gnu.so +0 -0
  4. ostk/astrodynamics/__init__.pyi +3 -3
  5. ostk/astrodynamics/conjunction/__init__.pyi +119 -1
  6. ostk/astrodynamics/conjunction/close_approach.pyi +85 -0
  7. ostk/astrodynamics/dynamics.pyi +1 -1
  8. ostk/astrodynamics/flight/__init__.pyi +29 -1
  9. ostk/astrodynamics/guidance_law.pyi +82 -7
  10. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16 +0 -0
  11. ostk/astrodynamics/test/conjunction/close_approach/__init__.py +0 -0
  12. ostk/astrodynamics/test/conjunction/close_approach/test_generator.py +228 -0
  13. ostk/astrodynamics/test/conjunction/test_close_approach.py +244 -0
  14. ostk/astrodynamics/test/dynamics/test_thruster.py +21 -6
  15. ostk/astrodynamics/test/flight/test_maneuver.py +37 -3
  16. ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +87 -1
  17. ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py +164 -0
  18. ostk/astrodynamics/test/trajectory/test_segment.py +107 -2
  19. ostk/astrodynamics/trajectory/__init__.pyi +24 -0
  20. {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/WHEEL +0 -0
  21. {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/top_level.txt +0 -0
  22. {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,228 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ from ostk.physics.unit import Length
6
+ from ostk.physics.unit import Angle
7
+ from ostk.physics.time import DateTime
8
+ from ostk.physics.time import Scale
9
+ from ostk.physics.time import Duration
10
+ from ostk.physics.time import Instant
11
+ from ostk.physics.time import Interval
12
+ from ostk.physics import Environment
13
+ from ostk.physics.environment.object import Celestial
14
+
15
+ from ostk.astrodynamics import Trajectory
16
+ from ostk.astrodynamics.trajectory import Orbit
17
+ from ostk.astrodynamics.trajectory.orbit.model import Kepler
18
+ from ostk.astrodynamics.trajectory.orbit.model.kepler import COE
19
+ from ostk.astrodynamics.conjunction import CloseApproach
20
+ from ostk.astrodynamics.conjunction.close_approach import Generator
21
+
22
+
23
+ @pytest.fixture
24
+ def environment() -> Environment:
25
+ return Environment.default()
26
+
27
+
28
+ @pytest.fixture
29
+ def earth(environment: Environment) -> Celestial:
30
+ return environment.access_celestial_object_with_name("Earth")
31
+
32
+
33
+ @pytest.fixture
34
+ def epoch() -> Instant:
35
+ return Instant.date_time(DateTime(2024, 1, 1, 0, 0, 0), Scale.UTC)
36
+
37
+
38
+ @pytest.fixture
39
+ def step() -> Duration:
40
+ return Duration.seconds(60.0)
41
+
42
+
43
+ @pytest.fixture
44
+ def tolerance() -> Duration:
45
+ return Duration.milliseconds(1.0)
46
+
47
+
48
+ @pytest.fixture
49
+ def reference_trajectory(earth: Celestial, epoch: Instant) -> Trajectory:
50
+ return Orbit(
51
+ model=Kepler(
52
+ coe=COE(
53
+ semi_major_axis=Length.kilometers(7000.0),
54
+ eccentricity=0.0,
55
+ inclination=Angle.degrees(0.0),
56
+ raan=Angle.degrees(0.0),
57
+ aop=Angle.degrees(0.0),
58
+ true_anomaly=Angle.degrees(0.0),
59
+ ),
60
+ epoch=epoch,
61
+ celestial_object=earth,
62
+ perturbation_type=Kepler.PerturbationType.No,
63
+ ),
64
+ celestial_object=earth,
65
+ )
66
+
67
+
68
+ @pytest.fixture
69
+ def other_trajectory(earth: Celestial, epoch: Instant) -> Trajectory:
70
+ return Orbit(
71
+ model=Kepler(
72
+ coe=COE(
73
+ semi_major_axis=Length.kilometers(7000.0),
74
+ eccentricity=0.0,
75
+ inclination=Angle.degrees(90.0),
76
+ raan=Angle.degrees(0.0),
77
+ aop=Angle.degrees(0.0),
78
+ true_anomaly=Angle.degrees(1.0 - 3), # Avoid co-location
79
+ ),
80
+ epoch=epoch,
81
+ celestial_object=earth,
82
+ perturbation_type=Kepler.PerturbationType.No,
83
+ ),
84
+ celestial_object=earth,
85
+ )
86
+
87
+
88
+ @pytest.fixture
89
+ def generator(
90
+ reference_trajectory: Trajectory,
91
+ step: Duration,
92
+ tolerance: Duration,
93
+ ) -> Generator:
94
+ return Generator(
95
+ reference_trajectory=reference_trajectory,
96
+ step=step,
97
+ tolerance=tolerance,
98
+ )
99
+
100
+
101
+ @pytest.fixture
102
+ def search_interval(epoch: Instant) -> Interval:
103
+ return Interval.closed(
104
+ epoch,
105
+ epoch + Duration.hours(3.0),
106
+ )
107
+
108
+
109
+ class TestGenerator:
110
+ def test_constructor_success(
111
+ self,
112
+ reference_trajectory: Trajectory,
113
+ step: Duration,
114
+ tolerance: Duration,
115
+ ):
116
+ generator = Generator(
117
+ reference_trajectory=reference_trajectory,
118
+ step=step,
119
+ tolerance=tolerance,
120
+ )
121
+
122
+ assert generator is not None
123
+ assert isinstance(generator, Generator)
124
+
125
+ def test_constructor_with_defaults_success(
126
+ self,
127
+ reference_trajectory: Trajectory,
128
+ ):
129
+ generator = Generator(reference_trajectory=reference_trajectory)
130
+
131
+ assert generator is not None
132
+ assert isinstance(generator, Generator)
133
+ assert generator.is_defined() is True
134
+
135
+ def test_is_defined_success(self, generator: Generator):
136
+ assert generator.is_defined() is True
137
+
138
+ def test_undefined_success(self):
139
+ generator = Generator.undefined()
140
+
141
+ assert generator is not None
142
+ assert isinstance(generator, Generator)
143
+ assert generator.is_defined() is False
144
+
145
+ def test_get_reference_trajectory_success(
146
+ self,
147
+ generator: Generator,
148
+ ):
149
+ trajectory = generator.get_reference_trajectory()
150
+
151
+ assert trajectory is not None
152
+ assert isinstance(trajectory, Trajectory)
153
+
154
+ def test_get_step_success(
155
+ self,
156
+ generator: Generator,
157
+ step: Duration,
158
+ ):
159
+ result_step = generator.get_step()
160
+
161
+ assert result_step is not None
162
+ assert isinstance(result_step, Duration)
163
+ assert result_step == step
164
+
165
+ def test_get_tolerance_success(
166
+ self,
167
+ generator: Generator,
168
+ tolerance: Duration,
169
+ ):
170
+ result_tolerance = generator.get_tolerance()
171
+
172
+ assert result_tolerance is not None
173
+ assert isinstance(result_tolerance, Duration)
174
+ assert result_tolerance == tolerance
175
+
176
+ def test_set_step_success(self, generator: Generator):
177
+ new_step = Duration.seconds(30.0)
178
+
179
+ generator.set_step(step=new_step)
180
+
181
+ assert generator.get_step() == new_step
182
+
183
+ def test_set_tolerance_success(self, generator: Generator):
184
+ new_tolerance = Duration.microseconds(100.0)
185
+
186
+ generator.set_tolerance(tolerance=new_tolerance)
187
+
188
+ assert generator.get_tolerance() == new_tolerance
189
+
190
+ def test_compute_close_approaches_success(
191
+ self,
192
+ generator: Generator,
193
+ other_trajectory: Trajectory,
194
+ search_interval: Interval,
195
+ ):
196
+ close_approaches = generator.compute_close_approaches(
197
+ trajectory=other_trajectory,
198
+ search_interval=search_interval,
199
+ )
200
+
201
+ assert close_approaches is not None
202
+ assert isinstance(close_approaches, list)
203
+ assert len(close_approaches) > 1
204
+
205
+ for close_approach in close_approaches:
206
+ assert isinstance(close_approach, CloseApproach)
207
+ assert close_approach.is_defined() is True
208
+ assert search_interval.contains_instant(close_approach.get_instant())
209
+
210
+ def test_compute_close_approaches_empty_interval_success(
211
+ self,
212
+ generator: Generator,
213
+ other_trajectory: Trajectory,
214
+ epoch: Instant,
215
+ ):
216
+ search_interval = Interval.closed(
217
+ epoch + Duration.hours(10.0),
218
+ epoch + Duration.hours(10.0) + Duration.seconds(1.0),
219
+ )
220
+
221
+ close_approaches = generator.compute_close_approaches(
222
+ trajectory=other_trajectory,
223
+ search_interval=search_interval,
224
+ )
225
+
226
+ assert close_approaches is not None
227
+ assert isinstance(close_approaches, list)
228
+ assert len(close_approaches) == 0
@@ -0,0 +1,244 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ from ostk.physics.unit import Length
6
+ from ostk.physics.unit import Angle
7
+ from ostk.physics.time import DateTime
8
+ from ostk.physics.time import Scale
9
+ from ostk.physics.time import Instant
10
+ from ostk.physics.coordinate import Frame
11
+ from ostk.physics import Environment
12
+ from ostk.physics.environment.object import Celestial
13
+
14
+ from ostk.astrodynamics.trajectory import Orbit
15
+ from ostk.astrodynamics.trajectory import State
16
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
17
+ from ostk.astrodynamics.trajectory.orbit.model import Kepler
18
+ from ostk.astrodynamics.trajectory.orbit.model.kepler import COE
19
+ from ostk.astrodynamics.conjunction import CloseApproach
20
+
21
+
22
+ @pytest.fixture
23
+ def environment() -> Environment:
24
+ return Environment.default()
25
+
26
+
27
+ @pytest.fixture
28
+ def earth(environment: Environment) -> Celestial:
29
+ return environment.access_celestial_object_with_name("Earth")
30
+
31
+
32
+ @pytest.fixture
33
+ def instant() -> Instant:
34
+ return Instant.date_time(DateTime(2024, 1, 1, 0, 0, 0), Scale.UTC)
35
+
36
+
37
+ @pytest.fixture
38
+ def gcrf_frame() -> Frame:
39
+ return Frame.GCRF()
40
+
41
+
42
+ @pytest.fixture
43
+ def object_1_state(
44
+ instant: Instant,
45
+ earth: Celestial,
46
+ ) -> State:
47
+ orbit = Orbit(
48
+ model=Kepler(
49
+ coe=COE(
50
+ semi_major_axis=Length.kilometers(7000.0),
51
+ eccentricity=0.0,
52
+ inclination=Angle.degrees(0.0),
53
+ raan=Angle.degrees(0.0),
54
+ aop=Angle.degrees(0.0),
55
+ true_anomaly=Angle.degrees(0.0),
56
+ ),
57
+ epoch=instant,
58
+ celestial_object=earth,
59
+ perturbation_type=Kepler.PerturbationType.No,
60
+ ),
61
+ celestial_object=earth,
62
+ )
63
+ return orbit.get_state_at(instant)
64
+
65
+
66
+ @pytest.fixture
67
+ def object_2_state(
68
+ instant: Instant,
69
+ earth: Celestial,
70
+ ) -> State:
71
+ orbit = Orbit(
72
+ model=Kepler(
73
+ coe=COE(
74
+ semi_major_axis=Length.kilometers(7000.0),
75
+ eccentricity=0.0,
76
+ inclination=Angle.degrees(90.0),
77
+ raan=Angle.degrees(0.0),
78
+ aop=Angle.degrees(0.0),
79
+ true_anomaly=Angle.degrees(1.0),
80
+ ),
81
+ epoch=instant,
82
+ celestial_object=earth,
83
+ perturbation_type=Kepler.PerturbationType.No,
84
+ ),
85
+ celestial_object=earth,
86
+ )
87
+ return orbit.get_state_at(instant)
88
+
89
+
90
+ @pytest.fixture
91
+ def close_approach(
92
+ object_1_state: State,
93
+ object_2_state: State,
94
+ ) -> CloseApproach:
95
+ return CloseApproach(
96
+ object_1_state=object_1_state,
97
+ object_2_state=object_2_state,
98
+ )
99
+
100
+
101
+ class TestCloseApproach:
102
+ def test_constructor_success(
103
+ self,
104
+ object_1_state: State,
105
+ object_2_state: State,
106
+ ):
107
+ close_approach = CloseApproach(
108
+ object_1_state=object_1_state,
109
+ object_2_state=object_2_state,
110
+ )
111
+
112
+ assert close_approach is not None
113
+ assert isinstance(close_approach, CloseApproach)
114
+
115
+ def test_is_defined_success(
116
+ self,
117
+ close_approach: CloseApproach,
118
+ ):
119
+ assert close_approach.is_defined() is True
120
+
121
+ def test_undefined_success(self):
122
+ close_approach = CloseApproach.undefined()
123
+
124
+ assert close_approach is not None
125
+ assert isinstance(close_approach, CloseApproach)
126
+ assert close_approach.is_defined() is False
127
+
128
+ def test_get_object_1_state_success(
129
+ self,
130
+ close_approach: CloseApproach,
131
+ object_1_state: State,
132
+ ):
133
+ state = close_approach.get_object_1_state()
134
+
135
+ assert state is not None
136
+ assert isinstance(state, State)
137
+ assert state.get_instant() == object_1_state.get_instant()
138
+
139
+ def test_get_object_2_state_success(
140
+ self,
141
+ close_approach: CloseApproach,
142
+ object_2_state: State,
143
+ ):
144
+ state = close_approach.get_object_2_state()
145
+
146
+ assert state is not None
147
+ assert isinstance(state, State)
148
+ assert state.get_instant() == object_2_state.get_instant()
149
+
150
+ def test_get_instant_success(
151
+ self,
152
+ close_approach: CloseApproach,
153
+ instant: Instant,
154
+ ):
155
+ ca_instant = close_approach.get_instant()
156
+
157
+ assert ca_instant is not None
158
+ assert isinstance(ca_instant, Instant)
159
+ assert ca_instant == instant
160
+
161
+ def test_get_miss_distance_success(
162
+ self,
163
+ close_approach: CloseApproach,
164
+ ):
165
+ miss_distance = close_approach.get_miss_distance()
166
+
167
+ assert miss_distance is not None
168
+ assert isinstance(miss_distance, Length)
169
+ assert miss_distance.in_meters() > 0.0
170
+
171
+ def test_get_relative_state_success(
172
+ self,
173
+ close_approach: CloseApproach,
174
+ ):
175
+ relative_state = close_approach.get_relative_state()
176
+
177
+ assert relative_state is not None
178
+ assert isinstance(relative_state, State)
179
+
180
+ def test_compute_miss_distance_components_in_frame_success(
181
+ self,
182
+ close_approach: CloseApproach,
183
+ gcrf_frame: Frame,
184
+ ):
185
+ components = close_approach.compute_miss_distance_components_in_frame(
186
+ frame=gcrf_frame
187
+ )
188
+
189
+ assert components is not None
190
+ assert isinstance(components, tuple)
191
+ assert len(components) == 3
192
+
193
+ # Check each component is a Length
194
+ for component in components:
195
+ assert isinstance(component, Length)
196
+
197
+ def test_compute_miss_distance_components_in_local_orbital_frame_success(
198
+ self,
199
+ close_approach: CloseApproach,
200
+ gcrf_frame: Frame,
201
+ ):
202
+ qsw_factory = LocalOrbitalFrameFactory.QSW(gcrf_frame)
203
+
204
+ components = (
205
+ close_approach.compute_miss_distance_components_in_local_orbital_frame(
206
+ local_orbital_frame_factory=qsw_factory
207
+ )
208
+ )
209
+
210
+ assert components is not None
211
+ assert isinstance(components, tuple)
212
+ assert len(components) == 3
213
+
214
+ # Check each component is a Length
215
+ for component in components:
216
+ assert isinstance(component, Length)
217
+
218
+ def test_equality_operators_success(
219
+ self,
220
+ object_1_state: State,
221
+ object_2_state: State,
222
+ ):
223
+ close_approach_1 = CloseApproach(
224
+ object_1_state=object_1_state,
225
+ object_2_state=object_2_state,
226
+ )
227
+
228
+ close_approach_2 = CloseApproach(
229
+ object_1_state=object_1_state,
230
+ object_2_state=object_2_state,
231
+ )
232
+
233
+ assert close_approach_1 == close_approach_2
234
+ assert not (close_approach_1 != close_approach_2)
235
+
236
+ def test_string_representation_success(
237
+ self,
238
+ close_approach: CloseApproach,
239
+ ):
240
+ string_repr = str(close_approach)
241
+
242
+ assert string_repr is not None
243
+ assert isinstance(string_repr, str)
244
+ assert len(string_repr) > 0
@@ -22,7 +22,6 @@ from ostk.astrodynamics.trajectory.state import CoordinateBroker
22
22
  from ostk.astrodynamics.trajectory import State
23
23
  from ostk.astrodynamics.flight.system import PropulsionSystem
24
24
  from ostk.astrodynamics.flight.system import SatelliteSystem
25
- from ostk.astrodynamics import Dynamics
26
25
  from ostk.astrodynamics.dynamics import Thruster
27
26
  from ostk.astrodynamics.guidance_law import ConstantThrust
28
27
 
@@ -96,11 +95,27 @@ def state(coordinate_broker: CoordinateBroker) -> State:
96
95
 
97
96
 
98
97
  class TestThruster:
99
- def test_constructors(self, dynamics: Thruster):
100
- assert dynamics is not None
101
- assert isinstance(dynamics, Thruster)
102
- assert isinstance(dynamics, Dynamics)
103
- assert dynamics.is_defined()
98
+ def test_constructors(
99
+ self,
100
+ guidance_law: ConstantThrust,
101
+ satellite_system: SatelliteSystem,
102
+ ):
103
+ thrusters = [
104
+ Thruster(
105
+ satellite_system=satellite_system,
106
+ guidance_law=guidance_law,
107
+ ),
108
+ Thruster(
109
+ satellite_system=satellite_system,
110
+ guidance_law=guidance_law,
111
+ name="A name",
112
+ ),
113
+ ]
114
+
115
+ for thruster in thrusters:
116
+ assert thruster is not None
117
+ assert isinstance(thruster, Thruster)
118
+ assert thruster.is_defined()
104
119
 
105
120
  def test_getters(self, dynamics: Thruster):
106
121
  assert dynamics.get_satellite_system() is not None
@@ -4,16 +4,18 @@ import pytest
4
4
 
5
5
  import numpy as np
6
6
 
7
-
8
7
  from ostk.physics.time import Instant
9
8
  from ostk.physics.time import Interval
10
9
  from ostk.physics.time import Duration
11
10
  from ostk.physics.coordinate import Frame
12
11
  from ostk.physics.unit import Mass
12
+ from ostk.physics.unit import Angle
13
13
 
14
+ from ostk.astrodynamics.dynamics import Tabulated as TabulatedDynamics
14
15
  from ostk.astrodynamics.flight import Maneuver
16
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameDirection
17
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
15
18
  from ostk.astrodynamics.trajectory import State
16
- from ostk.astrodynamics.dynamics import Tabulated as TabulatedDynamics
17
19
  from ostk.astrodynamics.trajectory.state import CoordinateSubset
18
20
  from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianPosition
19
21
  from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianVelocity
@@ -184,12 +186,44 @@ class TestManeuver:
184
186
 
185
187
  assert tabulated_dynamics.access_frame() == frame
186
188
 
189
+ def test_calculate_mean_thrust_direction_and_maximum_angular_offset(
190
+ self,
191
+ maneuver: Maneuver,
192
+ ):
193
+ mean_thrust_direction_and_maximum_angular_offset = (
194
+ maneuver.calculate_mean_thrust_direction_and_maximum_angular_offset(
195
+ local_orbital_frame_factory=LocalOrbitalFrameFactory.TNW(Frame.GCRF()),
196
+ )
197
+ )
198
+ assert isinstance(
199
+ mean_thrust_direction_and_maximum_angular_offset[0],
200
+ LocalOrbitalFrameDirection,
201
+ )
202
+ assert isinstance(mean_thrust_direction_and_maximum_angular_offset[1], Angle)
203
+
204
+ def test_to_constant_local_orbital_frame_direction_maneuver(
205
+ self,
206
+ maneuver: Maneuver,
207
+ ):
208
+ maneuver: Maneuver = maneuver.to_constant_local_orbital_frame_direction_maneuver(
209
+ local_orbital_frame_factory=LocalOrbitalFrameFactory.TNW(Frame.GCRF()),
210
+ )
211
+ assert maneuver.is_defined()
212
+
213
+ maneuver_with_maximum_allowed_angular_offset: Maneuver = (
214
+ maneuver.to_constant_local_orbital_frame_direction_maneuver(
215
+ local_orbital_frame_factory=LocalOrbitalFrameFactory.TNW(Frame.GCRF()),
216
+ maximum_allowed_angular_offset=Angle.degrees(180.0),
217
+ )
218
+ )
219
+ assert maneuver_with_maximum_allowed_angular_offset.is_defined()
220
+
187
221
  def test_from_constant_mass_flow_rate(
188
222
  self,
189
223
  states: list[State],
190
224
  ):
191
225
  mass_flow_rate: float = -1.0e-5
192
- maneuver = Maneuver.constant_mass_flow_rate_profile(
226
+ maneuver: Maneuver = Maneuver.constant_mass_flow_rate_profile(
193
227
  states=states,
194
228
  mass_flow_rate=mass_flow_rate,
195
229
  )
@@ -2,16 +2,26 @@
2
2
 
3
3
  import pytest
4
4
 
5
- from ostk.physics.time import Instant
5
+ import numpy as np
6
+
6
7
  from ostk.physics.time import DateTime
8
+ from ostk.physics.time import Duration
9
+ from ostk.physics.time import Instant
7
10
  from ostk.physics.time import Scale
11
+ from ostk.physics.unit import Angle
8
12
  from ostk.physics.coordinate import Frame
9
13
 
10
14
  from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
11
15
  from ostk.astrodynamics.trajectory import LocalOrbitalFrameDirection
12
16
 
13
17
  from ostk.astrodynamics import GuidanceLaw
18
+ from ostk.astrodynamics.flight import Maneuver
14
19
  from ostk.astrodynamics.guidance_law import ConstantThrust
20
+ from ostk.astrodynamics.trajectory import State
21
+ from ostk.astrodynamics.trajectory.state import CoordinateSubset
22
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianPosition
23
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianVelocity
24
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianAcceleration
15
25
 
16
26
 
17
27
  @pytest.fixture
@@ -54,6 +64,64 @@ def frame() -> Frame:
54
64
  return Frame.GCRF()
55
65
 
56
66
 
67
+ @pytest.fixture
68
+ def instants() -> list[Instant]:
69
+ return [
70
+ Instant.J2000(),
71
+ Instant.J2000() + Duration.seconds(30.0),
72
+ Instant.J2000() + Duration.seconds(35.0),
73
+ Instant.J2000() + Duration.seconds(37.0),
74
+ ]
75
+
76
+
77
+ @pytest.fixture
78
+ def acceleration_profile() -> list[np.ndarray]:
79
+ return [
80
+ np.array([1.0e-3, 0.0e-3, 0.0e-3]),
81
+ np.array([0.0e-3, 1.0e-3, 0.0e-3]),
82
+ np.array([0.0e-3, 0.0e-3, 1.0e-3]),
83
+ np.array([1.0e-3, 1.0e-3, 1.0e-3]),
84
+ ]
85
+
86
+
87
+ @pytest.fixture
88
+ def mass_flow_rate_profile() -> list[float]:
89
+ return [-1.0e-5, -1.1e-5, -0.9e-5, -1.0e-5]
90
+
91
+
92
+ @pytest.fixture
93
+ def coordinate_subsets() -> list[CoordinateSubset]:
94
+ return [
95
+ CartesianPosition.default(),
96
+ CartesianVelocity.default(),
97
+ CartesianAcceleration.thrust_acceleration(),
98
+ CoordinateSubset.mass_flow_rate(),
99
+ ]
100
+
101
+
102
+ @pytest.fixture
103
+ def maneuver(
104
+ instants: list[Instant],
105
+ acceleration_profile: list[np.ndarray],
106
+ mass_flow_rate_profile: list[float],
107
+ frame: Frame,
108
+ coordinate_subsets: list[CoordinateSubset],
109
+ ) -> Maneuver:
110
+ states = []
111
+ for instant, acceleration, mass_flow_rate in zip(
112
+ instants, acceleration_profile, mass_flow_rate_profile
113
+ ):
114
+ states.append(
115
+ State(
116
+ instant,
117
+ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, *acceleration, mass_flow_rate],
118
+ frame,
119
+ coordinate_subsets,
120
+ )
121
+ )
122
+ return Maneuver(states)
123
+
124
+
57
125
  class TestConstantThrust:
58
126
  def test_constructors(self, guidance_law: ConstantThrust):
59
127
  assert guidance_law is not None
@@ -89,3 +157,21 @@ class TestConstantThrust:
89
157
 
90
158
  assert len(contribution) == 3
91
159
  assert contribution == pytest.approx([0.0, 0.009523809523809525, 0.0], abs=5e-11)
160
+
161
+ def test_from_maneuver(self, maneuver: Maneuver):
162
+ constant_thrust = ConstantThrust.from_maneuver(
163
+ maneuver=maneuver,
164
+ local_orbital_frame_factory=LocalOrbitalFrameFactory.TNW(Frame.GCRF()),
165
+ )
166
+ assert isinstance(constant_thrust, ConstantThrust)
167
+
168
+ constant_thrust_with_maximum_allowed_angular_offset = (
169
+ ConstantThrust.from_maneuver(
170
+ maneuver=maneuver,
171
+ local_orbital_frame_factory=LocalOrbitalFrameFactory.TNW(Frame.GCRF()),
172
+ maximum_allowed_angular_offset=Angle.degrees(180.0),
173
+ )
174
+ )
175
+ assert isinstance(
176
+ constant_thrust_with_maximum_allowed_angular_offset, ConstantThrust
177
+ )