open-space-toolkit-astrodynamics 16.8.0__py310-none-manylinux2014_aarch64.whl → 16.10.0__py310-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.
- {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/METADATA +1 -1
- {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/RECORD +22 -17
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-310-aarch64-linux-gnu.so +0 -0
- ostk/astrodynamics/__init__.pyi +2 -2
- ostk/astrodynamics/conjunction/__init__.pyi +119 -1
- ostk/astrodynamics/conjunction/close_approach.pyi +85 -0
- ostk/astrodynamics/dynamics.pyi +1 -1
- ostk/astrodynamics/flight/__init__.pyi +29 -1
- ostk/astrodynamics/guidance_law.pyi +82 -7
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16 +0 -0
- ostk/astrodynamics/test/conjunction/close_approach/__init__.py +0 -0
- ostk/astrodynamics/test/conjunction/close_approach/test_generator.py +228 -0
- ostk/astrodynamics/test/conjunction/test_close_approach.py +244 -0
- ostk/astrodynamics/test/dynamics/test_thruster.py +21 -6
- ostk/astrodynamics/test/flight/test_maneuver.py +37 -3
- ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +87 -1
- ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py +164 -0
- ostk/astrodynamics/test/trajectory/test_segment.py +107 -2
- ostk/astrodynamics/trajectory/__init__.pyi +24 -0
- {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/WHEEL +0 -0
- {open_space_toolkit_astrodynamics-16.8.0.dist-info → open_space_toolkit_astrodynamics-16.10.0.dist-info}/top_level.txt +0 -0
- {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(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
+
)
|