open-space-toolkit-astrodynamics 16.8.0__py313-none-manylinux2014_aarch64.whl → 16.9.0__py313-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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: open-space-toolkit-astrodynamics
3
- Version: 16.8.0
3
+ Version: 16.9.0
4
4
  Summary: Orbit, attitude, access.
5
5
  Author: Open Space Collective
6
6
  Author-email: contact@open-space-collective.org
@@ -1,18 +1,18 @@
1
1
  ostk/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
2
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-313-aarch64-linux-gnu.so,sha256=UloSW5i0-b6rlxGKjo8_9ONH3NhjTxi9sXzDLk2JXhA,2664832
2
+ ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-313-aarch64-linux-gnu.so,sha256=A8Qv3TCkK7bZUxzT1ZaSXgGslyBF8RNv4-FArg6smQo,2693512
3
3
  ostk/astrodynamics/__init__.py,sha256=3gWyqFIbhAfcdeMhmfBPQPlPQTmaOzm-6flkJe745Zk,251
4
- ostk/astrodynamics/__init__.pyi,sha256=l39uJ41QUoFdQXjRXzara_EPHNnf_9Q086T1-Jv-9X4,32224
4
+ ostk/astrodynamics/__init__.pyi,sha256=wUzGQSogTjxlQuTfsfV5CkW1PHTiA2zBY0O7_nd8bGA,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
8
8
  ostk/astrodynamics/dataframe.py,sha256=v-36rBHKdf7kQw1jdvJWrvkR-ZjW5DKRchtTEJWhdxk,25012
9
9
  ostk/astrodynamics/display.py,sha256=D9PrX1G-XmXk2j3Jl7Na7q52PTc4WKWB1-oRFJPT6w0,8606
10
- ostk/astrodynamics/dynamics.pyi,sha256=gZ95KoGex4SB-1z6yMrngkZN1Ir9X6bEmrZgdLxq1ZE,13270
10
+ ostk/astrodynamics/dynamics.pyi,sha256=12t5esJJ18YmCeD2-8Glcb4JPOs6vl2c6QeUaDCel9E,13308
11
11
  ostk/astrodynamics/eclipse.pyi,sha256=Q8fzdukA7fyxbUM3wjEXAHukT-Ovl4FM00NbjsMPnUM,3211
12
12
  ostk/astrodynamics/estimator.pyi,sha256=MnahWzp8aACxrNKWlYRsgQr5zpBxogNr-yPm7hJob5k,14000
13
13
  ostk/astrodynamics/event_condition.pyi,sha256=2c_1Sq7tkYKeAA_aRWBi43KDQXXxu6EMSmUpEWz_Fa4,45814
14
- ostk/astrodynamics/guidance_law.pyi,sha256=rVmbpV2Y5FsIXejaInxINS67nVHmTIxVEkhaDIn17SA,12171
15
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=r2QRZUN_s_w90Bu6E5_BWFfIdnfdGlU6w3TEnue8xSU,4027856
14
+ ostk/astrodynamics/guidance_law.pyi,sha256=Xtqneu5PLpidOoSH4YIAY3D8rWZ0QH_ESqEdfbiTBVQ,16936
15
+ ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.16,sha256=64f3DTQELHsOR76LlOG8924thWDsmLMbodCKz2JgDQM,4036632
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
@@ -21,7 +21,7 @@ ostk/astrodynamics/conjunction/message/__init__.pyi,sha256=5H__sg_QUx7ybf9jtVWvX
21
21
  ostk/astrodynamics/conjunction/message/ccsds.pyi,sha256=1Peto19hRqlD7KHf1cyLP3CT4OAKzwtemqvO6_4FZ0g,28162
22
22
  ostk/astrodynamics/data/__init__.pyi,sha256=4l_mfVbnU_L7wImwgTCe8fVI81gK_tUmd0z7BY9lLi8,81
23
23
  ostk/astrodynamics/data/provider.pyi,sha256=O4Lg9FBq9itufgATnic5SMg90pn8vJJJMUdNcWP72NI,1492
24
- ostk/astrodynamics/flight/__init__.pyi,sha256=OnR2OxLLvmVSp18nLzM7R_QHoJvoikIaz2qiIZgH0bA,23606
24
+ ostk/astrodynamics/flight/__init__.pyi,sha256=VL6OTS5uFz9xEVQ-enHz5FinFTS283nBAztEn4uGNJw,25865
25
25
  ostk/astrodynamics/flight/system.pyi,sha256=WVxy6Oe4q3C81c0AOxSwAmnwUHcpXO7JCEvESjs2g4A,10508
26
26
  ostk/astrodynamics/flight/profile/__init__.pyi,sha256=WBTG17V59UwD-X1r6TOrXT_rA3WjKY-2ML1cWfji_4g,3688
27
27
  ostk/astrodynamics/flight/profile/model.pyi,sha256=wwnVaTjTytxPqKyNo1tpdZRedabQlIHgyxfT0nivO5M,7777
@@ -56,7 +56,7 @@ ostk/astrodynamics/test/dynamics/test_dynamics.py,sha256=4VHL3Khbh8qTBKZw7m-4gSU
56
56
  ostk/astrodynamics/test/dynamics/test_position_derivative.py,sha256=JRuq-oRrJfisbN75OnE-ldm6TeYcIc0qYaGVMflbn2g,1610
57
57
  ostk/astrodynamics/test/dynamics/test_tabulated.py,sha256=qFHA7nmcvJtWucpCOYHAFmNRtImhTGlAZ0CHGEadeX8,4029
58
58
  ostk/astrodynamics/test/dynamics/test_third_body_gravity.py,sha256=7xBOKbVio-zZ2-QQaje152cP38n_ALAaR5x6QIdBmXE,1879
59
- ostk/astrodynamics/test/dynamics/test_thruster.py,sha256=cCJtKWuNcRHmcreCv7BajwuJnHCat9m88OHh_o0cB7w,4383
59
+ ostk/astrodynamics/test/dynamics/test_thruster.py,sha256=MJicjTLJCiULNgBk10ZVrZF5Bi3nRb3UZPWVcrpnxz8,4746
60
60
  ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv,sha256=jJWOMJcQ83Vy1Wrb9PTh3ffOdoDF98kk6w5b-_E0uWg,47419
61
61
  ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv,sha256=f4x-zmxZADxD_doAbACt24k36VSTp3rSIJiSzuSroKs,8377
62
62
  ostk/astrodynamics/test/eclipse/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
@@ -71,7 +71,7 @@ ostk/astrodynamics/test/event_condition/test_instant_condition.py,sha256=Qr5dN6n
71
71
  ostk/astrodynamics/test/event_condition/test_logical_condition.py,sha256=09h5TYWtwGt4NQW3k_tziiVs0Q2981rNII9wyKg7p2Q,3321
72
72
  ostk/astrodynamics/test/event_condition/test_real_condition.py,sha256=tle6HVzMFMIIkfRY7CuaA0mPtw3riJBG_JQkc1L0dpk,1374
73
73
  ostk/astrodynamics/test/flight/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
74
- ostk/astrodynamics/test/flight/test_maneuver.py,sha256=HonAvD9qtax72lizoDLR6ELI-poC3LWk_umg-WYygYA,5388
74
+ ostk/astrodynamics/test/flight/test_maneuver.py,sha256=7ZiMHrmGfSopjXm2Z3L1fXvDCJ646zVRcVyTk8f7rxY,6905
75
75
  ostk/astrodynamics/test/flight/test_profile.py,sha256=U1l7pbFYrUjjxYBotQ3ujrvBxnBcyWgTrKwVHHClStc,8602
76
76
  ostk/astrodynamics/test/flight/test_system.py,sha256=5kJCULHdpkwAC7i6xLV7vdJnGfOdrOuhi0G22p_L160,1224
77
77
  ostk/astrodynamics/test/flight/profile/model/test_tabulated_profile.py,sha256=NZVa9dXzp4UDmohAlBaYeY594CPbqIv5j3VSEzlbYHk,3168
@@ -79,8 +79,9 @@ ostk/astrodynamics/test/flight/system/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5n
79
79
  ostk/astrodynamics/test/flight/system/test_propulsion_system.py,sha256=SoxOt-fjHvs_86f6Xb3jAsHDOShJ6Puz7eO5wrqibaM,1835
80
80
  ostk/astrodynamics/test/flight/system/test_satellite_system.py,sha256=dCFcl2RKiLxDKLI0sytPBXr3vHOFQIVNNCzpw5WzlAA,2437
81
81
  ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py,sha256=QCk70dPcptvdpWQOxtYR0-iGnlbtOIx2V6oO0UhYIQ0,2426
82
- ostk/astrodynamics/test/guidance_law/test_constant_thrust.py,sha256=YpMkMvat4YnE1Fyy5vZ1Sf3VvbvDaC_UvbIKNNCG_B4,2635
82
+ ostk/astrodynamics/test/guidance_law/test_constant_thrust.py,sha256=y7VaKuJ8oCVEDeZL62oWepcmmYKG-9Za2Ebjn8lVR7Y,5381
83
83
  ostk/astrodynamics/test/guidance_law/test_guidance_law.py,sha256=4ElWLS9ovnYBcj5HwJI3a6R9wrSLxyb_VMvxcCLDrKE,1560
84
+ ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py,sha256=qqIahgqFNhBjAe6RrztR0mR1XQ7ztXO-q7_pkpiKLWo,5076
84
85
  ostk/astrodynamics/test/guidance_law/test_qlaw.py,sha256=kZaBZeU_J8Qk2nFnnpSuu-2NXT_13g3vLamG--eZfeU,3634
85
86
  ostk/astrodynamics/test/solvers/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
86
87
  ostk/astrodynamics/test/solvers/test_finite_difference_solver.py,sha256=0tcarIg2HTpcdzItWlL5Kd6q2nHcDBA8ZE0D3ugUN_s,5953
@@ -92,7 +93,7 @@ ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py,sha256=s1
92
93
  ostk/astrodynamics/test/trajectory/test_model.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
93
94
  ostk/astrodynamics/test/trajectory/test_orbit.py,sha256=EwqQejhcSqYhKPCAkjxyBLgEc82fOhrh1zrJ86RGO9Q,7011
94
95
  ostk/astrodynamics/test/trajectory/test_propagator.py,sha256=ALjMvzndO9r6dVVXllk0iqu61tEoqu2j25MtzEitDIQ,14076
95
- ostk/astrodynamics/test/trajectory/test_segment.py,sha256=aXdF5CpNLwN_hKRU5sG3ID3hJR-NZ1_kgtNA-xHMBkM,12223
96
+ ostk/astrodynamics/test/trajectory/test_segment.py,sha256=u4AdttP27r2usLaaBthoSivofxKUyoQdH7dAeKEc9-E,16046
96
97
  ostk/astrodynamics/test/trajectory/test_sequence.py,sha256=4piYpMZ9C_eCXxaOcdz2We67wZxhsNBnETgKwvq6dl0,15083
97
98
  ostk/astrodynamics/test/trajectory/test_state.py,sha256=JO-SnUCRZ6sfVNwB5soT5c1f8c5DXuFN5lHZkiiTcYc,18119
98
99
  ostk/astrodynamics/test/trajectory/test_state_builder.py,sha256=LpCCYdCaLu_D2npm9wgA2sMlcekrtbRqP-afe5IE5L4,4871
@@ -128,7 +129,7 @@ ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quatern
128
129
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_acceleration.py,sha256=1p37_FYN85d9SrOqO3iCkNecovJJayhnFeZ4QCOw2ao,3528
129
130
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py,sha256=XvHdk1KjacTwtkgx2jUAc9I9N3nvjPDv03FAanpv8jQ,2702
130
131
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py,sha256=-kd5TZO5TICihbkqDTew2i_tDpggdpe3Yf23046FATM,3057
131
- ostk/astrodynamics/trajectory/__init__.pyi,sha256=zcowdlUplPRFZovsYG2Tj2Scd_0ZnIhUtYirtq6UUc8,76551
132
+ ostk/astrodynamics/trajectory/__init__.pyi,sha256=W7m5x95ofyR-GnBdVVrBf4uQQ9_GfXXzheDtlXq9ZgA,78464
132
133
  ostk/astrodynamics/trajectory/model.pyi,sha256=SIneZx3pQ6XAo6iBccr4B1OsATbs1PEsCngtaTvja00,10473
133
134
  ostk/astrodynamics/trajectory/orbit/__init__.pyi,sha256=wbuRK7Yp_NYBr3ta5-NpnJYMX4baUl7yIlUWhaRlR4o,12899
134
135
  ostk/astrodynamics/trajectory/orbit/message/__init__.pyi,sha256=-GNBlYPrsjelhKxWorYQYhIWzFsibiIQNZvMXjhRpfM,77
@@ -139,8 +140,8 @@ ostk/astrodynamics/trajectory/orbit/model/kepler.pyi,sha256=KL-FwPbG4OUkPjhQb1Pa
139
140
  ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi,sha256=OhFzoPPQHlYy7m3LiZ8TXF89M4uBTfNk6tGsBEp0sjI,14235
140
141
  ostk/astrodynamics/trajectory/state/__init__.pyi,sha256=bq__Fii35czVrTeNxc9eQVjXdqwbbQxUdNQWK3vLrMo,17649
141
142
  ostk/astrodynamics/trajectory/state/coordinate_subset.pyi,sha256=kYMfMwEjCqO1NepMYFT4QS6kIPBkVL6sGCLeLbogcMw,10176
142
- open_space_toolkit_astrodynamics-16.8.0.dist-info/METADATA,sha256=EfkYJE8PmXGgbf8PpG_rAkZgnWlv8XFvMDYSXiMxVV8,1913
143
- open_space_toolkit_astrodynamics-16.8.0.dist-info/WHEEL,sha256=SoYeZtqoiLCL_G4rzH07IDMwRvSqG334rWZuI48h-Rk,111
144
- open_space_toolkit_astrodynamics-16.8.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
145
- open_space_toolkit_astrodynamics-16.8.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
146
- open_space_toolkit_astrodynamics-16.8.0.dist-info/RECORD,,
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=SoYeZtqoiLCL_G4rzH07IDMwRvSqG334rWZuI48h-Rk,111
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,,
@@ -5,8 +5,8 @@ from ostk.astrodynamics.trajectory import State as PyState
5
5
  from ostk import core as OpenSpaceToolkitCorePy
6
6
  from ostk.core import container
7
7
  from ostk.core import filesystem
8
- from ostk.core import type
9
8
  import ostk.core.type
9
+ from ostk.core import type
10
10
  from ostk import io as OpenSpaceToolkitIOPy
11
11
  from ostk.io import URL
12
12
  from ostk.io import ip
@@ -14,18 +14,18 @@ from ostk import mathematics as OpenSpaceToolkitMathematicsPy
14
14
  from ostk.mathematics import curve_fitting
15
15
  from ostk.mathematics import geometry
16
16
  from ostk.mathematics import object
17
- from ostk import physics as OpenSpaceToolkitPhysicsPy
18
17
  import ostk.physics
18
+ from ostk import physics as OpenSpaceToolkitPhysicsPy
19
19
  from ostk.physics import Environment
20
20
  from ostk.physics import Manager
21
21
  from ostk.physics import Unit
22
- import ostk.physics.coordinate
23
22
  from ostk.physics import coordinate
23
+ 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
- from ostk.physics import time
28
27
  import ostk.physics.time
28
+ from ostk.physics import time
29
29
  from ostk.physics import unit
30
30
  import ostk.physics.unit
31
31
  import typing
@@ -270,7 +270,7 @@ class Thruster(ostk.astrodynamics.Dynamics):
270
270
  Args:
271
271
  satellite_system (SatelliteSystem): The satellite system.
272
272
  guidance_law (GuidanceLaw): The guidance law used to compute the acceleration vector.
273
- name (str): The name of the thruster.
273
+ name (str, optional): The name of the thruster. Defaults to String.empty().
274
274
  """
275
275
  def __repr__(self) -> str:
276
276
  ...
@@ -84,6 +84,16 @@ class Maneuver:
84
84
  Returns:
85
85
  float: The delta-v value (m/s).
86
86
  """
87
+ def calculate_mean_thrust_direction_and_maximum_angular_offset(self, local_orbital_frame_factory: ostk.astrodynamics.trajectory.LocalOrbitalFrameFactory) -> tuple[ostk.astrodynamics.trajectory.LocalOrbitalFrameDirection, ostk.physics.unit.Angle]:
88
+ """
89
+ Calculate the mean thrust direction in the Local Orbital Frame and its maximum angular offset w.r.t. the maneuver's thrust acceleration directions.
90
+
91
+ Args:
92
+ local_orbital_frame_factory (LocalOrbitalFrameFactory): The local orbital frame factory.
93
+
94
+ Returns:
95
+ Tuple[LocalOrbitalFrameDirection, Angle]: The mean thrust direction and its maximum angular offset.
96
+ """
87
97
  def get_interval(self) -> ostk.physics.time.Interval:
88
98
  """
89
99
  Get the interval of the maneuver.
@@ -105,6 +115,24 @@ class Maneuver:
105
115
  Returns:
106
116
  bool: True if the maneuver is defined, False otherwise. (Always returns true).
107
117
  """
118
+ def to_constant_local_orbital_frame_direction_maneuver(self, local_orbital_frame_factory: ostk.astrodynamics.trajectory.LocalOrbitalFrameFactory, maximum_allowed_angular_offset: ostk.physics.unit.Angle = ...) -> Maneuver:
119
+ """
120
+ Create a maneuver with a constant thrust acceleration direction in the Local Orbital Frame.
121
+
122
+ The new Maneuver contains the same states as the original Maneuver, but the thrust acceleration direction is
123
+ constant in the Local Orbital Frame. Said direction is the mean direction of the thrust acceleration directions
124
+ in the Local Orbital Frame of the original Maneuver. The thrust acceleration magnitude profile is the same as the original.
125
+
126
+ If defined, a runtime error will be thrown if the maximum allowed angular offset between the original thrust acceleration direction
127
+ and the mean thrust direction is violated.
128
+
129
+ Args:
130
+ local_orbital_frame_factory (LocalOrbitalFrameFactory): The local orbital frame factory.
131
+ maximum_allowed_angular_offset (Angle, optional): The maximum allowed angular offset to consider (if any). Defaults to Undefined.
132
+
133
+ Returns:
134
+ Maneuver: The constant local orbital frame direction maneuver.
135
+ """
108
136
  def to_tabulated_dynamics(self, frame: ostk.physics.coordinate.Frame = ..., interpolation_type: ostk.mathematics.curve_fitting.Interpolator.Type = ...) -> ...:
109
137
  """
110
138
  Convert the maneuver to tabulated dynamics.
@@ -200,7 +228,7 @@ class Profile:
200
228
  Constructor.
201
229
 
202
230
  Args:
203
- orientation_profile (list[Tuple[Instant, Vector3d]]): The orientation profile.
231
+ orientation_profile (list[tuple[Instant, Vector3d]]): The orientation profile.
204
232
  axis (Profile.Axis): The axis.
205
233
  anti_direction (bool): True if the direction is flipped, False otherwise. Defaults to False.
206
234
  """
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
  import numpy
3
3
  import ostk.astrodynamics
4
+ import ostk.astrodynamics.flight
4
5
  import ostk.astrodynamics.trajectory
5
6
  import ostk.astrodynamics.trajectory.orbit.model.kepler
6
7
  import ostk.core.type
@@ -8,26 +9,43 @@ import ostk.physics.coordinate
8
9
  import ostk.physics.time
9
10
  import ostk.physics.unit
10
11
  import typing
11
- __all__ = ['ConstantThrust', 'QLaw']
12
+ __all__ = ['ConstantThrust', 'HeterogeneousGuidanceLaw', 'QLaw']
12
13
  class ConstantThrust(ostk.astrodynamics.GuidanceLaw):
13
14
  """
14
15
 
15
- Constant Thrust, Constant Direction dynamics.
16
+ Constant Thrust, Constant Direction guidance law.
16
17
 
17
18
 
18
19
  """
19
20
  @staticmethod
21
+ def from_maneuver(maneuver: ostk.astrodynamics.flight.Maneuver, local_orbital_frame_factory: ostk.astrodynamics.trajectory.LocalOrbitalFrameFactory, maximum_allowed_angular_offset: ostk.physics.unit.Angle = ...) -> ConstantThrust:
22
+ """
23
+ Create a constant thrust guidance law from a maneuver.
24
+
25
+ The local orbital frame maneuver's mean thrust direction is calculated and used to create a
26
+ constant thrust guidance law in said direction.
27
+
28
+ If defined, a runtime error will be thrown if the maximum allowed angular offset between the original thrust acceleration
29
+ direction and the mean thrust direction is violated.
30
+
31
+ Args:
32
+ maneuver (Maneuver): The maneuver.
33
+ local_orbital_frame_factory (LocalOrbitalFrameFactory): The local orbital frame factory.
34
+ maximum_allowed_angular_offset (Angle, optional): The maximum allowed angular offset to consider (if any). Defaults to Undefined.
35
+
36
+ Returns:
37
+ ConstantThrust: The constant thrust guidance law.
38
+ """
39
+ @staticmethod
20
40
  def intrack(velocity_direction: bool = True) -> ConstantThrust:
21
41
  """
22
- Create a constant thrust in the in-track direction.
42
+ Create a constant thrust guidance law in the in-track direction.
23
43
 
24
44
  Args:
25
- satellite_system (SatelliteSystem): The satellite system.
26
- velocity_direction (bool, optional): If True, the thrust is applied in the velocity direction. Otherwise, it is applied in the opposite direction.
27
- frame (Frame, optional): The reference frame.
45
+ velocity_direction (bool, optional): If True, the thrust is applied in the velocity direction. Otherwise, it is applied in the opposite direction. Defaults to True.
28
46
 
29
47
  Returns:
30
- ConstantThrust: The constant thrust.
48
+ ConstantThrust: The constant thrust guidance law in the in-track direction.
31
49
  """
32
50
  def __init__(self, thrust_direction: ostk.astrodynamics.trajectory.LocalOrbitalFrameDirection) -> None:
33
51
  """
@@ -61,6 +79,63 @@ class ConstantThrust(ostk.astrodynamics.GuidanceLaw):
61
79
  Returns:
62
80
  LocalOrbitalFrameDirection: The local thrust direction.
63
81
  """
82
+ class HeterogeneousGuidanceLaw(ostk.astrodynamics.GuidanceLaw):
83
+ """
84
+
85
+ A guidance law that sequences multiple guidance laws over specific time intervals.
86
+
87
+ At each point in time, the applicable guidance law is selected and used to calculate
88
+ the thrust acceleration. Guidance laws don't need to be contiguous, and can be added
89
+ in any order. If the instant does not fall within any of the intervals, the thrust
90
+ acceleration is zero. The guidance law intervals must not intersect each other.
91
+
92
+ """
93
+ def __init__(self, guidance_laws_with_intervals: list[tuple[ostk.astrodynamics.GuidanceLaw, ostk.physics.time.Interval]] = []) -> None:
94
+ """
95
+ Constructor.
96
+
97
+ Args:
98
+ guidance_laws_with_intervals (list[tuple[GuidanceLaw, Interval]], optional): Array of tuples containing the guidance laws and their corresponding intervals. Defaults to empty array.
99
+
100
+ Raises:
101
+ RuntimeError: If any interval is undefined, if the guidance law is null or if the interval intersects with an existing interval.
102
+ """
103
+ def __repr__(self) -> str:
104
+ ...
105
+ def __str__(self) -> str:
106
+ ...
107
+ def add_guidance_law(self, guidance_law: ostk.astrodynamics.GuidanceLaw, interval: ostk.physics.time.Interval) -> None:
108
+ """
109
+ Add a guidance law with its corresponding interval.
110
+
111
+ Args:
112
+ guidance_law (GuidanceLaw): The guidance law to add.
113
+ interval (Interval): The interval during which the guidance law is active.
114
+
115
+ Raises:
116
+ RuntimeError: If the interval is undefined, if the guidance law is null or if the interval intersects with an existing interval.
117
+ """
118
+ def calculate_thrust_acceleration_at(self, instant: ostk.physics.time.Instant, position_coordinates: numpy.ndarray[numpy.float64[3, 1]], velocity_coordinates: numpy.ndarray[numpy.float64[3, 1]], thrust_acceleration: ostk.core.type.Real, output_frame: ostk.physics.coordinate.Frame) -> numpy.ndarray[numpy.float64[3, 1]]:
119
+ """
120
+ Calculate thrust acceleration at a given instant and state.
121
+
122
+ Args:
123
+ instant (Instant): The instant.
124
+ position_coordinates (numpy.ndarray): The position coordinates.
125
+ velocity_coordinates (numpy.ndarray): The velocity coordinates.
126
+ thrust_acceleration (float): The thrust acceleration magnitude.
127
+ output_frame (Frame): The frame in which the acceleration is expressed.
128
+
129
+ Returns:
130
+ numpy.ndarray: The acceleration vector at the provided coordinates.
131
+ """
132
+ def get_guidance_laws_with_intervals(self) -> list[tuple[ostk.astrodynamics.GuidanceLaw, ostk.physics.time.Interval]]:
133
+ """
134
+ Get the guidance laws with their corresponding intervals.
135
+
136
+ Returns:
137
+ list[tuple[GuidanceLaw, Interval]]: Array of tuples containing the guidance laws and their corresponding intervals.
138
+ """
64
139
  class QLaw(ostk.astrodynamics.GuidanceLaw):
65
140
  """
66
141
 
@@ -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
+ )
@@ -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.