open-space-toolkit-astrodynamics 16.9.0__py313-none-manylinux2014_x86_64.whl → 16.10.0__py313-none-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: open-space-toolkit-astrodynamics
3
- Version: 16.9.0
3
+ Version: 16.10.0
4
4
  Summary: Orbit, attitude, access.
5
5
  Author: Open Space Collective
6
6
  Author-email: contact@open-space-collective.org
@@ -1,7 +1,7 @@
1
1
  ostk/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
2
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-313-x86_64-linux-gnu.so,sha256=td13l_Mx2sn4AWJvcqrDg3THpIwQ9OycJ4jgo91r2_E,3029496
2
+ ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-313-x86_64-linux-gnu.so,sha256=_bkSMpoY3gGZ7KFYdgCek0NvilsEVV0IWYvmHUhWH0U,3074584
3
3
  ostk/astrodynamics/__init__.py,sha256=3gWyqFIbhAfcdeMhmfBPQPlPQTmaOzm-6flkJe745Zk,251
4
- ostk/astrodynamics/__init__.pyi,sha256=tmYS8pvUiJNxU5fDCOWHoGB36E0ImAVKQQDo2MfllXk,32224
4
+ ostk/astrodynamics/__init__.pyi,sha256=3oFac7idhJ9COTt9FdIVYba1kQB-Knbs6DcmDxGvGmA,32224
5
5
  ostk/astrodynamics/access.pyi,sha256=pp2t81kKH4sP4BcGb8nAZHmpwhISjFODMpbZ4ucLFAw,25157
6
6
  ostk/astrodynamics/converters.py,sha256=luPh30qMp9bzEkN7hUccmxlLf7zRp_AzqmBe8IUjPhU,3314
7
7
  ostk/astrodynamics/converters.pyi,sha256=tTUT0xGbiUkKxUj79Hq5EEtxV3b2uR6Wsnj_vwyj-mk,1625
@@ -12,11 +12,12 @@ ostk/astrodynamics/eclipse.pyi,sha256=Q8fzdukA7fyxbUM3wjEXAHukT-Ovl4FM00NbjsMPnU
12
12
  ostk/astrodynamics/estimator.pyi,sha256=MnahWzp8aACxrNKWlYRsgQr5zpBxogNr-yPm7hJob5k,14000
13
13
  ostk/astrodynamics/event_condition.pyi,sha256=2c_1Sq7tkYKeAA_aRWBi43KDQXXxu6EMSmUpEWz_Fa4,45814
14
14
  ostk/astrodynamics/guidance_law.pyi,sha256=Xtqneu5PLpidOoSH4YIAY3D8rWZ0QH_ESqEdfbiTBVQ,16936
15
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=5D-qWSAurqGG-_6sV1KXruQZ1gey3CFK_XzY7rTgpSk,133661616
15
+ ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=zqmjdqyKyqR0GHGqWrfpvZUoRqrsmnq1I0lBW4w9hsI,136595472
16
16
  ostk/astrodynamics/solver.pyi,sha256=dRdR9RLqwSaBBX_ZRmpeBv06WvM_BFNo3WUdNzorYTY,17805
17
17
  ostk/astrodynamics/utilities.py,sha256=y8mr3M46J5z-GhS1oIEnuEJA6otwcyJ9YDhvn_5JxHM,6976
18
18
  ostk/astrodynamics/viewer.py,sha256=SlKyOWKjaF3V9HFB3I7ZgHy7n_GLeHTWM9q2wXkpxe8,27077
19
- ostk/astrodynamics/conjunction/__init__.pyi,sha256=HFvWl8bCmrq3cBkUh5X5RGIh8webmVGxaZdpsz3WN-E,79
19
+ ostk/astrodynamics/conjunction/__init__.pyi,sha256=TEfTmgCRKr68B1GPJscDOgI130jQf7GLbtoKZPtx_SU,4918
20
+ ostk/astrodynamics/conjunction/close_approach.pyi,sha256=lDGzezf8ELdReQ0TGaqW36gpS9TwBN9QiCNrMi9X8v0,3690
20
21
  ostk/astrodynamics/conjunction/message/__init__.pyi,sha256=5H__sg_QUx7ybf9jtVWvXzrUHeK3ECotfhddAdHjJUc,75
21
22
  ostk/astrodynamics/conjunction/message/ccsds.pyi,sha256=1Peto19hRqlD7KHf1cyLP3CT4OAKzwtemqvO6_4FZ0g,28162
22
23
  ostk/astrodynamics/data/__init__.pyi,sha256=4l_mfVbnU_L7wImwgTCe8fVI81gK_tUmd0z7BY9lLi8,81
@@ -44,6 +45,9 @@ ostk/astrodynamics/test/test_viewer.py,sha256=6IxHjSrwnLkmLiTTzRcnwAdEeWdIF_b2Kj
44
45
  ostk/astrodynamics/test/access/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
45
46
  ostk/astrodynamics/test/access/test_generator.py,sha256=i7TnM80kF0Q_9KmyoqKt5n1ufg3ZjAIEMPVVds8ZDdI,10315
46
47
  ostk/astrodynamics/test/access/test_visibility_criterion.py,sha256=VA6WDQTj3q-f2YGIIkrrNp8G23Nf_0g9nKmfZAgAlWQ,6568
48
+ ostk/astrodynamics/test/conjunction/test_close_approach.py,sha256=vuvVNj2xG8MhM-N5zsAtH7kFciM0V1KMWDd_vNvkCMg,6864
49
+ ostk/astrodynamics/test/conjunction/close_approach/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
+ ostk/astrodynamics/test/conjunction/close_approach/test_generator.py,sha256=NEDVdhL_Ll2iTo8dCBFuLdE1soFC0FUlx-tuuA8tGIg,6536
47
51
  ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
48
52
  ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py,sha256=xyTd24828Ue2HgOWY8UhizIc5mzgRgoH2VSOfVfKCdw,14632
49
53
  ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py,sha256=ztpnOHI3ZiIV0QwijJ6Si_lIna8jLZF45r1KtPVK4ws,18514
@@ -140,8 +144,8 @@ ostk/astrodynamics/trajectory/orbit/model/kepler.pyi,sha256=KL-FwPbG4OUkPjhQb1Pa
140
144
  ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi,sha256=OhFzoPPQHlYy7m3LiZ8TXF89M4uBTfNk6tGsBEp0sjI,14235
141
145
  ostk/astrodynamics/trajectory/state/__init__.pyi,sha256=bq__Fii35czVrTeNxc9eQVjXdqwbbQxUdNQWK3vLrMo,17649
142
146
  ostk/astrodynamics/trajectory/state/coordinate_subset.pyi,sha256=kYMfMwEjCqO1NepMYFT4QS6kIPBkVL6sGCLeLbogcMw,10176
143
- open_space_toolkit_astrodynamics-16.9.0.dist-info/METADATA,sha256=gBHINQ0oyiWejSohb8XPjnxzlfl27qPJf5UrIM4YD-k,1913
144
- open_space_toolkit_astrodynamics-16.9.0.dist-info/WHEEL,sha256=mg_J2-vBxmW0UA4lbss-1qkxaUbm-OTIuSSaNWv6mlo,110
145
- open_space_toolkit_astrodynamics-16.9.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
146
- open_space_toolkit_astrodynamics-16.9.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
147
- open_space_toolkit_astrodynamics-16.9.0.dist-info/RECORD,,
147
+ open_space_toolkit_astrodynamics-16.10.0.dist-info/METADATA,sha256=VRCTKInuQXRzjAIDnrl4EOduGokQYU72xGWdlk--r1I,1914
148
+ open_space_toolkit_astrodynamics-16.10.0.dist-info/WHEEL,sha256=mg_J2-vBxmW0UA4lbss-1qkxaUbm-OTIuSSaNWv6mlo,110
149
+ open_space_toolkit_astrodynamics-16.10.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
150
+ open_space_toolkit_astrodynamics-16.10.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
151
+ open_space_toolkit_astrodynamics-16.10.0.dist-info/RECORD,,
@@ -24,8 +24,8 @@ import ostk.physics.coordinate
24
24
  import ostk.physics.coordinate.spherical
25
25
  from ostk.physics import environment
26
26
  import ostk.physics.environment.object
27
- import ostk.physics.time
28
27
  from ostk.physics import time
28
+ import ostk.physics.time
29
29
  import ostk.physics.unit
30
30
  from ostk.physics import unit
31
31
  import typing
@@ -1,3 +1,121 @@
1
1
  from __future__ import annotations
2
+ import ostk.astrodynamics.trajectory
3
+ import ostk.physics.coordinate
4
+ import ostk.physics.time
5
+ import ostk.physics.unit
6
+ import typing
7
+ from . import close_approach
2
8
  from . import message
3
- __all__ = ['message']
9
+ __all__ = ['CloseApproach', 'close_approach', 'message']
10
+ class CloseApproach:
11
+ """
12
+
13
+ Close approach between two objects.
14
+
15
+ This class represents a close approach event between two objects, providing access to the states of both
16
+ objects at the time of closest approach, the miss distance, and the relative state.
17
+
18
+ """
19
+ __hash__: typing.ClassVar[None] = None
20
+ @staticmethod
21
+ def undefined() -> CloseApproach:
22
+ """
23
+ Construct an undefined close approach.
24
+
25
+ Returns:
26
+ CloseApproach: An undefined close approach.
27
+ """
28
+ def __eq__(self, arg0: CloseApproach) -> bool:
29
+ """
30
+ Equal to operator.
31
+
32
+ Args:
33
+ other (CloseApproach): Another close approach.
34
+
35
+ Returns:
36
+ bool: True if close approaches are equal.
37
+ """
38
+ def __init__(self, object_1_state: ostk.astrodynamics.trajectory.State, object_2_state: ostk.astrodynamics.trajectory.State) -> None:
39
+ """
40
+ Constructor.
41
+
42
+ Args:
43
+ object_1_state (State): The state of Object 1.
44
+ object_2_state (State): The state of Object 2.
45
+ """
46
+ def __ne__(self, arg0: CloseApproach) -> bool:
47
+ """
48
+ Not equal to operator.
49
+
50
+ Args:
51
+ other (CloseApproach): Another close approach.
52
+
53
+ Returns:
54
+ bool: True if close approaches are not equal.
55
+ """
56
+ def __repr__(self) -> str:
57
+ ...
58
+ def __str__(self) -> str:
59
+ ...
60
+ def compute_miss_distance_components_in_frame(self, frame: ostk.physics.coordinate.Frame) -> tuple[ostk.physics.unit.Length, ostk.physics.unit.Length, ostk.physics.unit.Length]:
61
+ """
62
+ Compute the miss distance components in the desired frame.
63
+
64
+ Args:
65
+ frame (Frame): The frame in which to resolve the miss distance components.
66
+
67
+ Returns:
68
+ tuple[Length, Length, Length]: The miss distance components (x, y, z).
69
+ """
70
+ def compute_miss_distance_components_in_local_orbital_frame(self, local_orbital_frame_factory: ostk.astrodynamics.trajectory.LocalOrbitalFrameFactory) -> tuple[ostk.physics.unit.Length, ostk.physics.unit.Length, ostk.physics.unit.Length]:
71
+ """
72
+ Compute the miss distance components in a local orbital frame (generated from Object 1 state).
73
+
74
+ Args:
75
+ local_orbital_frame_factory (LocalOrbitalFrameFactory): The local orbital frame factory.
76
+
77
+ Returns:
78
+ tuple[Length, Length, Length]: The miss distance components (radial, in-track, cross-track or similar depending on the factory).
79
+ """
80
+ def get_instant(self) -> ostk.physics.time.Instant:
81
+ """
82
+ Get the instant of the close approach.
83
+
84
+ Returns:
85
+ Instant: The instant of closest approach.
86
+ """
87
+ def get_miss_distance(self) -> ostk.physics.unit.Length:
88
+ """
89
+ Get the miss distance.
90
+
91
+ Returns:
92
+ Length: The miss distance between the two objects.
93
+ """
94
+ def get_object_1_state(self) -> ostk.astrodynamics.trajectory.State:
95
+ """
96
+ Get the state of Object 1.
97
+
98
+ Returns:
99
+ State: The state of Object 1.
100
+ """
101
+ def get_object_2_state(self) -> ostk.astrodynamics.trajectory.State:
102
+ """
103
+ Get the state of Object 2.
104
+
105
+ Returns:
106
+ State: The state of Object 2.
107
+ """
108
+ def get_relative_state(self) -> ostk.astrodynamics.trajectory.State:
109
+ """
110
+ Get the relative state (Object 2 relative to Object 1).
111
+
112
+ Returns:
113
+ State: The relative state.
114
+ """
115
+ def is_defined(self) -> bool:
116
+ """
117
+ Check if the close approach is defined.
118
+
119
+ Returns:
120
+ bool: True if close approach is defined, False otherwise.
121
+ """
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+ import ostk.astrodynamics
3
+ import ostk.astrodynamics.conjunction
4
+ import ostk.physics.time
5
+ __all__ = ['Generator']
6
+ class Generator:
7
+ """
8
+
9
+ Compute close approaches to a reference trajectory.
10
+
11
+ This class computes close approach events between a reference trajectory and other object trajectories
12
+ over a specified time interval. It uses a temporal condition solver to identify time periods when objects
13
+ are approaching and then determines the exact time of closest approach.
14
+
15
+ """
16
+ @staticmethod
17
+ def undefined() -> Generator:
18
+ """
19
+ Construct an undefined generator.
20
+
21
+ Returns:
22
+ Generator: An undefined generator.
23
+ """
24
+ def __init__(self, reference_trajectory: ostk.astrodynamics.Trajectory, step: ostk.physics.time.Duration = ..., tolerance: ostk.physics.time.Duration = ...) -> None:
25
+ """
26
+ Constructor.
27
+
28
+ Args:
29
+ reference_trajectory (Trajectory): The reference trajectory for which to compute close approaches (Object 1).
30
+ step (Duration): The step to use during the close approach search. Defaults to Duration.seconds(15.0).
31
+ tolerance (Duration): The tolerance to use during the close approach search. Defaults to Duration.milliseconds(1.0).
32
+ """
33
+ def compute_close_approaches(self, trajectory: ostk.astrodynamics.Trajectory, search_interval: ostk.physics.time.Interval) -> list[ostk.astrodynamics.conjunction.CloseApproach]:
34
+ """
35
+ Compute close approaches between the reference trajectory and another object over a search interval.
36
+
37
+ Args:
38
+ trajectory (Trajectory): The trajectory of the other object (Object 2).
39
+ search_interval (Interval): The interval over which close approaches are searched.
40
+
41
+ Returns:
42
+ list[CloseApproach]: Array of close approaches over the search interval (with Object 1 being the reference trajectory).
43
+ """
44
+ def get_reference_trajectory(self) -> ostk.astrodynamics.Trajectory:
45
+ """
46
+ Get the reference trajectory.
47
+
48
+ Returns:
49
+ Trajectory: The reference trajectory.
50
+ """
51
+ def get_step(self) -> ostk.physics.time.Duration:
52
+ """
53
+ Get the step.
54
+
55
+ Returns:
56
+ Duration: The step.
57
+ """
58
+ def get_tolerance(self) -> ostk.physics.time.Duration:
59
+ """
60
+ Get the tolerance.
61
+
62
+ Returns:
63
+ Duration: The tolerance.
64
+ """
65
+ def is_defined(self) -> bool:
66
+ """
67
+ Check if the generator is defined.
68
+
69
+ Returns:
70
+ bool: True if generator is defined, False otherwise.
71
+ """
72
+ def set_step(self, step: ostk.physics.time.Duration) -> None:
73
+ """
74
+ Set the step.
75
+
76
+ Args:
77
+ step (Duration): The step.
78
+ """
79
+ def set_tolerance(self, tolerance: ostk.physics.time.Duration) -> None:
80
+ """
81
+ Set the tolerance.
82
+
83
+ Args:
84
+ tolerance (Duration): The tolerance.
85
+ """
@@ -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