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,164 @@
1
+ # Apache License 2.0
2
+
3
+ import numpy as np
4
+
5
+ import pytest
6
+
7
+ from ostk.physics.coordinate import Frame
8
+ from ostk.physics.time import Instant
9
+ from ostk.physics.time import Duration
10
+ from ostk.physics.time import Interval
11
+ from ostk.astrodynamics import GuidanceLaw
12
+ from ostk.astrodynamics.guidance_law import HeterogeneousGuidanceLaw
13
+
14
+
15
+ @pytest.fixture
16
+ def interval_1() -> Interval:
17
+ return Interval.closed(
18
+ start_instant=Instant.J2000(),
19
+ end_instant=Instant.J2000() + Duration.seconds(100.0),
20
+ )
21
+
22
+
23
+ @pytest.fixture
24
+ def guidance_law_1() -> GuidanceLaw:
25
+ class GuidanceLaw1(GuidanceLaw):
26
+
27
+ def __init__(
28
+ self,
29
+ name: str,
30
+ ):
31
+ super().__init__(name)
32
+
33
+ def calculate_thrust_acceleration_at(
34
+ self,
35
+ instant: Instant,
36
+ position_coordinates: np.array,
37
+ velocity_coordinates: np.array,
38
+ thrust_acceleration: float,
39
+ output_frame: Frame,
40
+ ) -> np.array:
41
+ return np.array([1.0, 2.0, 3.0])
42
+
43
+ return GuidanceLaw1("My Guidance Law 1")
44
+
45
+
46
+ @pytest.fixture
47
+ def interval_2() -> Interval:
48
+ return Interval.closed(
49
+ start_instant=Instant.J2000() + Duration.seconds(200.0),
50
+ end_instant=Instant.J2000() + Duration.seconds(300.0),
51
+ )
52
+
53
+
54
+ @pytest.fixture
55
+ def guidance_law_2() -> GuidanceLaw:
56
+ class GuidanceLaw2(GuidanceLaw):
57
+
58
+ def __init__(
59
+ self,
60
+ name: str,
61
+ ):
62
+ super().__init__(name)
63
+
64
+ def calculate_thrust_acceleration_at(
65
+ self,
66
+ instant: Instant,
67
+ position_coordinates: np.array,
68
+ velocity_coordinates: np.array,
69
+ thrust_acceleration: float,
70
+ output_frame: Frame,
71
+ ) -> np.array:
72
+ return np.array([4.0, 5.0, 6.0])
73
+
74
+ return GuidanceLaw2("My Guidance Law 2")
75
+
76
+
77
+ @pytest.fixture
78
+ def heterogeneous_guidance_law(
79
+ interval_1: Interval,
80
+ guidance_law_1: GuidanceLaw,
81
+ interval_2: Interval,
82
+ guidance_law_2: GuidanceLaw,
83
+ ) -> HeterogeneousGuidanceLaw:
84
+ return HeterogeneousGuidanceLaw(
85
+ guidance_laws_with_intervals=[
86
+ (guidance_law_1, interval_1),
87
+ (guidance_law_2, interval_2),
88
+ ],
89
+ )
90
+
91
+
92
+ class TestHeterogeneousGuidanceLaw:
93
+ def test_constructor_and_getters(
94
+ self,
95
+ interval_1: Interval,
96
+ guidance_law_1: GuidanceLaw,
97
+ interval_2: Interval,
98
+ guidance_law_2: GuidanceLaw,
99
+ ):
100
+ heterogeneous_guidance_law = HeterogeneousGuidanceLaw(
101
+ guidance_laws_with_intervals=[
102
+ (guidance_law_1, interval_1),
103
+ (guidance_law_2, interval_2),
104
+ ],
105
+ )
106
+ assert heterogeneous_guidance_law is not None
107
+ assert isinstance(heterogeneous_guidance_law, HeterogeneousGuidanceLaw)
108
+ assert heterogeneous_guidance_law.get_guidance_laws_with_intervals() == [
109
+ (guidance_law_1, interval_1),
110
+ (guidance_law_2, interval_2),
111
+ ]
112
+
113
+ def test_add_guidance_law(
114
+ self,
115
+ interval_1: Interval,
116
+ guidance_law_1: GuidanceLaw,
117
+ interval_2: Interval,
118
+ guidance_law_2: GuidanceLaw,
119
+ ):
120
+ heterogeneous_guidance_law = HeterogeneousGuidanceLaw()
121
+
122
+ assert isinstance(heterogeneous_guidance_law, HeterogeneousGuidanceLaw)
123
+ assert heterogeneous_guidance_law.get_guidance_laws_with_intervals() == []
124
+
125
+ heterogeneous_guidance_law.add_guidance_law(guidance_law_1, interval_1)
126
+ assert heterogeneous_guidance_law.get_guidance_laws_with_intervals() == [
127
+ (guidance_law_1, interval_1),
128
+ ]
129
+
130
+ heterogeneous_guidance_law.add_guidance_law(guidance_law_2, interval_2)
131
+ assert heterogeneous_guidance_law.get_guidance_laws_with_intervals() == [
132
+ (guidance_law_1, interval_1),
133
+ (guidance_law_2, interval_2),
134
+ ]
135
+
136
+ @pytest.mark.parametrize(
137
+ (
138
+ "instant",
139
+ "expected_thrust_acceleration",
140
+ ),
141
+ [
142
+ (Instant.J2000() - Duration.seconds(50.0), np.array([0.0, 0.0, 0.0])),
143
+ (Instant.J2000() + Duration.seconds(50.0), np.array([1.0, 2.0, 3.0])),
144
+ (Instant.J2000() + Duration.seconds(150.0), np.array([0.0, 0.0, 0.0])),
145
+ (Instant.J2000() + Duration.seconds(250.0), np.array([4.0, 5.0, 6.0])),
146
+ (Instant.J2000() + Duration.seconds(350.0), np.array([0.0, 0.0, 0.0])),
147
+ ],
148
+ )
149
+ def test_calculate_thrust_acceleration_at(
150
+ self,
151
+ heterogeneous_guidance_law: HeterogeneousGuidanceLaw,
152
+ instant: Instant,
153
+ expected_thrust_acceleration: np.array,
154
+ ):
155
+ assert np.array_equal(
156
+ heterogeneous_guidance_law.calculate_thrust_acceleration_at(
157
+ instant=instant,
158
+ position_coordinates=np.array([0.0, 0.0, 0.0]),
159
+ velocity_coordinates=np.array([0.0, 0.0, 0.0]),
160
+ thrust_acceleration=1.0,
161
+ output_frame=Frame.GCRF(),
162
+ ),
163
+ expected_thrust_acceleration,
164
+ )
@@ -10,6 +10,7 @@ from ostk.physics.time import Scale
10
10
  from ostk.physics.time import Duration
11
11
  from ostk.physics.coordinate import Frame
12
12
  from ostk.physics.environment.object.celestial import Earth
13
+ from ostk.physics.unit import Angle
13
14
 
14
15
  from ostk.astrodynamics.flight.system import SatelliteSystem
15
16
  from ostk.astrodynamics import Dynamics
@@ -17,6 +18,7 @@ from ostk.astrodynamics.dynamics import CentralBodyGravity
17
18
  from ostk.astrodynamics.dynamics import PositionDerivative
18
19
  from ostk.astrodynamics.dynamics import Thruster
19
20
  from ostk.astrodynamics.guidance_law import ConstantThrust
21
+ from ostk.astrodynamics.trajectory import LocalOrbitalFrameFactory
20
22
  from ostk.astrodynamics.trajectory import State
21
23
  from ostk.astrodynamics.trajectory import Segment
22
24
  from ostk.astrodynamics.event_condition import InstantCondition
@@ -75,6 +77,11 @@ def dynamics(
75
77
  ]
76
78
 
77
79
 
80
+ @pytest.fixture
81
+ def local_orbital_frame_factory() -> LocalOrbitalFrameFactory:
82
+ return LocalOrbitalFrameFactory.VNC(Frame.GCRF())
83
+
84
+
78
85
  @pytest.fixture
79
86
  def numerical_solver() -> NumericalSolver:
80
87
  return NumericalSolver.default_conditional()
@@ -122,6 +129,51 @@ def maneuver_segment(
122
129
  )
123
130
 
124
131
 
132
+ @pytest.fixture
133
+ def maximum_allowed_angular_offset() -> Angle:
134
+ return Angle.degrees(180.0)
135
+
136
+
137
+ @pytest.fixture
138
+ def constant_local_orbital_frame_direction_maneuver_segment(
139
+ name: str,
140
+ instant_condition: InstantCondition,
141
+ thruster_dynamics: Thruster,
142
+ dynamics: list,
143
+ numerical_solver: NumericalSolver,
144
+ local_orbital_frame_factory: LocalOrbitalFrameFactory,
145
+ ) -> Segment:
146
+ return Segment.constant_local_orbital_frame_direction_maneuver(
147
+ name=name,
148
+ event_condition=instant_condition,
149
+ thruster_dynamics=thruster_dynamics,
150
+ dynamics=dynamics,
151
+ numerical_solver=numerical_solver,
152
+ local_orbital_frame_factory=local_orbital_frame_factory,
153
+ )
154
+
155
+
156
+ @pytest.fixture
157
+ def constant_local_orbital_frame_direction_maneuver_segment_with_maximum_allowed_angular_offset(
158
+ name: str,
159
+ instant_condition: InstantCondition,
160
+ thruster_dynamics: Thruster,
161
+ dynamics: list,
162
+ numerical_solver: NumericalSolver,
163
+ local_orbital_frame_factory: LocalOrbitalFrameFactory,
164
+ maximum_allowed_angular_offset: Angle,
165
+ ) -> Segment:
166
+ return Segment.constant_local_orbital_frame_direction_maneuver(
167
+ name=name,
168
+ event_condition=instant_condition,
169
+ thruster_dynamics=thruster_dynamics,
170
+ dynamics=dynamics,
171
+ numerical_solver=numerical_solver,
172
+ local_orbital_frame_factory=local_orbital_frame_factory,
173
+ maximum_allowed_angular_offset=maximum_allowed_angular_offset,
174
+ )
175
+
176
+
125
177
  @pytest.fixture
126
178
  def instants(state: State) -> list[Instant]:
127
179
  return [state.get_instant(), state.get_instant() + Duration.minutes(1.0)]
@@ -293,6 +345,41 @@ class TestSegment:
293
345
  is not None
294
346
  )
295
347
 
348
+ def test_constant_local_orbital_frame_direction_maneuver(
349
+ self,
350
+ name: str,
351
+ instant_condition: InstantCondition,
352
+ thruster_dynamics: ConstantThrust,
353
+ dynamics: list,
354
+ numerical_solver: NumericalSolver,
355
+ local_orbital_frame_factory: LocalOrbitalFrameFactory,
356
+ maximum_allowed_angular_offset: Angle,
357
+ ):
358
+ assert (
359
+ Segment.constant_local_orbital_frame_direction_maneuver(
360
+ name=name,
361
+ event_condition=instant_condition,
362
+ thruster_dynamics=thruster_dynamics,
363
+ dynamics=dynamics,
364
+ numerical_solver=numerical_solver,
365
+ local_orbital_frame_factory=local_orbital_frame_factory,
366
+ )
367
+ is not None
368
+ )
369
+
370
+ assert (
371
+ Segment.constant_local_orbital_frame_direction_maneuver(
372
+ name=name,
373
+ event_condition=instant_condition,
374
+ thruster_dynamics=thruster_dynamics,
375
+ dynamics=dynamics,
376
+ numerical_solver=numerical_solver,
377
+ local_orbital_frame_factory=local_orbital_frame_factory,
378
+ maximum_allowed_angular_offset=maximum_allowed_angular_offset,
379
+ )
380
+ is not None
381
+ )
382
+
296
383
  def test_create_solution(
297
384
  self,
298
385
  dynamics: list,
@@ -310,7 +397,7 @@ class TestSegment:
310
397
 
311
398
  assert segment_solution is not None
312
399
 
313
- def test_solve(
400
+ def test_solve_maneuver_segment(
314
401
  self,
315
402
  state: State,
316
403
  end_instant: Instant,
@@ -318,7 +405,7 @@ class TestSegment:
318
405
  instants: list[Instant],
319
406
  numerical_solver: NumericalSolver,
320
407
  ):
321
- solution = maneuver_segment.solve(state)
408
+ solution: Segment.Solution = maneuver_segment.solve(state)
322
409
 
323
410
  assert (
324
411
  pytest.approx(
@@ -400,3 +487,21 @@ class TestSegment:
400
487
  solution.get_dynamics_acceleration_contribution(
401
488
  solution.dynamics[0], state.get_frame()
402
489
  )
490
+
491
+ def test_solve_constant_local_orbital_frame_direction_maneuver_segment(
492
+ self,
493
+ state: State,
494
+ constant_local_orbital_frame_direction_maneuver_segment: Segment,
495
+ constant_local_orbital_frame_direction_maneuver_segment_with_maximum_allowed_angular_offset: Segment,
496
+ ):
497
+ solution: Segment.Solution = (
498
+ constant_local_orbital_frame_direction_maneuver_segment.solve(state)
499
+ )
500
+ assert solution is not None
501
+
502
+ solution_with_maximum_allowed_angular_offset: Segment.Solution = (
503
+ constant_local_orbital_frame_direction_maneuver_segment_with_maximum_allowed_angular_offset.solve(
504
+ state
505
+ )
506
+ )
507
+ assert solution_with_maximum_allowed_angular_offset is not None
@@ -1094,6 +1094,30 @@ class Segment:
1094
1094
  Segment: The coast segment.
1095
1095
  """
1096
1096
  @staticmethod
1097
+ def constant_local_orbital_frame_direction_maneuver(name: ostk.core.type.String, event_condition: typing.Any, thruster_dynamics: typing.Any, dynamics: list[...], numerical_solver: state.NumericalSolver, local_orbital_frame_factory: LocalOrbitalFrameFactory, maximum_allowed_angular_offset: ostk.physics.unit.Angle = ...) -> Segment:
1098
+ """
1099
+ Create a maneuvering segment that produces maneuvers with a constant direction in the local orbital frame.
1100
+
1101
+ The provided thruster dynamics are used to solve the segment at first. The maneuvers produced by this segement solution
1102
+ are then used to create a new thruster dynamics with a constant direction in the local orbital frame. This new thruster dynamics
1103
+ is then used to actually solve the segment.
1104
+
1105
+ If defined, a runtime error will be thrown if the maximum allowed angular offset between the original thruster dynamics
1106
+ and the mean thrust direction is violated.
1107
+
1108
+ Args:
1109
+ name (str): The name of the segment.
1110
+ event_condition (EventCondition): The event condition.
1111
+ thruster_dynamics (ThrusterDynamics): The thruster dynamics.
1112
+ dynamics (Dynamics): The dynamics.
1113
+ numerical_solver (NumericalSolver): The numerical solver.
1114
+ local_orbital_frame_factory (LocalOrbitalFrameFactory): The local orbital frame factory.
1115
+ maximum_allowed_angular_offset (Angle, optional): The maximum allowed angular offset to consider (if any). Defaults to Angle.undefined().
1116
+
1117
+ Returns:
1118
+ Segment: The maneuver segment.
1119
+ """
1120
+ @staticmethod
1097
1121
  def maneuver(name: ostk.core.type.String, event_condition: typing.Any, thruster_dynamics: typing.Any, dynamics: list[...], numerical_solver: state.NumericalSolver) -> Segment:
1098
1122
  """
1099
1123
  Create a maneuver segment.