open-space-toolkit-astrodynamics 12.2.0__py310-none-manylinux2014_aarch64.whl → 12.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: open-space-toolkit-astrodynamics
3
- Version: 12.2.0
3
+ Version: 12.3.0
4
4
  Summary: Orbit, attitude, access.
5
5
  Author: Open Space Collective
6
6
  Author-email: contact@open-space-collective.org
@@ -13,8 +13,8 @@ Classifier: Operating System :: POSIX :: Linux
13
13
  Description-Content-Type: text/markdown
14
14
  Requires-Dist: open-space-toolkit-core ~=4.1
15
15
  Requires-Dist: open-space-toolkit-io ~=4.0
16
- Requires-Dist: open-space-toolkit-mathematics ~=4.2
17
- Requires-Dist: open-space-toolkit-physics ~=10.1
16
+ Requires-Dist: open-space-toolkit-mathematics ~=4.3
17
+ Requires-Dist: open-space-toolkit-physics ~=11.1
18
18
 
19
19
  # Open Space Toolkit ▸ Astrodynamics
20
20
 
@@ -1,21 +1,21 @@
1
1
  ostk/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
2
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-310-aarch64-linux-gnu.so,sha256=TvU42bb_3tvKKDSuP7bOagbEG-iAv4aca_1DVgaa99M,2176880
2
+ ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-310-aarch64-linux-gnu.so,sha256=V9SEq9x3O3okv9D8eCZxaen2jkKWBQ6oX3dy4zGNr9U,2197376
3
3
  ostk/astrodynamics/__init__.py,sha256=3gWyqFIbhAfcdeMhmfBPQPlPQTmaOzm-6flkJe745Zk,251
4
- ostk/astrodynamics/converters.py,sha256=Ld-DLTaqg7pBkIJAcYeoaA-sDOQbR_uksRR-lg9dfXo,3278
5
- ostk/astrodynamics/dataframe.py,sha256=njimcfT2cvnSOp9eBR1Ej-LuYpXbVieAVEBk2tYW4Vg,18761
6
- ostk/astrodynamics/display.py,sha256=KiEGsjNftQfoUe-HfZeYpQQQ4TkaEnxtEl6p5_LX5f0,6303
7
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.12,sha256=YADwNfzSSpqxeXNm9XeaD45dsKpO4_-BQqig8So9nbM,3371168
8
- ostk/astrodynamics/utilities.py,sha256=f0ySFhD6X9jhAZW1hLnbLVEOuy3QgVq2C6k4m8MFGYA,6963
4
+ ostk/astrodynamics/converters.py,sha256=luPh30qMp9bzEkN7hUccmxlLf7zRp_AzqmBe8IUjPhU,3314
5
+ ostk/astrodynamics/dataframe.py,sha256=9fXRk7sJl_OrBTCjZC_TFx6JMPE7IDGqv2JgEmGCdgM,18775
6
+ ostk/astrodynamics/display.py,sha256=LZESZgx2wlrFO4cwAGMb3VPJfdtcjNgCgKFrqot0NYU,6339
7
+ ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.12,sha256=u1_p4yAUQZJjS-HU2_awEzC2_IH4kI8JTgXgQ0EKv3o,3367536
8
+ ostk/astrodynamics/utilities.py,sha256=mlKL3WrOASrY_pLt7bzUz0XZT7jagzLOZjMOIqjbQ1Y,6999
9
9
  ostk/astrodynamics/viewer.py,sha256=8UxqsvU5wui7gPUwxXaKcIB-509NJiih9Df3mQgXyXY,11776
10
10
  ostk/astrodynamics/pytrajectory/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
11
- ostk/astrodynamics/pytrajectory/pystate.py,sha256=IoR4AIqZe7KuvxrNbnpPtQLC-bY1RPxUlSB8TbfmANg,6681
11
+ ostk/astrodynamics/pytrajectory/pystate.py,sha256=zyQon4c-bemdl_PBFWrXV_VVchpvbkEBRod8ospAuFA,7867
12
12
  ostk/astrodynamics/test/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
13
13
  ostk/astrodynamics/test/conftest.py,sha256=stmQOt7UXjBlXKJzNN6RkS2miv1xXSOX-YNpFhaHTqI,2771
14
14
  ostk/astrodynamics/test/test_access.py,sha256=MCBsUPtuVm7NgHZR0z0DpWnPZ_qBu4aRhLI2PnRNUYs,3940
15
15
  ostk/astrodynamics/test/test_converters.py,sha256=mFpDD0YM8o356lj91GGIwYdMk2ut6xZFV3uYcgZepMY,8744
16
16
  ostk/astrodynamics/test/test_dataframe.py,sha256=IJMOODzTVYtiPxekmltH6lOMArPXbHwSEAl-jg0Ab2g,28414
17
17
  ostk/astrodynamics/test/test_display.py,sha256=Ykvw2rUWWw8jsX0Suy11X27bHjwm_ira__xj0ZyHHxw,3672
18
- ostk/astrodynamics/test/test_event_condition.py,sha256=mhMTH7wAoYFWRYt_8l2d1vjNPrFhVjMAEET4INLCVXY,1472
18
+ ostk/astrodynamics/test/test_event_condition.py,sha256=RSO2L5x3D8grWW4t7WK-aTMl7vFOMpj6F3ByN3EaovY,2175
19
19
  ostk/astrodynamics/test/test_import.py,sha256=py_hALBR0IYuUzv9dfgQZzrrLHJIpnyKvt3Oi1XBqCg,1251
20
20
  ostk/astrodynamics/test/test_root_solver.py,sha256=hQ8O6g-WP49gZH_H3Rdufv0F0gQorpzJyIcjBGGUQ34,1831
21
21
  ostk/astrodynamics/test/test_trajectory.py,sha256=0sgGPkMFy-u-33z6SF-sTvaBb_NU7OvaeMTVF6wevfY,3148
@@ -45,7 +45,7 @@ ostk/astrodynamics/test/event_condition/test_logical_condition.py,sha256=09h5TYW
45
45
  ostk/astrodynamics/test/event_condition/test_real_condition.py,sha256=tle6HVzMFMIIkfRY7CuaA0mPtw3riJBG_JQkc1L0dpk,1374
46
46
  ostk/astrodynamics/test/flight/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
47
47
  ostk/astrodynamics/test/flight/test_maneuver.py,sha256=6LhnNhaiwId-IveUraHnhx8dYxeOgzntP_WPtv9GFjQ,6197
48
- ostk/astrodynamics/test/flight/test_profile.py,sha256=-MI7mtkvJdq7eFr6terpD-2na306VRzYBTJ36qigjB4,7518
48
+ ostk/astrodynamics/test/flight/test_profile.py,sha256=mV5ru4odPTxg3nkiMjRbDzzXK3hkAyOsv_wLYiD60ho,7815
49
49
  ostk/astrodynamics/test/flight/test_system.py,sha256=MVaE7lJYisH4vmJPD-G-Hw4wNj-Xe8yMksgu8IXCLvg,1322
50
50
  ostk/astrodynamics/test/flight/system/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
51
51
  ostk/astrodynamics/test/flight/system/test_propulsion_system.py,sha256=jLUC74YjIQz7APr-kQcdYRYiKm8lfequjosptG29kmI,1964
@@ -62,9 +62,9 @@ ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py,sha256=hX
62
62
  ostk/astrodynamics/test/trajectory/test_model.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
63
63
  ostk/astrodynamics/test/trajectory/test_orbit.py,sha256=iKVgScEo1osMIXjON4ToURCgp2LDZV18fKQuy49RktI,6484
64
64
  ostk/astrodynamics/test/trajectory/test_propagator.py,sha256=dG31S5FY0NjJ5bnisqJ_HdL7amj_AZAWWLiOmdHfJAc,14233
65
- ostk/astrodynamics/test/trajectory/test_segment.py,sha256=ACVMYvjlTyCQHZ2-hFEurK08-LQxt6VlcGTMOrgDHaE,9333
66
- ostk/astrodynamics/test/trajectory/test_sequence.py,sha256=gswWqEBTYvbi5TQjF0DNPkAj5COo9Nb9IK7TdwJ_5C0,12882
67
- ostk/astrodynamics/test/trajectory/test_state.py,sha256=rgJ4lHgp71axKdvdGSkk0sQbQ9i00gBi8ewLxXCojmo,13316
65
+ ostk/astrodynamics/test/trajectory/test_segment.py,sha256=4XacBFrX8ZkS-OLDWyIs7kqDa2vG9UQ5guMXVl5ecJk,12275
66
+ ostk/astrodynamics/test/trajectory/test_sequence.py,sha256=iCNDDqEGoo4WZIOOov0qKGzrxjg7yWVIk8ajfKMhgI4,14503
67
+ ostk/astrodynamics/test/trajectory/test_state.py,sha256=8B7KVlxZZPzwwsfCrTudeSkTTC2wAjXcglB0PZdjD1Y,15688
68
68
  ostk/astrodynamics/test/trajectory/test_state_builder.py,sha256=PSlXtmGURCrlf0w50tn3dEBRo6p0x4NmIF9gJsNOR3k,4782
69
69
  ostk/astrodynamics/test/trajectory/orbit/__init__.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
70
70
  ostk/astrodynamics/test/trajectory/orbit/test_model.py,sha256=epnVn2PwdQkUDZ1msqBRO5nEZIOUBIq-IfK3IlNPijE,21
@@ -93,8 +93,8 @@ ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity
93
93
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py,sha256=UEu9ApzQLmT87eeISw6_gcHTlX-4b2scIvHz-uE1p_c,393
94
94
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py,sha256=XvHdk1KjacTwtkgx2jUAc9I9N3nvjPDv03FAanpv8jQ,2702
95
95
  ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py,sha256=-kd5TZO5TICihbkqDTew2i_tDpggdpe3Yf23046FATM,3057
96
- open_space_toolkit_astrodynamics-12.2.0.dist-info/METADATA,sha256=ucBrKvH3LneOcECxN2_fL-UgEpSRSXsYkC0N4H_vxew,1917
97
- open_space_toolkit_astrodynamics-12.2.0.dist-info/WHEEL,sha256=qqN5URp-VURm8LPVcwSxCMMwBbCqD2CU32siGCKjjV0,111
98
- open_space_toolkit_astrodynamics-12.2.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
99
- open_space_toolkit_astrodynamics-12.2.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
- open_space_toolkit_astrodynamics-12.2.0.dist-info/RECORD,,
96
+ open_space_toolkit_astrodynamics-12.3.0.dist-info/METADATA,sha256=bYMlUhTMfTMNNSNsQ93rzwyiUxnwRSgn3mhzrFy6dKw,1917
97
+ open_space_toolkit_astrodynamics-12.3.0.dist-info/WHEEL,sha256=qqN5URp-VURm8LPVcwSxCMMwBbCqD2CU32siGCKjjV0,111
98
+ open_space_toolkit_astrodynamics-12.3.0.dist-info/top_level.txt,sha256=zOR18699uDYnafgarhL8WU_LmTZY_5NVqutv-flp_x4,5
99
+ open_space_toolkit_astrodynamics-12.3.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
+ open_space_toolkit_astrodynamics-12.3.0.dist-info/RECORD,,
@@ -1,5 +1,7 @@
1
1
  # Apache License 2.0
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import re
4
6
 
5
7
  from datetime import datetime, timezone
@@ -1,4 +1,6 @@
1
- # Copyright © Loft Orbital Solutions Inc.
1
+ # Apache License 2.0
2
+
3
+ from __future__ import annotations
2
4
 
3
5
  import numpy as np
4
6
 
@@ -1,5 +1,7 @@
1
1
  # Apache License 2.0
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from typing import Any
4
6
 
5
7
  import pandas as pd
@@ -1,7 +1,8 @@
1
1
  # Apache License 2.0
2
2
 
3
-
4
3
  # Python-only State functionality
4
+ import re
5
+
5
6
  import numpy as np
6
7
 
7
8
  from ostk.physics.coordinate import Frame
@@ -17,6 +18,8 @@ from ostk.astrodynamics.trajectory.state.coordinate_subset import (
17
18
  AngularVelocity,
18
19
  )
19
20
 
21
+ CANONICAL_FORMAT: str = r"(r|v)_(.*?)_(x|y|z)"
22
+
20
23
 
21
24
  @staticmethod
22
25
  def custom_class_generator(frame: Frame, coordinate_subsets: list) -> type:
@@ -45,14 +48,17 @@ def custom_class_generator(frame: Frame, coordinate_subsets: list) -> type:
45
48
  def from_dict(data: dict) -> State:
46
49
  """
47
50
  Create a State from a dictionary.
51
+
52
+ Note: Implicit assumption that ECEF = ITRF, and ECI = GCRF.
53
+
48
54
  The dictionary must contain the following:
49
55
  - 'timestamp': The timestamp of the state.
50
- - 'rx'/'x_eci'/'x_ecef': The x-coordinate of the position.
51
- - 'ry'/'y_eci'/'y_ecef': The y-coordinate of the position.
52
- - 'rz'/'z_eci'/'z_ecef': The z-coordinate of the position.
53
- - 'vx'/'vx_eci'/'vx_ecef': The x-coordinate of the velocity.
54
- - 'vy'/'vy_eci'/'vy_ecef': The y-coordinate of the velocity.
55
- - 'vz'/'vz_eci'/'vz_ecef': The z-coordinate of the velocity.
56
+ - 'r_ITRF_x'/'rx'/'rx_eci'/'rx_ecef': The x-coordinate of the position.
57
+ - 'r_ITRF_y'/'ry'/'ry_eci'/'ry_ecef': The y-coordinate of the position.
58
+ - 'r_ITRF_z'/'rz'/'rz_eci'/'rz_ecef': The z-coordinate of the position.
59
+ - 'v_ITRF_x'/'vx'/'vx_eci'/'vx_ecef': The x-coordinate of the velocity.
60
+ - 'v_ITRF_y'/'vy'/'vy_eci'/'vy_ecef': The y-coordinate of the velocity.
61
+ - 'v_ITRF_z'/'vz'/'vz_eci'/'vz_ecef': The z-coordinate of the velocity.
56
62
  - 'frame': The frame of the state. Required if 'rx', 'ry', 'rz', 'vx', 'vy', 'vz' are provided.
57
63
  - 'q_B_ECI_x': The x-coordinate of the quaternion. Optional.
58
64
  - 'q_B_ECI_y': The y-coordinate of the quaternion. Optional.
@@ -65,7 +71,6 @@ def from_dict(data: dict) -> State:
65
71
  - 'cross_sectional_area'/'surface_area': The cross-sectional area. Optional.
66
72
  - 'mass': The mass. Optional.
67
73
 
68
-
69
74
  Args:
70
75
  data (dict): The dictionary.
71
76
 
@@ -100,6 +105,17 @@ def from_dict(data: dict) -> State:
100
105
  "vz",
101
106
  ]
102
107
 
108
+ # Replace non-standard position keys with canonical representation
109
+ if all(key in data.keys() for key in ("x_eci", "y_eci", "z_eci")):
110
+ data["rx_eci"] = data["x_eci"]
111
+ data["ry_eci"] = data["y_eci"]
112
+ data["rz_eci"] = data["z_eci"]
113
+
114
+ if all(key in data.keys() for key in ("x_ecef", "y_ecef", "z_ecef")):
115
+ data["rx_ecef"] = data["x_ecef"]
116
+ data["ry_ecef"] = data["y_ecef"]
117
+ data["rz_ecef"] = data["z_ecef"]
118
+
103
119
  frame: Frame
104
120
  coordinates: np.ndarray
105
121
 
@@ -108,8 +124,21 @@ def from_dict(data: dict) -> State:
108
124
  CartesianVelocity.default(),
109
125
  ]
110
126
 
111
- if all(column in data for column in eci_columns):
112
- frame = Frame.GCRF()
127
+ match_groups: list[re.Match] = [
128
+ re.match(CANONICAL_FORMAT, column) for column in data.keys()
129
+ ]
130
+
131
+ if len(matches := [match for match in match_groups if match is not None]) == 6:
132
+ frame_name: str = matches[0].group(2)
133
+ try:
134
+ frame: Frame = Frame.with_name(frame_name) or getattr(Frame, frame_name)()
135
+ except Exception:
136
+ raise ValueError(f"No frame exists with name [{frame_name}].")
137
+
138
+ coordinates = np.array([data[match.group(0)] for match in matches])
139
+
140
+ elif all(column in data for column in eci_columns):
141
+ frame: Frame = Frame.GCRF()
113
142
  coordinates = np.array(
114
143
  [
115
144
  data["rx_eci"],
@@ -120,6 +149,7 @@ def from_dict(data: dict) -> State:
120
149
  data["vz_eci"],
121
150
  ]
122
151
  )
152
+
123
153
  elif all(column in data for column in ecef_columns):
124
154
  frame = Frame.ITRF()
125
155
  coordinates = np.array(
@@ -132,6 +162,7 @@ def from_dict(data: dict) -> State:
132
162
  data["vz_ecef"],
133
163
  ]
134
164
  )
165
+
135
166
  elif all(column in data for column in generic_columns):
136
167
  if "frame" not in data:
137
168
  raise ValueError("Frame must be provided for generic columns.")
@@ -134,6 +134,17 @@ class TestProfile:
134
134
  assert alignment_target is not None
135
135
  assert isinstance(alignment_target, Profile.Target)
136
136
 
137
+ def test_access_model(self, profile: Profile):
138
+ model = profile.access_model()
139
+
140
+ assert model is not None
141
+
142
+ if model.is_transform():
143
+ assert model.as_transform() is not None
144
+
145
+ if model.is_tabulated():
146
+ assert model.as_tabulated() is not None
147
+
137
148
  def test_get_state_at(self, profile: Profile, instant: Instant):
138
149
  state: State = profile.get_state_at(instant)
139
150
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  import pytest
4
4
 
5
+ from ostk.physics.time import Instant
6
+ from ostk.physics.coordinate import Position, Velocity, Frame
7
+
8
+ from ostk.astrodynamics.trajectory import State
5
9
  from ostk.astrodynamics import EventCondition
6
10
 
7
11
 
@@ -12,7 +16,7 @@ def name() -> str:
12
16
 
13
17
  @pytest.fixture
14
18
  def evaluator() -> callable:
15
- return lambda state: 0.0
19
+ return lambda state: 5.0
16
20
 
17
21
 
18
22
  @pytest.fixture
@@ -22,7 +26,7 @@ def target_value() -> float:
22
26
 
23
27
  @pytest.fixture
24
28
  def target(target_value: float) -> EventCondition.Target:
25
- return EventCondition.Target(target_value, EventCondition.Target.Type.Absolute)
29
+ return EventCondition.Target(target_value, EventCondition.Target.Type.Relative)
26
30
 
27
31
 
28
32
  @pytest.fixture
@@ -32,12 +36,13 @@ def event_condition(
32
36
  class MyEventCondition(EventCondition):
33
37
  def is_satisfied(
34
38
  self,
35
- current_state_vector,
36
- current_time,
37
- previous_state_vector,
38
- previous_time,
39
+ current_state,
40
+ previous_state,
39
41
  ):
40
- return current_state_vector[0] > 0.0 and previous_state_vector[0] < 0.0
42
+ return (
43
+ current_state.get_position().get_coordinates()[2] > 0.0
44
+ and previous_state.get_position().get_coordinates()[2] < 0.0
45
+ )
41
46
 
42
47
  return MyEventCondition(name, evaluator, target)
43
48
 
@@ -56,3 +61,18 @@ class TestEventCondition:
56
61
  self, event_condition: EventCondition, target: EventCondition.Target
57
62
  ):
58
63
  assert event_condition.get_target() == target
64
+
65
+ def test_update_target(
66
+ self,
67
+ event_condition: EventCondition,
68
+ ):
69
+ current_target_value_offset: float = event_condition.get_target().value_offset
70
+ event_condition.update_target(
71
+ State(
72
+ Instant.J2000(),
73
+ Position.meters([0.0, 0.0, 0.0], Frame.GCRF()),
74
+ Velocity.meters_per_second([0.0, 0.0, 0.0], Frame.GCRF()),
75
+ )
76
+ )
77
+
78
+ assert event_condition.get_target().value_offset != current_target_value_offset
@@ -11,8 +11,8 @@ 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
13
 
14
- from ostk.astrodynamics.flight import Maneuver
15
14
  from ostk.astrodynamics.flight.system import SatelliteSystem
15
+ from ostk.astrodynamics import Dynamics
16
16
  from ostk.astrodynamics.dynamics import CentralBodyGravity
17
17
  from ostk.astrodynamics.dynamics import PositionDerivative
18
18
  from ostk.astrodynamics.dynamics import Thruster
@@ -135,6 +135,104 @@ def thruster_dynamics() -> Thruster:
135
135
  )
136
136
 
137
137
 
138
+ @pytest.fixture
139
+ def segment_solution(dynamics: list[Dynamics], state: State) -> Segment.Solution:
140
+ return Segment.Solution(
141
+ name="A Segment",
142
+ dynamics=dynamics,
143
+ states=[
144
+ state,
145
+ ],
146
+ condition_is_satisfied=True,
147
+ segment_type=Segment.Type.Coast,
148
+ )
149
+
150
+
151
+ class TestSegmentSolution:
152
+ def test_constructors(
153
+ self,
154
+ segment_solution: Segment.Solution,
155
+ ):
156
+ assert segment_solution is not None
157
+ assert segment_solution.name is not None
158
+ assert segment_solution.dynamics is not None
159
+ assert segment_solution.states is not None
160
+ assert segment_solution.condition_is_satisfied is not None
161
+ assert segment_solution.segment_type is not None
162
+
163
+ def test_getters_and_accessors(
164
+ self,
165
+ segment_solution: Segment.Solution,
166
+ ):
167
+ assert segment_solution.access_start_instant() is not None
168
+ assert segment_solution.access_end_instant() is not None
169
+ assert segment_solution.get_interval() is not None
170
+ assert segment_solution.get_initial_mass() is not None
171
+ assert segment_solution.get_final_mass() is not None
172
+ assert segment_solution.get_propagation_duration() is not None
173
+
174
+ def test_compute_delta_v(
175
+ self,
176
+ segment_solution: Segment.Solution,
177
+ ):
178
+ assert segment_solution.compute_delta_v(1500.0) is not None
179
+
180
+ def test_compute_delta_mass(
181
+ self,
182
+ segment_solution: Segment.Solution,
183
+ ):
184
+ assert segment_solution.compute_delta_mass() is not None
185
+
186
+ @pytest.mark.skip(reason="Not implemented yet")
187
+ def test_extract_maneuvers(
188
+ self,
189
+ segment_solution: Segment.Solution,
190
+ ):
191
+ assert segment_solution.extract_maneuvers(Frame.GCRF()) is not None
192
+
193
+ def test_calculate_states_at(
194
+ self,
195
+ segment_solution: Segment.Solution,
196
+ ):
197
+ assert (
198
+ segment_solution.calculate_states_at(
199
+ [
200
+ segment_solution.states[0].get_instant(),
201
+ ],
202
+ NumericalSolver.default_conditional(),
203
+ )
204
+ is not None
205
+ )
206
+
207
+ def get_dynamics_contribution(
208
+ self,
209
+ segment_solution: Segment.Solution,
210
+ ):
211
+ assert (
212
+ segment_solution.get_dynamics_contribution(
213
+ segment_solution.dynamics[0], Frame.GCRF()
214
+ )
215
+ is not None
216
+ )
217
+
218
+ def get_dynamics_acceleration_contribution(
219
+ self,
220
+ segment_solution: Segment.Solution,
221
+ ):
222
+ assert (
223
+ segment_solution.get_dynamics_acceleration_contribution(
224
+ segment_solution.dynamics[0], Frame.GCRF()
225
+ )
226
+ is not None
227
+ )
228
+
229
+ def get_all_dynamics_contributions(
230
+ self,
231
+ segment_solution: Segment.Solution,
232
+ ):
233
+ assert segment_solution.get_all_dynamics_contributions(Frame.GCRF()) is not None
234
+
235
+
138
236
  class TestSegment:
139
237
  def test_get_name(self, coast_duration_segment: Segment, name: str):
140
238
  assert coast_duration_segment.get_name() == name
@@ -339,6 +339,59 @@ def instants(state: State) -> list[Instant]:
339
339
  return [state.get_instant(), state.get_instant() + Duration.minutes(1.0)]
340
340
 
341
341
 
342
+ @pytest.fixture
343
+ def sequence_solution(
344
+ segment_solution: Segment.Solution,
345
+ ):
346
+ return Sequence.Solution(
347
+ segment_solutions=[
348
+ segment_solution,
349
+ ],
350
+ execution_is_complete=True,
351
+ )
352
+
353
+
354
+ class TestSequenceSolution:
355
+ def test_properties(
356
+ self,
357
+ sequence_solution: Sequence.Solution,
358
+ ):
359
+ assert sequence_solution is not None
360
+ assert len(sequence_solution.segment_solutions) == 1
361
+ assert sequence_solution.execution_is_complete
362
+
363
+ def test_getters_and_accessors(
364
+ self,
365
+ sequence_solution: Sequence.Solution,
366
+ ):
367
+ assert sequence_solution.access_start_instant() is not None
368
+ assert sequence_solution.access_end_instant() is not None
369
+
370
+ assert sequence_solution.get_states() is not None
371
+ assert sequence_solution.get_initial_mass() is not None
372
+ assert sequence_solution.get_final_mass() is not None
373
+ assert sequence_solution.get_propagation_duration() is not None
374
+
375
+ assert sequence_solution.compute_delta_mass() is not None
376
+ assert sequence_solution.compute_delta_v(1500.0) is not None
377
+
378
+ def test_calculate_states_at(
379
+ self,
380
+ sequence_solution: Sequence.Solution,
381
+ numerical_solver: NumericalSolver,
382
+ ):
383
+ instants: list[Instant] = sequence_solution.get_interval().generate_grid(
384
+ Duration.seconds(10.0)
385
+ )
386
+ states: list[State] = sequence_solution.calculate_states_at(
387
+ instants,
388
+ numerical_solver,
389
+ )
390
+
391
+ assert states is not None
392
+ assert len(states) == len(instants)
393
+
394
+
342
395
  class TestSequence:
343
396
  def test_get_segments(
344
397
  self,
@@ -407,6 +407,82 @@ class TestState:
407
407
  assert isinstance(state, State)
408
408
  assert state.get_size() == 7
409
409
 
410
+ @pytest.mark.parametrize(
411
+ ("data", "expected_length", "expected_frame"),
412
+ [
413
+ (
414
+ {
415
+ "timestamp": datetime.now(timezone.utc).isoformat(),
416
+ "r_GCRF_x": 1.0,
417
+ "r_GCRF_y": 2.0,
418
+ "r_GCRF_z": 3.0,
419
+ "v_GCRF_x": 4.0,
420
+ "v_GCRF_y": 5.0,
421
+ "v_GCRF_z": 6.0,
422
+ },
423
+ 6,
424
+ Frame.GCRF(),
425
+ ),
426
+ (
427
+ {
428
+ "timestamp": datetime.now(timezone.utc).isoformat(),
429
+ "r_ITRF_x": 1.0,
430
+ "r_ITRF_y": 2.0,
431
+ "r_ITRF_z": 3.0,
432
+ "v_ITRF_x": 4.0,
433
+ "v_ITRF_y": 5.0,
434
+ "v_ITRF_z": 6.0,
435
+ },
436
+ 6,
437
+ Frame.ITRF(),
438
+ ),
439
+ ],
440
+ )
441
+ def test_from_dict_cannonical_success(
442
+ self, data: dict, expected_length: int, expected_frame: Frame
443
+ ):
444
+ state: State = State.from_dict(data)
445
+
446
+ assert state is not None
447
+ assert isinstance(state, State)
448
+
449
+ assert state.get_size() == expected_length
450
+ assert state.get_frame() == expected_frame
451
+
452
+ @pytest.mark.parametrize(
453
+ ("data", "expected_failure_message"),
454
+ [
455
+ (
456
+ {
457
+ "timestamp": datetime.now(timezone.utc).isoformat(),
458
+ "r_GCRF_x": 1.0,
459
+ "r_GCRF_y": 2.0,
460
+ "r_GCRF_z": 3.0,
461
+ "v_GCRF_x": 4.0,
462
+ "v_GCRF_y": 5.0,
463
+ },
464
+ "Invalid state data.",
465
+ ),
466
+ (
467
+ {
468
+ "timestamp": datetime.now(timezone.utc).isoformat(),
469
+ "r_TEST_x": 1.0,
470
+ "r_TEST_y": 2.0,
471
+ "r_TEST_z": 3.0,
472
+ "v_TEST_x": 4.0,
473
+ "v_TEST_y": 5.0,
474
+ "v_TEST_z": 6.0,
475
+ },
476
+ "No frame exists with name \\[TEST\\].",
477
+ ),
478
+ ],
479
+ )
480
+ def test_from_dict_cannonical_failure(
481
+ self, data: dict, expected_failure_message: str
482
+ ):
483
+ with pytest.raises(ValueError, match=expected_failure_message):
484
+ State.from_dict(data)
485
+
410
486
  def test_comparators(self, state: State):
411
487
  assert (state == state) is True
412
488
  assert (state != state) is False
@@ -1,5 +1,7 @@
1
1
  # Apache License 2.0
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from datetime import datetime
4
6
  from datetime import timezone
5
7