open-space-toolkit-astrodynamics 17.2.0__py312-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.
Files changed (151) hide show
  1. open_space_toolkit_astrodynamics-17.2.0.dist-info/METADATA +30 -0
  2. open_space_toolkit_astrodynamics-17.2.0.dist-info/RECORD +151 -0
  3. open_space_toolkit_astrodynamics-17.2.0.dist-info/WHEEL +5 -0
  4. open_space_toolkit_astrodynamics-17.2.0.dist-info/top_level.txt +1 -0
  5. open_space_toolkit_astrodynamics-17.2.0.dist-info/zip-safe +1 -0
  6. ostk/__init__.py +1 -0
  7. ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-312-x86_64-linux-gnu.so +0 -0
  8. ostk/astrodynamics/__init__.py +11 -0
  9. ostk/astrodynamics/__init__.pyi +720 -0
  10. ostk/astrodynamics/access.pyi +577 -0
  11. ostk/astrodynamics/conjunction/__init__.pyi +121 -0
  12. ostk/astrodynamics/conjunction/close_approach.pyi +89 -0
  13. ostk/astrodynamics/conjunction/message/__init__.pyi +3 -0
  14. ostk/astrodynamics/conjunction/message/ccsds.pyi +705 -0
  15. ostk/astrodynamics/converters.py +130 -0
  16. ostk/astrodynamics/converters.pyi +58 -0
  17. ostk/astrodynamics/data/__init__.pyi +3 -0
  18. ostk/astrodynamics/data/provider.pyi +22 -0
  19. ostk/astrodynamics/dataframe.py +597 -0
  20. ostk/astrodynamics/display.py +281 -0
  21. ostk/astrodynamics/dynamics.pyi +311 -0
  22. ostk/astrodynamics/eclipse.pyi +70 -0
  23. ostk/astrodynamics/estimator.pyi +268 -0
  24. ostk/astrodynamics/event_condition.pyi +910 -0
  25. ostk/astrodynamics/flight/__init__.pyi +626 -0
  26. ostk/astrodynamics/flight/profile/__init__.pyi +99 -0
  27. ostk/astrodynamics/flight/profile/model.pyi +179 -0
  28. ostk/astrodynamics/flight/system.pyi +268 -0
  29. ostk/astrodynamics/guidance_law.pyi +416 -0
  30. ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.17 +0 -0
  31. ostk/astrodynamics/pytrajectory/__init__.py +1 -0
  32. ostk/astrodynamics/pytrajectory/__init__.pyi +3 -0
  33. ostk/astrodynamics/pytrajectory/pystate.py +263 -0
  34. ostk/astrodynamics/pytrajectory/pystate.pyi +66 -0
  35. ostk/astrodynamics/solver.pyi +432 -0
  36. ostk/astrodynamics/test/__init__.py +1 -0
  37. ostk/astrodynamics/test/access/__init__.py +1 -0
  38. ostk/astrodynamics/test/access/test_generator.py +319 -0
  39. ostk/astrodynamics/test/access/test_visibility_criterion.py +201 -0
  40. ostk/astrodynamics/test/conftest.py +119 -0
  41. ostk/astrodynamics/test/conjunction/close_approach/__init__.py +0 -0
  42. ostk/astrodynamics/test/conjunction/close_approach/test_generator.py +228 -0
  43. ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py +1 -0
  44. ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py +325 -0
  45. ostk/astrodynamics/test/conjunction/message/ccsds/data/cdm.json +303 -0
  46. ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py +416 -0
  47. ostk/astrodynamics/test/conjunction/test_close_approach.py +244 -0
  48. ostk/astrodynamics/test/data/provider/test_off_nadir.py +58 -0
  49. ostk/astrodynamics/test/dynamics/__init__.py +1 -0
  50. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv +565 -0
  51. ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv +100 -0
  52. ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
  53. ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
  54. ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
  55. ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
  56. ostk/astrodynamics/test/dynamics/test_tabulated.py +138 -0
  57. ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
  58. ostk/astrodynamics/test/dynamics/test_thruster.py +157 -0
  59. ostk/astrodynamics/test/eclipse/__init__.py +1 -0
  60. ostk/astrodynamics/test/eclipse/test_generator.py +138 -0
  61. ostk/astrodynamics/test/estimator/test_orbit_determination_solver.py +261 -0
  62. ostk/astrodynamics/test/estimator/test_tle_solver.py +216 -0
  63. ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
  64. ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
  65. ostk/astrodynamics/test/event_condition/test_brouwer_lyddane_mean_long_condition.py +135 -0
  66. ostk/astrodynamics/test/event_condition/test_coe_condition.py +135 -0
  67. ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
  68. ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
  69. ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
  70. ostk/astrodynamics/test/flight/__init__.py +1 -0
  71. ostk/astrodynamics/test/flight/profile/model/test_tabulated_profile.py +115 -0
  72. ostk/astrodynamics/test/flight/system/__init__.py +1 -0
  73. ostk/astrodynamics/test/flight/system/test_propulsion_system.py +64 -0
  74. ostk/astrodynamics/test/flight/system/test_satellite_system.py +83 -0
  75. ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
  76. ostk/astrodynamics/test/flight/test_maneuver.py +231 -0
  77. ostk/astrodynamics/test/flight/test_profile.py +293 -0
  78. ostk/astrodynamics/test/flight/test_system.py +45 -0
  79. ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +177 -0
  80. ostk/astrodynamics/test/guidance_law/test_guidance_law.py +60 -0
  81. ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py +164 -0
  82. ostk/astrodynamics/test/guidance_law/test_qlaw.py +209 -0
  83. ostk/astrodynamics/test/solvers/__init__.py +1 -0
  84. ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +196 -0
  85. ostk/astrodynamics/test/solvers/test_least_squares_solver.py +334 -0
  86. ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +161 -0
  87. ostk/astrodynamics/test/test_access.py +128 -0
  88. ostk/astrodynamics/test/test_converters.py +290 -0
  89. ostk/astrodynamics/test/test_dataframe.py +1355 -0
  90. ostk/astrodynamics/test/test_display.py +184 -0
  91. ostk/astrodynamics/test/test_event_condition.py +80 -0
  92. ostk/astrodynamics/test/test_import.py +26 -0
  93. ostk/astrodynamics/test/test_root_solver.py +70 -0
  94. ostk/astrodynamics/test/test_trajectory.py +126 -0
  95. ostk/astrodynamics/test/test_utilities.py +338 -0
  96. ostk/astrodynamics/test/test_viewer.py +318 -0
  97. ostk/astrodynamics/test/trajectory/__init__.py +1 -0
  98. ostk/astrodynamics/test/trajectory/model/test_nadir_trajectory.py +87 -0
  99. ostk/astrodynamics/test/trajectory/model/test_tabulated_trajectory.py +303 -0
  100. ostk/astrodynamics/test/trajectory/model/test_target_scan_trajectory.py +126 -0
  101. ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
  102. ostk/astrodynamics/test/trajectory/orbit/message/__init__.py +1 -0
  103. ostk/astrodynamics/test/trajectory/orbit/message/spacex/__init__.py +1 -0
  104. ostk/astrodynamics/test/trajectory/orbit/message/spacex/conftest.py +18 -0
  105. ostk/astrodynamics/test/trajectory/orbit/message/spacex/data/opm_1.yaml +44 -0
  106. ostk/astrodynamics/test/trajectory/orbit/message/spacex/test_opm.py +108 -0
  107. ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
  108. ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
  109. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
  110. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
  111. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
  112. ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +305 -0
  113. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
  114. ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +337 -0
  115. ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
  116. ostk/astrodynamics/test/trajectory/orbit/models/test_modified_equinoctial.py +142 -0
  117. ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +234 -0
  118. ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
  119. ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
  120. ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
  121. ostk/astrodynamics/test/trajectory/orbit/test_pass.py +75 -0
  122. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity.py +30 -0
  123. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py +18 -0
  124. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_acceleration.py +136 -0
  125. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py +107 -0
  126. ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py +115 -0
  127. ostk/astrodynamics/test/trajectory/state/test_coordinate_broker.py +84 -0
  128. ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +58 -0
  129. ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +316 -0
  130. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
  131. ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +119 -0
  132. ostk/astrodynamics/test/trajectory/test_model.py +1 -0
  133. ostk/astrodynamics/test/trajectory/test_orbit.py +212 -0
  134. ostk/astrodynamics/test/trajectory/test_propagator.py +452 -0
  135. ostk/astrodynamics/test/trajectory/test_segment.py +694 -0
  136. ostk/astrodynamics/test/trajectory/test_sequence.py +550 -0
  137. ostk/astrodynamics/test/trajectory/test_state.py +629 -0
  138. ostk/astrodynamics/test/trajectory/test_state_builder.py +172 -0
  139. ostk/astrodynamics/trajectory/__init__.pyi +1982 -0
  140. ostk/astrodynamics/trajectory/model.pyi +259 -0
  141. ostk/astrodynamics/trajectory/orbit/__init__.pyi +349 -0
  142. ostk/astrodynamics/trajectory/orbit/message/__init__.pyi +3 -0
  143. ostk/astrodynamics/trajectory/orbit/message/spacex.pyi +264 -0
  144. ostk/astrodynamics/trajectory/orbit/model/__init__.pyi +648 -0
  145. ostk/astrodynamics/trajectory/orbit/model/brouwerLyddaneMean.pyi +121 -0
  146. ostk/astrodynamics/trajectory/orbit/model/kepler.pyi +709 -0
  147. ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi +330 -0
  148. ostk/astrodynamics/trajectory/state/__init__.pyi +402 -0
  149. ostk/astrodynamics/trajectory/state/coordinate_subset.pyi +208 -0
  150. ostk/astrodynamics/utilities.py +396 -0
  151. ostk/astrodynamics/viewer.py +851 -0
@@ -0,0 +1,196 @@
1
+ # Apache License 2.0
2
+
3
+ from typing import Callable
4
+
5
+ import pytest
6
+ import math
7
+
8
+ import numpy as np
9
+
10
+ from ostk.physics.coordinate import Frame
11
+ from ostk.physics.time import Instant
12
+ from ostk.physics.time import Duration
13
+
14
+ from ostk.astrodynamics.solver import FiniteDifferenceSolver
15
+ from ostk.astrodynamics.trajectory import State
16
+ from ostk.astrodynamics.trajectory.state import CoordinateSubset
17
+
18
+
19
+ @pytest.fixture
20
+ def step_percentage() -> float:
21
+ return 1e-3
22
+
23
+
24
+ @pytest.fixture
25
+ def step_duration() -> Duration:
26
+ return Duration.seconds(1e-6)
27
+
28
+
29
+ @pytest.fixture(
30
+ params=[
31
+ FiniteDifferenceSolver.Type.Forward,
32
+ FiniteDifferenceSolver.Type.Backward,
33
+ FiniteDifferenceSolver.Type.Central,
34
+ ]
35
+ )
36
+ def finite_difference_solver(request, step_percentage: float, step_duration: Duration):
37
+ return FiniteDifferenceSolver(request.param, step_percentage, step_duration)
38
+
39
+
40
+ @pytest.fixture
41
+ def coordinate_subsets() -> list[CoordinateSubset]:
42
+ return [CoordinateSubset("Position", 1), CoordinateSubset("Velocity", 1)]
43
+
44
+
45
+ @pytest.fixture
46
+ def frame() -> Frame:
47
+ return Frame.GCRF()
48
+
49
+
50
+ @pytest.fixture
51
+ def state(
52
+ initial_instant: Instant, frame: Frame, coordinate_subsets: list[CoordinateSubset]
53
+ ):
54
+ return State(initial_instant, [1, 0], frame, coordinate_subsets)
55
+
56
+
57
+ @pytest.fixture
58
+ def initial_instant() -> Instant:
59
+ return Instant.J2000()
60
+
61
+
62
+ @pytest.fixture
63
+ def instant(initial_instant: Instant) -> Instant:
64
+ return initial_instant + Duration.seconds(100.0)
65
+
66
+
67
+ @pytest.fixture
68
+ def instants(initial_instant: Instant) -> list[Instant]:
69
+ return [
70
+ initial_instant + Duration.seconds(100.0),
71
+ initial_instant + Duration.seconds(200.0),
72
+ ]
73
+
74
+
75
+ @pytest.fixture
76
+ def generate_states_coordinates() -> Callable:
77
+ def state_fn(state, instants) -> np.ndarray:
78
+ x0: float = state.get_coordinates()[0]
79
+ v0: float = state.get_coordinates()[1]
80
+ omega: float = 1.0
81
+
82
+ states_coordinates: list[list[float]] = []
83
+
84
+ for instant in instants:
85
+ t: float = (instant - state.get_instant()).in_seconds()
86
+ x: float = x0 * math.cos(omega * t) + v0 / omega * math.sin(omega * t)
87
+ v: float = -x0 * omega * math.sin(omega * t) + v0 * math.cos(omega * t)
88
+
89
+ coordinates: list[float] = list(np.array([x, v]).astype(float))
90
+
91
+ states_coordinates.append(coordinates)
92
+
93
+ return np.array(states_coordinates).transpose()
94
+
95
+ return state_fn
96
+
97
+
98
+ @pytest.fixture
99
+ def generate_state_coordinates() -> Callable:
100
+ def state_fn(state, instant) -> np.ndarray:
101
+ x0: float = state.get_coordinates()[0]
102
+ v0: float = state.get_coordinates()[1]
103
+ omega: float = 1.0
104
+
105
+ t: float = (instant - state.get_instant()).in_seconds()
106
+ x: float = x0 * math.cos(omega * t) + v0 / omega * math.sin(omega * t)
107
+ v: float = -x0 * omega * math.sin(omega * t) + v0 * math.cos(omega * t)
108
+
109
+ return np.array([x, v])
110
+
111
+ return state_fn
112
+
113
+
114
+ class TestFiniteDifferenceSolver:
115
+ def test_constructor(self, finite_difference_solver: FiniteDifferenceSolver):
116
+ assert isinstance(finite_difference_solver, FiniteDifferenceSolver)
117
+
118
+ def test_getters(self, finite_difference_solver: FiniteDifferenceSolver):
119
+ assert isinstance(
120
+ finite_difference_solver.get_type(), FiniteDifferenceSolver.Type
121
+ )
122
+ assert finite_difference_solver.get_step_percentage() is not None
123
+ assert isinstance(finite_difference_solver.get_step_duration(), Duration)
124
+
125
+ def test_string_from_type(self):
126
+ assert (
127
+ FiniteDifferenceSolver.string_from_type(FiniteDifferenceSolver.Type.Forward)
128
+ == "Forward"
129
+ )
130
+
131
+ def test_compute_state_transition_matrix_array(
132
+ self,
133
+ finite_difference_solver: FiniteDifferenceSolver,
134
+ state: State,
135
+ instants: list[Instant],
136
+ generate_states_coordinates: Callable,
137
+ ):
138
+ stms: list[np.ndarray] = finite_difference_solver.compute_state_transition_matrix(
139
+ state=state,
140
+ instants=instants,
141
+ generate_states_coordinates=generate_states_coordinates,
142
+ )
143
+
144
+ assert len(stms) == len(instants)
145
+
146
+ assert stms[0].shape == (
147
+ len(state.get_coordinates()),
148
+ len(state.get_coordinates()),
149
+ )
150
+
151
+ def test_compute_state_transition_matrix_single(
152
+ self,
153
+ finite_difference_solver: FiniteDifferenceSolver,
154
+ state: State,
155
+ generate_state_coordinates: Callable,
156
+ instant: Instant,
157
+ ):
158
+ stm: np.ndarray = finite_difference_solver.compute_state_transition_matrix(
159
+ state=state,
160
+ instant=instant,
161
+ generate_state_coordinates=generate_state_coordinates,
162
+ )
163
+ assert isinstance(stm, np.ndarray)
164
+ assert stm.shape == (
165
+ len(state.get_coordinates()),
166
+ len(state.get_coordinates()),
167
+ )
168
+
169
+ def test_compute_gradient(
170
+ self,
171
+ finite_difference_solver: FiniteDifferenceSolver,
172
+ state: State,
173
+ generate_state_coordinates: Callable,
174
+ ):
175
+ gradient = finite_difference_solver.compute_gradient(
176
+ state=state,
177
+ generate_state_coordinates=generate_state_coordinates,
178
+ )
179
+ assert isinstance(gradient, np.ndarray)
180
+ assert all(gradient - np.array([0.0, -1.0]) < 1e-6)
181
+
182
+ def test_compute_jacobian(
183
+ self,
184
+ finite_difference_solver: FiniteDifferenceSolver,
185
+ state: State,
186
+ generate_state_coordinates: Callable,
187
+ ):
188
+ gradient = finite_difference_solver.compute_jacobian(
189
+ state=state,
190
+ generate_state_coordinates=generate_state_coordinates,
191
+ )
192
+ assert isinstance(gradient, np.ndarray)
193
+ assert np.all(gradient - np.array([[0.0, 1.0], [-1.0, 0.0]]) < 1e-6)
194
+
195
+ def test_default(self):
196
+ assert isinstance(FiniteDifferenceSolver.default(), FiniteDifferenceSolver)
@@ -0,0 +1,334 @@
1
+ # Apache License 2.0
2
+
3
+ from typing import Callable
4
+
5
+ import pytest
6
+ import numpy as np
7
+
8
+ from ostk.core.type import Real
9
+ from ostk.core.type import String
10
+
11
+ from ostk.physics.time import Instant
12
+ from ostk.physics.time import Duration
13
+ from ostk.physics.coordinate import Frame
14
+
15
+ from ostk.astrodynamics.solver import LeastSquaresSolver
16
+ from ostk.astrodynamics.solver import FiniteDifferenceSolver
17
+ from ostk.astrodynamics.trajectory import State
18
+ from ostk.astrodynamics.trajectory.state import CoordinateSubset
19
+
20
+
21
+ @pytest.fixture
22
+ def rms_error() -> float:
23
+ return 1.0
24
+
25
+
26
+ @pytest.fixture
27
+ def x_hat() -> np.ndarray:
28
+ return np.array([1.0, 0.0])
29
+
30
+
31
+ @pytest.fixture
32
+ def step(
33
+ rms_error: float,
34
+ x_hat: np.ndarray,
35
+ ) -> LeastSquaresSolver.Step:
36
+ return LeastSquaresSolver.Step(
37
+ rms_error=rms_error,
38
+ x_hat=x_hat,
39
+ )
40
+
41
+
42
+ @pytest.fixture
43
+ def termination_criteria() -> str:
44
+ return "RMS Update Threshold"
45
+
46
+
47
+ @pytest.fixture
48
+ def estimated_state(coordinate_subsets: list[CoordinateSubset]) -> State:
49
+ return State(
50
+ Instant.J2000(),
51
+ [1.0, 0.0],
52
+ Frame.GCRF(),
53
+ coordinate_subsets,
54
+ )
55
+
56
+
57
+ @pytest.fixture
58
+ def estimated_covariance() -> np.ndarray:
59
+ return np.array([[1.0, 0.0], [0.0, 1.0]])
60
+
61
+
62
+ @pytest.fixture
63
+ def estimated_frisbee_covariance() -> np.ndarray:
64
+ return np.array([[1.0, 0.0], [0.0, 1.0]])
65
+
66
+
67
+ @pytest.fixture
68
+ def observation_count() -> int:
69
+ return 10
70
+
71
+
72
+ @pytest.fixture
73
+ def computed_observations(
74
+ observations: list[State],
75
+ ) -> list[State]:
76
+ return observations
77
+
78
+
79
+ @pytest.fixture
80
+ def steps(step: LeastSquaresSolver.Step) -> list[LeastSquaresSolver.Step]:
81
+ return [step]
82
+
83
+
84
+ @pytest.fixture
85
+ def analysis(
86
+ termination_criteria: str,
87
+ estimated_state: State,
88
+ estimated_covariance: np.ndarray,
89
+ estimated_frisbee_covariance: np.ndarray,
90
+ computed_observations: list[State],
91
+ steps: list[LeastSquaresSolver.Step],
92
+ ) -> LeastSquaresSolver.Analysis:
93
+ return LeastSquaresSolver.Analysis(
94
+ termination_criteria=termination_criteria,
95
+ estimated_state=estimated_state,
96
+ estimated_covariance=estimated_covariance,
97
+ estimated_frisbee_covariance=estimated_frisbee_covariance,
98
+ computed_observations=computed_observations,
99
+ steps=steps,
100
+ )
101
+
102
+
103
+ @pytest.fixture
104
+ def max_iteration_count() -> int:
105
+ return 20
106
+
107
+
108
+ @pytest.fixture
109
+ def rms_update_threshold() -> float:
110
+ return 1.0
111
+
112
+
113
+ @pytest.fixture
114
+ def finite_difference_solver() -> FiniteDifferenceSolver:
115
+ return FiniteDifferenceSolver.default()
116
+
117
+
118
+ @pytest.fixture
119
+ def least_squares_solver(
120
+ max_iteration_count: int,
121
+ rms_update_threshold: float,
122
+ finite_difference_solver: FiniteDifferenceSolver,
123
+ ) -> LeastSquaresSolver:
124
+ return LeastSquaresSolver(
125
+ maximum_iteration_count=max_iteration_count,
126
+ rms_update_threshold=rms_update_threshold,
127
+ finite_difference_solver=finite_difference_solver,
128
+ )
129
+
130
+
131
+ @pytest.fixture
132
+ def coordinate_subsets() -> list[CoordinateSubset]:
133
+ return [CoordinateSubset("Position", 1), CoordinateSubset("Velocity", 1)]
134
+
135
+
136
+ @pytest.fixture
137
+ def initial_guess_sigmas(
138
+ coordinate_subsets: list[CoordinateSubset],
139
+ ) -> dict[CoordinateSubset, list[float]]:
140
+ return {
141
+ coordinate_subsets[0]: [1e-1],
142
+ coordinate_subsets[1]: [1e-2],
143
+ }
144
+
145
+
146
+ @pytest.fixture
147
+ def observation_sigmas(
148
+ coordinate_subsets: list[CoordinateSubset],
149
+ ) -> dict[CoordinateSubset, list[float]]:
150
+ return {
151
+ coordinate_subsets[0]: [1e-1],
152
+ coordinate_subsets[1]: [1e-2],
153
+ }
154
+
155
+
156
+ @pytest.fixture
157
+ def initial_instant() -> Instant:
158
+ return Instant.J2000()
159
+
160
+
161
+ @pytest.fixture
162
+ def frame() -> Frame:
163
+ return Frame.GCRF()
164
+
165
+
166
+ @pytest.fixture
167
+ def initial_guess(
168
+ initial_instant: Instant,
169
+ coordinate_subsets: list[CoordinateSubset],
170
+ frame: Frame,
171
+ ) -> State:
172
+ return State(initial_instant, [1.0, 0.0], frame, coordinate_subsets)
173
+
174
+
175
+ @pytest.fixture
176
+ def observations(
177
+ initial_instant: Instant,
178
+ coordinate_subsets: list[CoordinateSubset],
179
+ frame: Frame,
180
+ observation_count: int,
181
+ ) -> list[State]:
182
+ return [
183
+ State(
184
+ initial_instant + Duration.seconds(float(x)),
185
+ [np.cos(x), -np.sin(x)],
186
+ frame,
187
+ coordinate_subsets,
188
+ )
189
+ for x in range(0, observation_count)
190
+ ]
191
+
192
+
193
+ @pytest.fixture
194
+ def state_generator() -> Callable:
195
+ def state_fn(state, instants) -> list[State]:
196
+ x0: float = state.get_coordinates()[0]
197
+ v0: float = state.get_coordinates()[1]
198
+ omega: float = 1.0
199
+
200
+ states: list[State] = []
201
+
202
+ for instant in instants:
203
+ t: float = float((instant - state.get_instant()).in_seconds())
204
+ x: float = x0 * np.cos(omega * t) + v0 / omega * np.sin(omega * t)
205
+ v: float = -x0 * omega * np.sin(omega * t) + v0 * np.cos(omega * t)
206
+
207
+ states.append(
208
+ State(instant, [x, v], Frame.GCRF(), state.get_coordinate_subsets())
209
+ )
210
+
211
+ return states
212
+
213
+ return state_fn
214
+
215
+
216
+ class TestLeastSquaresSolverStep:
217
+
218
+ def test_constructor(
219
+ self,
220
+ step: LeastSquaresSolver.Step,
221
+ ):
222
+ assert isinstance(step, LeastSquaresSolver.Step)
223
+
224
+ def test_getters(
225
+ self,
226
+ step: LeastSquaresSolver.Step,
227
+ rms_error: float,
228
+ x_hat: np.ndarray,
229
+ ):
230
+ assert step.rms_error == rms_error
231
+ assert np.array_equal(step.x_hat, x_hat)
232
+
233
+
234
+ class TestLeastSquaresSolverAnalysis:
235
+
236
+ def test_constructor(
237
+ self,
238
+ analysis: LeastSquaresSolver.Analysis,
239
+ ):
240
+ assert isinstance(analysis, LeastSquaresSolver.Analysis)
241
+
242
+ def test_getters(
243
+ self,
244
+ analysis: LeastSquaresSolver.Analysis,
245
+ ):
246
+ assert isinstance(analysis.rms_error, Real)
247
+ assert isinstance(analysis.observation_count, int)
248
+ assert isinstance(analysis.iteration_count, int)
249
+ assert isinstance(analysis.termination_criteria, String)
250
+ assert isinstance(analysis.estimated_state, State)
251
+ assert isinstance(analysis.estimated_covariance, np.ndarray)
252
+ assert isinstance(analysis.estimated_frisbee_covariance, np.ndarray)
253
+ assert isinstance(analysis.computed_observations, list)
254
+ assert isinstance(analysis.steps, list)
255
+
256
+ def test_compute_residual_states(
257
+ self,
258
+ analysis: LeastSquaresSolver.Analysis,
259
+ observations: list[State],
260
+ ):
261
+ residuals: list[State] = analysis.compute_residual_states(
262
+ observations=observations
263
+ )
264
+
265
+ assert isinstance(residuals, list)
266
+ assert len(residuals) == len(observations)
267
+
268
+
269
+ class TestLeastSquaresSolver:
270
+ def test_constructor(
271
+ self,
272
+ least_squares_solver: LeastSquaresSolver,
273
+ ):
274
+ assert isinstance(least_squares_solver, LeastSquaresSolver)
275
+
276
+ def test_getters(
277
+ self,
278
+ least_squares_solver: LeastSquaresSolver,
279
+ max_iteration_count: int,
280
+ rms_update_threshold: float,
281
+ ):
282
+ assert least_squares_solver.get_max_iteration_count() == max_iteration_count
283
+ assert least_squares_solver.get_rms_update_threshold() == rms_update_threshold
284
+ assert least_squares_solver.get_finite_difference_solver() is not None
285
+
286
+ def test_solve_defaults(
287
+ self,
288
+ least_squares_solver: LeastSquaresSolver,
289
+ initial_guess: State,
290
+ observations: list[State],
291
+ state_generator: callable,
292
+ ):
293
+ analysis = least_squares_solver.solve(
294
+ initial_guess=initial_guess,
295
+ observations=observations,
296
+ state_generator=state_generator,
297
+ )
298
+
299
+ assert analysis is not None
300
+
301
+ def test_solve(
302
+ self,
303
+ least_squares_solver: LeastSquaresSolver,
304
+ initial_guess: State,
305
+ observations: list[State],
306
+ state_generator: callable,
307
+ initial_guess_sigmas: dict[CoordinateSubset, list[float]],
308
+ observation_sigmas: dict[CoordinateSubset, list[float]],
309
+ ):
310
+ analysis = least_squares_solver.solve(
311
+ initial_guess=initial_guess,
312
+ observations=observations,
313
+ state_generator=state_generator,
314
+ initial_guess_sigmas=initial_guess_sigmas,
315
+ observation_sigmas=observation_sigmas,
316
+ )
317
+
318
+ assert analysis is not None
319
+
320
+ def test_calculate_empirical_covariance(
321
+ self,
322
+ observations: list[State],
323
+ ):
324
+ covariance: np.ndarray = LeastSquaresSolver.calculate_empirical_covariance(
325
+ observations
326
+ )
327
+
328
+ assert isinstance(covariance, np.ndarray)
329
+ assert covariance.shape == (2, 2)
330
+
331
+ def test_default(self):
332
+ default_solver: LeastSquaresSolver = LeastSquaresSolver.default()
333
+
334
+ assert isinstance(default_solver, LeastSquaresSolver)
@@ -0,0 +1,161 @@
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 Instant
8
+ from ostk.physics.time import Duration
9
+ from ostk.physics.time import Interval
10
+ from ostk.physics.time import DateTime
11
+ from ostk.physics.time import Scale
12
+ from ostk.physics import Environment
13
+
14
+ from ostk.astrodynamics.trajectory import Orbit
15
+ from ostk.astrodynamics.trajectory.orbit.model import Kepler
16
+ from ostk.astrodynamics.trajectory.orbit.model.kepler import COE
17
+ from ostk.astrodynamics.access import Generator
18
+ from ostk.astrodynamics.solver import TemporalConditionSolver
19
+ from ostk.astrodynamics.access import AccessTarget
20
+ from ostk.astrodynamics.access import VisibilityCriterion
21
+
22
+
23
+ @pytest.fixture
24
+ def temporal_condition_solver() -> TemporalConditionSolver:
25
+ return TemporalConditionSolver(
26
+ time_step=Duration.seconds(30.0),
27
+ tolerance=Duration.milliseconds(1.0),
28
+ maximum_iteration_count=1234,
29
+ )
30
+
31
+
32
+ @pytest.fixture
33
+ def interval() -> Interval:
34
+ return Interval.closed(
35
+ Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
36
+ Instant.date_time(DateTime(2018, 1, 1, 2, 0, 0), Scale.UTC),
37
+ )
38
+
39
+
40
+ class TestTemporalConditionSolver:
41
+ def test_constructor_success(self):
42
+ temporal_condition_solver = TemporalConditionSolver(
43
+ time_step=Duration.seconds(30.0),
44
+ tolerance=Duration.milliseconds(1.0),
45
+ maximum_iteration_count=1234,
46
+ )
47
+
48
+ assert isinstance(temporal_condition_solver, TemporalConditionSolver)
49
+
50
+ def test_getters_success(
51
+ self,
52
+ temporal_condition_solver: TemporalConditionSolver,
53
+ ):
54
+ assert temporal_condition_solver.get_time_step() == Duration.seconds(30.0)
55
+ assert temporal_condition_solver.get_tolerance() == Duration.milliseconds(1.0)
56
+ assert temporal_condition_solver.get_maximum_iteration_count() == 1234
57
+
58
+ def test_solve_success_one_condition_always_true(
59
+ self,
60
+ temporal_condition_solver: TemporalConditionSolver,
61
+ interval: Interval,
62
+ ):
63
+ solution: list[Interval] = temporal_condition_solver.solve(
64
+ condition=lambda _: True,
65
+ interval=interval,
66
+ )
67
+
68
+ assert isinstance(solution, list)
69
+ assert solution == [interval]
70
+
71
+ def test_solve_success_one_condition_always_false(
72
+ self,
73
+ temporal_condition_solver: TemporalConditionSolver,
74
+ interval: Interval,
75
+ ):
76
+ solution: list[Interval] = temporal_condition_solver.solve(
77
+ condition=lambda _: False,
78
+ interval=interval,
79
+ )
80
+
81
+ assert isinstance(solution, list)
82
+ assert solution == []
83
+
84
+ def test_solve_success_multiple_conditions_always_true(
85
+ self,
86
+ temporal_condition_solver: TemporalConditionSolver,
87
+ interval: Interval,
88
+ ):
89
+ solution: list[Interval] = temporal_condition_solver.solve(
90
+ conditions=[
91
+ lambda _: True,
92
+ lambda _: True,
93
+ ],
94
+ interval=interval,
95
+ )
96
+
97
+ assert isinstance(solution, list)
98
+ assert solution == [interval]
99
+
100
+ def test_solve_success_multiple_conditions_one_always_false(
101
+ self,
102
+ temporal_condition_solver: TemporalConditionSolver,
103
+ interval: Interval,
104
+ ):
105
+ solution: list[Interval] = temporal_condition_solver.solve(
106
+ conditions=[
107
+ lambda _: True,
108
+ lambda _: False,
109
+ ],
110
+ interval=interval,
111
+ )
112
+
113
+ assert isinstance(solution, list)
114
+ assert solution == []
115
+
116
+ def test_solve_success_using_access_generator(
117
+ self,
118
+ temporal_condition_solver: TemporalConditionSolver,
119
+ interval: Interval,
120
+ ):
121
+ environment = Environment.default()
122
+
123
+ generator = Generator(
124
+ environment=environment,
125
+ )
126
+
127
+ earth = environment.access_celestial_object_with_name("Earth")
128
+
129
+ trajectory = Orbit(
130
+ model=Kepler(
131
+ coe=COE(
132
+ semi_major_axis=Length.kilometers(7000.0),
133
+ eccentricity=0.0,
134
+ inclination=Angle.degrees(45.0),
135
+ raan=Angle.degrees(0.0),
136
+ aop=Angle.degrees(0.0),
137
+ true_anomaly=Angle.degrees(0.0),
138
+ ),
139
+ epoch=Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC),
140
+ celestial_object=earth,
141
+ perturbation_type=Kepler.PerturbationType.No,
142
+ ),
143
+ celestial_object=earth,
144
+ )
145
+
146
+ visibility_criterion: VisibilityCriterion = (
147
+ VisibilityCriterion.from_line_of_sight(environment)
148
+ )
149
+
150
+ access_target = AccessTarget.from_trajectory(visibility_criterion, trajectory)
151
+
152
+ solution: list[Interval] = temporal_condition_solver.solve(
153
+ condition=generator.get_condition_function(
154
+ access_target=access_target,
155
+ to_trajectory=trajectory,
156
+ ),
157
+ interval=interval,
158
+ )
159
+
160
+ assert isinstance(solution, list)
161
+ assert solution == [interval]