open-space-toolkit-astrodynamics 15.0.0__py313-none-manylinux2014_aarch64.whl → 15.2.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.
@@ -9,6 +9,7 @@ from ostk.physics import Environment
9
9
  from ostk.physics.time import Instant
10
10
  from ostk.physics.time import DateTime
11
11
  from ostk.physics.time import Scale
12
+ from ostk.physics.unit import Angle
12
13
 
13
14
  from ostk.astrodynamics.access import VisibilityCriterion
14
15
 
@@ -152,9 +153,11 @@ class TestVisibilityCriterion:
152
153
  elevation_criterion = VisibilityCriterion.ElevationInterval(elevation_interval)
153
154
  elevation_valid = np.pi / 8 # 22.5 degrees
154
155
  assert elevation_criterion.is_satisfied(elevation_valid) is True
156
+ assert elevation_criterion.is_satisfied(Angle.radians(elevation_valid)) is True
155
157
 
156
158
  elevation_invalid = np.pi / 2 # 90 degrees
157
159
  assert elevation_criterion.is_satisfied(elevation_invalid) is False
160
+ assert elevation_criterion.is_satisfied(Angle.radians(elevation_invalid)) is False
158
161
 
159
162
  def test_line_of_sight_is_satisfied(
160
163
  self,
@@ -0,0 +1,261 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ import pandas as pd
6
+
7
+ import numpy as np
8
+
9
+ from ostk.physics import Environment
10
+ from ostk.physics.time import Instant
11
+ from ostk.physics.coordinate import Frame
12
+ from ostk.physics.environment.object.celestial import Earth
13
+
14
+ from ostk.astrodynamics.solver import LeastSquaresSolver
15
+ from ostk.astrodynamics.trajectory import State
16
+ from ostk.astrodynamics.trajectory import Orbit
17
+ from ostk.astrodynamics.trajectory import Propagator
18
+ from ostk.astrodynamics.trajectory.state import NumericalSolver
19
+ from ostk.astrodynamics.trajectory.state import CoordinateSubset
20
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianPosition
21
+ from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianVelocity
22
+ from ostk.astrodynamics.estimator import OrbitDeterminationSolver
23
+ from ostk.astrodynamics.dataframe import generate_states_from_dataframe
24
+
25
+
26
+ @pytest.fixture
27
+ def environment() -> Environment:
28
+ return Environment(central_celestial_object=Earth.EGM96(10, 10), objects=[])
29
+
30
+
31
+ @pytest.fixture
32
+ def numerical_solver() -> NumericalSolver:
33
+ return NumericalSolver.default()
34
+
35
+
36
+ @pytest.fixture
37
+ def least_squares_solver() -> LeastSquaresSolver:
38
+ return LeastSquaresSolver.default()
39
+
40
+
41
+ @pytest.fixture
42
+ def orbit_determination_solver(
43
+ environment: Environment,
44
+ numerical_solver: NumericalSolver,
45
+ least_squares_solver: LeastSquaresSolver,
46
+ ) -> OrbitDeterminationSolver:
47
+ return OrbitDeterminationSolver(
48
+ environment=environment,
49
+ numerical_solver=numerical_solver,
50
+ solver=least_squares_solver,
51
+ )
52
+
53
+
54
+ @pytest.fixture
55
+ def coordinate_subsets() -> list[CoordinateSubset]:
56
+ return [
57
+ CartesianPosition.default(),
58
+ CartesianVelocity.default(),
59
+ ]
60
+
61
+
62
+ @pytest.fixture
63
+ def initial_guess(
64
+ observations: list[State],
65
+ ) -> State:
66
+ return observations[0]
67
+
68
+
69
+ @pytest.fixture
70
+ def observations() -> list[State]:
71
+ return generate_states_from_dataframe(
72
+ pd.read_csv(
73
+ "/app/test/OpenSpaceToolkit/Astrodynamics/Estimator/OrbitDeterminationSolverData/gnss_data.csv"
74
+ ),
75
+ reference_frame=Frame.ITRF(),
76
+ )
77
+
78
+
79
+ @pytest.fixture
80
+ def initial_guess_sigmas(
81
+ coordinate_subsets: list[CoordinateSubset],
82
+ ) -> dict[CoordinateSubset, list[float]]:
83
+ return {
84
+ coordinate_subsets[0]: [1e-1, 1e-1, 1e-1],
85
+ coordinate_subsets[1]: [1e-2, 1e-2, 1e-2],
86
+ }
87
+
88
+
89
+ @pytest.fixture
90
+ def observation_sigmas(
91
+ coordinate_subsets: list[CoordinateSubset],
92
+ ) -> dict[CoordinateSubset, list[float]]:
93
+ return {
94
+ coordinate_subsets[0]: [1e-1, 1e-1, 1e-1],
95
+ coordinate_subsets[1]: [1e-2, 1e-2, 1e-2],
96
+ }
97
+
98
+
99
+ @pytest.fixture
100
+ def rms_error() -> float:
101
+ return 1.0
102
+
103
+
104
+ @pytest.fixture
105
+ def x_hat() -> np.ndarray:
106
+ return np.array([1.0, 0.0])
107
+
108
+
109
+ @pytest.fixture
110
+ def step(
111
+ rms_error: float,
112
+ x_hat: np.ndarray,
113
+ ) -> LeastSquaresSolver.Step:
114
+ return LeastSquaresSolver.Step(
115
+ rms_error=rms_error,
116
+ x_hat=x_hat,
117
+ )
118
+
119
+
120
+ @pytest.fixture
121
+ def observation_count() -> int:
122
+ return 5
123
+
124
+
125
+ @pytest.fixture
126
+ def termination_criteria() -> str:
127
+ return "RMS Update Threshold"
128
+
129
+
130
+ @pytest.fixture
131
+ def estimated_state() -> State:
132
+ return State(
133
+ Instant.J2000(),
134
+ [1.0, 0.0],
135
+ Frame.GCRF(),
136
+ [CoordinateSubset("Position", 1), CoordinateSubset("Velocity", 1)],
137
+ )
138
+
139
+
140
+ @pytest.fixture
141
+ def estimated_covariance() -> np.ndarray:
142
+ return np.array([[1.0, 0.0], [0.0, 1.0]])
143
+
144
+
145
+ @pytest.fixture
146
+ def estimated_frisbee_covariance() -> np.ndarray:
147
+ return np.array([[1.0, 0.0], [0.0, 1.0]])
148
+
149
+
150
+ @pytest.fixture
151
+ def computed_observations(
152
+ observations: list[State],
153
+ ) -> list[State]:
154
+ return observations
155
+
156
+
157
+ @pytest.fixture
158
+ def steps(step: LeastSquaresSolver.Step) -> list[LeastSquaresSolver.Step]:
159
+ return [step]
160
+
161
+
162
+ @pytest.fixture
163
+ def solver_analysis(
164
+ termination_criteria: str,
165
+ estimated_state: State,
166
+ estimated_covariance: np.ndarray,
167
+ estimated_frisbee_covariance: np.ndarray,
168
+ computed_observations: list[State],
169
+ steps: list[LeastSquaresSolver.Step],
170
+ ) -> LeastSquaresSolver.Analysis:
171
+ return LeastSquaresSolver.Analysis(
172
+ termination_criteria=termination_criteria,
173
+ estimated_state=estimated_state,
174
+ estimated_covariance=estimated_covariance,
175
+ estimated_frisbee_covariance=estimated_frisbee_covariance,
176
+ computed_observations=computed_observations,
177
+ steps=steps,
178
+ )
179
+
180
+
181
+ @pytest.fixture
182
+ def analysis(
183
+ initial_guess: State,
184
+ solver_analysis: LeastSquaresSolver.Analysis,
185
+ ) -> OrbitDeterminationSolver.Analysis:
186
+ return OrbitDeterminationSolver.Analysis(
187
+ estimated_state=initial_guess,
188
+ solver_analysis=solver_analysis,
189
+ )
190
+
191
+
192
+ class TestOrbitDeterminationSolverAnalysis:
193
+ def test_constructor(
194
+ self,
195
+ analysis: OrbitDeterminationSolver.Analysis,
196
+ ):
197
+ assert isinstance(analysis, OrbitDeterminationSolver.Analysis)
198
+
199
+ def test_properties(
200
+ self,
201
+ analysis: OrbitDeterminationSolver.Analysis,
202
+ ):
203
+ assert isinstance(analysis.estimated_state, State)
204
+ assert isinstance(analysis.solver_analysis, LeastSquaresSolver.Analysis)
205
+
206
+
207
+ class TestOrbitDeterminationSolver:
208
+ def test_constructor(
209
+ self,
210
+ orbit_determination_solver: OrbitDeterminationSolver,
211
+ ):
212
+ assert isinstance(orbit_determination_solver, OrbitDeterminationSolver)
213
+
214
+ def test_access_methods(
215
+ self,
216
+ orbit_determination_solver: OrbitDeterminationSolver,
217
+ ):
218
+ assert isinstance(orbit_determination_solver.access_environment(), Environment)
219
+ assert isinstance(orbit_determination_solver.access_propagator(), Propagator)
220
+ assert isinstance(orbit_determination_solver.access_solver(), LeastSquaresSolver)
221
+ assert isinstance(orbit_determination_solver.access_estimation_frame(), Frame)
222
+
223
+ def test_estimate(
224
+ self,
225
+ orbit_determination_solver: OrbitDeterminationSolver,
226
+ initial_guess: State,
227
+ observations: list[State],
228
+ coordinate_subsets: list[CoordinateSubset],
229
+ initial_guess_sigmas: dict[CoordinateSubset, list[float]],
230
+ observation_sigmas: dict[CoordinateSubset, list[float]],
231
+ ):
232
+ analysis: OrbitDeterminationSolver.Analysis = orbit_determination_solver.estimate(
233
+ initial_guess=initial_guess,
234
+ observations=observations,
235
+ estimation_coordinate_subsets=coordinate_subsets,
236
+ initial_guess_sigmas=initial_guess_sigmas,
237
+ observation_sigmas=observation_sigmas,
238
+ )
239
+
240
+ assert isinstance(analysis, OrbitDeterminationSolver.Analysis)
241
+ assert isinstance(analysis.estimated_state, State)
242
+ assert isinstance(analysis.solver_analysis, LeastSquaresSolver.Analysis)
243
+
244
+ def test_estimate_orbit(
245
+ self,
246
+ orbit_determination_solver: OrbitDeterminationSolver,
247
+ initial_guess: State,
248
+ observations: list[State],
249
+ coordinate_subsets: list[CoordinateSubset],
250
+ initial_guess_sigmas: dict[CoordinateSubset, list[float]],
251
+ observation_sigmas: dict[CoordinateSubset, list[float]],
252
+ ):
253
+ orbit: Orbit = orbit_determination_solver.estimate_orbit(
254
+ initial_guess=initial_guess,
255
+ observations=observations,
256
+ estimation_coordinate_subsets=coordinate_subsets,
257
+ initial_guess_sigmas=initial_guess_sigmas,
258
+ observation_sigmas=observation_sigmas,
259
+ )
260
+
261
+ assert isinstance(orbit, Orbit)
@@ -0,0 +1,215 @@
1
+ # Apache License 2.0
2
+
3
+ import pytest
4
+
5
+ import pandas as pd
6
+
7
+ from ostk.core.type import Real
8
+ from ostk.core.type import Integer
9
+
10
+ from ostk.physics.coordinate import Frame
11
+
12
+ from ostk.astrodynamics.solver import LeastSquaresSolver
13
+ from ostk.astrodynamics.trajectory import State
14
+ from ostk.astrodynamics.trajectory import StateBuilder
15
+ from ostk.astrodynamics.trajectory import Orbit
16
+ from ostk.astrodynamics.trajectory.orbit.model.sgp4 import TLE
17
+ from ostk.astrodynamics.estimator import TLESolver
18
+ from ostk.astrodynamics.dataframe import generate_states_from_dataframe
19
+
20
+
21
+ @pytest.fixture
22
+ def least_squares_solver() -> LeastSquaresSolver:
23
+ return LeastSquaresSolver.default()
24
+
25
+
26
+ @pytest.fixture
27
+ def tle_solver(least_squares_solver: LeastSquaresSolver) -> TLESolver:
28
+ return TLESolver(
29
+ solver=least_squares_solver,
30
+ satellite_number=25544, # ISS NORAD ID
31
+ international_designator="98067A", # ISS Int'l Designator
32
+ revolution_number=12345,
33
+ estimate_b_star=True,
34
+ )
35
+
36
+
37
+ @pytest.fixture
38
+ def initial_tle() -> TLE:
39
+ return TLE(
40
+ "1 25544U 98067A 22253.00000622 .00000000 00000-0 71655-1 0 02",
41
+ "2 25544 97.5641 21.8296 0012030 155.5301 309.4836 15.14446734123455",
42
+ )
43
+
44
+
45
+ @pytest.fixture
46
+ def initial_state(observations: list[State]) -> State:
47
+ return observations[0]
48
+
49
+
50
+ @pytest.fixture
51
+ def initial_state_with_b_star(observations: list[State]) -> tuple[State, float]:
52
+ return observations[0], 1e-4
53
+
54
+
55
+ @pytest.fixture
56
+ def observations() -> list[State]:
57
+ return generate_states_from_dataframe(
58
+ pd.read_csv(
59
+ "/app/test/OpenSpaceToolkit/Astrodynamics/Estimator/OrbitDeterminationSolverData/gnss_data.csv"
60
+ ),
61
+ reference_frame=Frame.ITRF(),
62
+ )
63
+
64
+
65
+ class TestTLESolver:
66
+ def test_constructor(
67
+ self,
68
+ tle_solver: TLESolver,
69
+ ):
70
+ assert isinstance(tle_solver, TLESolver)
71
+ assert tle_solver.access_satellite_number() == 25544
72
+ assert tle_solver.access_international_designator() == "98067A"
73
+ assert tle_solver.access_revolution_number() == 12345
74
+ assert tle_solver.access_estimate_b_star() is True
75
+
76
+ def test_constructor_defaults(self):
77
+ solver = TLESolver()
78
+ assert solver.access_satellite_number() == 0
79
+ assert solver.access_international_designator() == "00001A"
80
+ assert solver.access_revolution_number() == 0
81
+ assert solver.access_estimate_b_star() is True
82
+
83
+ def test_access_methods(self, tle_solver: TLESolver):
84
+ assert isinstance(tle_solver.access_solver(), LeastSquaresSolver)
85
+ assert isinstance(tle_solver.access_default_b_star(), Real)
86
+ assert isinstance(
87
+ tle_solver.access_first_derivative_mean_motion_divided_by_2(), Real
88
+ )
89
+ assert isinstance(
90
+ tle_solver.access_second_derivative_mean_motion_divided_by_6(), Real
91
+ )
92
+ assert isinstance(tle_solver.access_ephemeris_type(), Integer)
93
+ assert isinstance(tle_solver.access_element_set_number(), Integer)
94
+ assert isinstance(tle_solver.access_tle_state_builder(), StateBuilder)
95
+
96
+ def test_estimate_from_tle(
97
+ self,
98
+ tle_solver: TLESolver,
99
+ initial_tle: TLE,
100
+ observations: list[State],
101
+ ):
102
+ analysis: TLESolver.Analysis = tle_solver.estimate(
103
+ initial_guess=initial_tle,
104
+ observations=observations,
105
+ )
106
+
107
+ assert isinstance(analysis, TLESolver.Analysis)
108
+ assert isinstance(analysis.estimated_tle, TLE)
109
+ assert isinstance(analysis.solver_analysis, LeastSquaresSolver.Analysis)
110
+
111
+ assert analysis.solver_analysis.termination_criteria == "RMS Update Threshold"
112
+
113
+ def test_estimate_from_state_b_star(
114
+ self,
115
+ tle_solver: TLESolver,
116
+ initial_state: State,
117
+ observations: list[State],
118
+ ):
119
+ analysis: TLESolver.Analysis = tle_solver.estimate(
120
+ initial_guess=(initial_state, 1e-4),
121
+ observations=observations,
122
+ )
123
+ assert isinstance(analysis, TLESolver.Analysis)
124
+ assert isinstance(analysis.estimated_tle, TLE)
125
+ assert isinstance(analysis.solver_analysis, LeastSquaresSolver.Analysis)
126
+
127
+ assert analysis.solver_analysis.termination_criteria == "RMS Update Threshold"
128
+
129
+ def test_estimate_from_state(
130
+ self,
131
+ initial_state: State,
132
+ observations: list[State],
133
+ ):
134
+ tle_solver_no_b_star = TLESolver(
135
+ satellite_number=25544,
136
+ international_designator="98067A",
137
+ revolution_number=12345,
138
+ estimate_b_star=False,
139
+ )
140
+ analysis: TLESolver.Analysis = tle_solver_no_b_star.estimate(
141
+ initial_guess=initial_state,
142
+ observations=observations,
143
+ )
144
+ assert isinstance(analysis, TLESolver.Analysis)
145
+ assert isinstance(analysis.estimated_tle, TLE)
146
+ assert isinstance(analysis.solver_analysis, LeastSquaresSolver.Analysis)
147
+
148
+ assert analysis.solver_analysis.termination_criteria == "RMS Update Threshold"
149
+
150
+ def test_estimate_invalid_initial_guess(
151
+ self,
152
+ tle_solver: TLESolver,
153
+ observations: list[State],
154
+ ):
155
+ with pytest.raises(RuntimeError) as e:
156
+ tle_solver.estimate(initial_guess="invalid", observations=observations)
157
+ assert "Initial guess must be a TLE, tuple[State, float], or State." in str(
158
+ e.value
159
+ )
160
+
161
+ def test_estimate_invalid_state_only(
162
+ self,
163
+ tle_solver: TLESolver,
164
+ initial_state: State,
165
+ observations: list[State],
166
+ ):
167
+ with pytest.raises(RuntimeError) as e:
168
+ tle_solver.estimate(initial_guess=initial_state, observations=observations)
169
+ assert (
170
+ "Initial guess must be a TLE or (State, B*) when also estimating B*."
171
+ in str(e.value)
172
+ )
173
+
174
+ def test_estimate_no_observations(
175
+ self,
176
+ tle_solver: TLESolver,
177
+ initial_state: State,
178
+ ):
179
+ with pytest.raises(Exception):
180
+ tle_solver.estimate(initial_guess=initial_state, observations=[])
181
+
182
+ def test_estimate_orbit(
183
+ self,
184
+ initial_state: State,
185
+ observations: list[State],
186
+ ):
187
+ tle_solver_no_b_star = TLESolver(
188
+ satellite_number=25544,
189
+ international_designator="98067A",
190
+ revolution_number=12345,
191
+ estimate_b_star=False,
192
+ )
193
+ orbit: Orbit = tle_solver_no_b_star.estimate_orbit(
194
+ initial_guess=initial_state,
195
+ observations=observations,
196
+ )
197
+
198
+ assert isinstance(orbit, Orbit)
199
+
200
+ def test_fit_with_different_frames(
201
+ self,
202
+ tle_solver: TLESolver,
203
+ initial_state_with_b_star: tuple[State, float],
204
+ observations: list[State],
205
+ ):
206
+ # Convert observations to TEME frame
207
+ teme_observations = [obs.in_frame(Frame.TEME()) for obs in observations]
208
+
209
+ analysis: TLESolver.Analysis = tle_solver.estimate(
210
+ initial_guess=initial_state_with_b_star, observations=teme_observations
211
+ )
212
+ assert isinstance(analysis, TLESolver.Analysis)
213
+ assert isinstance(analysis.estimated_tle, TLE)
214
+
215
+ assert analysis.solver_analysis.termination_criteria == "RMS Update Threshold"