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.
- open_space_toolkit_astrodynamics-17.2.0.dist-info/METADATA +30 -0
- open_space_toolkit_astrodynamics-17.2.0.dist-info/RECORD +151 -0
- open_space_toolkit_astrodynamics-17.2.0.dist-info/WHEEL +5 -0
- open_space_toolkit_astrodynamics-17.2.0.dist-info/top_level.txt +1 -0
- open_space_toolkit_astrodynamics-17.2.0.dist-info/zip-safe +1 -0
- ostk/__init__.py +1 -0
- ostk/astrodynamics/OpenSpaceToolkitAstrodynamicsPy.cpython-312-x86_64-linux-gnu.so +0 -0
- ostk/astrodynamics/__init__.py +11 -0
- ostk/astrodynamics/__init__.pyi +720 -0
- ostk/astrodynamics/access.pyi +577 -0
- ostk/astrodynamics/conjunction/__init__.pyi +121 -0
- ostk/astrodynamics/conjunction/close_approach.pyi +89 -0
- ostk/astrodynamics/conjunction/message/__init__.pyi +3 -0
- ostk/astrodynamics/conjunction/message/ccsds.pyi +705 -0
- ostk/astrodynamics/converters.py +130 -0
- ostk/astrodynamics/converters.pyi +58 -0
- ostk/astrodynamics/data/__init__.pyi +3 -0
- ostk/astrodynamics/data/provider.pyi +22 -0
- ostk/astrodynamics/dataframe.py +597 -0
- ostk/astrodynamics/display.py +281 -0
- ostk/astrodynamics/dynamics.pyi +311 -0
- ostk/astrodynamics/eclipse.pyi +70 -0
- ostk/astrodynamics/estimator.pyi +268 -0
- ostk/astrodynamics/event_condition.pyi +910 -0
- ostk/astrodynamics/flight/__init__.pyi +626 -0
- ostk/astrodynamics/flight/profile/__init__.pyi +99 -0
- ostk/astrodynamics/flight/profile/model.pyi +179 -0
- ostk/astrodynamics/flight/system.pyi +268 -0
- ostk/astrodynamics/guidance_law.pyi +416 -0
- ostk/astrodynamics/libopen-space-toolkit-astrodynamics.so.17 +0 -0
- ostk/astrodynamics/pytrajectory/__init__.py +1 -0
- ostk/astrodynamics/pytrajectory/__init__.pyi +3 -0
- ostk/astrodynamics/pytrajectory/pystate.py +263 -0
- ostk/astrodynamics/pytrajectory/pystate.pyi +66 -0
- ostk/astrodynamics/solver.pyi +432 -0
- ostk/astrodynamics/test/__init__.py +1 -0
- ostk/astrodynamics/test/access/__init__.py +1 -0
- ostk/astrodynamics/test/access/test_generator.py +319 -0
- ostk/astrodynamics/test/access/test_visibility_criterion.py +201 -0
- ostk/astrodynamics/test/conftest.py +119 -0
- ostk/astrodynamics/test/conjunction/close_approach/__init__.py +0 -0
- ostk/astrodynamics/test/conjunction/close_approach/test_generator.py +228 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/__init__.py +1 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/conftest.py +325 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/data/cdm.json +303 -0
- ostk/astrodynamics/test/conjunction/message/ccsds/test_cdm.py +416 -0
- ostk/astrodynamics/test/conjunction/test_close_approach.py +244 -0
- ostk/astrodynamics/test/data/provider/test_off_nadir.py +58 -0
- ostk/astrodynamics/test/dynamics/__init__.py +1 -0
- ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity.csv +565 -0
- ostk/astrodynamics/test/dynamics/data/Tabulated_Earth_Gravity_Truth.csv +100 -0
- ostk/astrodynamics/test/dynamics/test_atmospheric_drag.py +128 -0
- ostk/astrodynamics/test/dynamics/test_central_body_gravity.py +58 -0
- ostk/astrodynamics/test/dynamics/test_dynamics.py +50 -0
- ostk/astrodynamics/test/dynamics/test_position_derivative.py +51 -0
- ostk/astrodynamics/test/dynamics/test_tabulated.py +138 -0
- ostk/astrodynamics/test/dynamics/test_third_body_gravity.py +67 -0
- ostk/astrodynamics/test/dynamics/test_thruster.py +157 -0
- ostk/astrodynamics/test/eclipse/__init__.py +1 -0
- ostk/astrodynamics/test/eclipse/test_generator.py +138 -0
- ostk/astrodynamics/test/estimator/test_orbit_determination_solver.py +261 -0
- ostk/astrodynamics/test/estimator/test_tle_solver.py +216 -0
- ostk/astrodynamics/test/event_condition/test_angular_condition.py +113 -0
- ostk/astrodynamics/test/event_condition/test_boolean_condition.py +55 -0
- ostk/astrodynamics/test/event_condition/test_brouwer_lyddane_mean_long_condition.py +135 -0
- ostk/astrodynamics/test/event_condition/test_coe_condition.py +135 -0
- ostk/astrodynamics/test/event_condition/test_instant_condition.py +48 -0
- ostk/astrodynamics/test/event_condition/test_logical_condition.py +120 -0
- ostk/astrodynamics/test/event_condition/test_real_condition.py +50 -0
- ostk/astrodynamics/test/flight/__init__.py +1 -0
- ostk/astrodynamics/test/flight/profile/model/test_tabulated_profile.py +115 -0
- ostk/astrodynamics/test/flight/system/__init__.py +1 -0
- ostk/astrodynamics/test/flight/system/test_propulsion_system.py +64 -0
- ostk/astrodynamics/test/flight/system/test_satellite_system.py +83 -0
- ostk/astrodynamics/test/flight/system/test_satellite_system_builder.py +71 -0
- ostk/astrodynamics/test/flight/test_maneuver.py +231 -0
- ostk/astrodynamics/test/flight/test_profile.py +293 -0
- ostk/astrodynamics/test/flight/test_system.py +45 -0
- ostk/astrodynamics/test/guidance_law/test_constant_thrust.py +177 -0
- ostk/astrodynamics/test/guidance_law/test_guidance_law.py +60 -0
- ostk/astrodynamics/test/guidance_law/test_heterogeneous_guidance_law.py +164 -0
- ostk/astrodynamics/test/guidance_law/test_qlaw.py +209 -0
- ostk/astrodynamics/test/solvers/__init__.py +1 -0
- ostk/astrodynamics/test/solvers/test_finite_difference_solver.py +196 -0
- ostk/astrodynamics/test/solvers/test_least_squares_solver.py +334 -0
- ostk/astrodynamics/test/solvers/test_temporal_condition_solver.py +161 -0
- ostk/astrodynamics/test/test_access.py +128 -0
- ostk/astrodynamics/test/test_converters.py +290 -0
- ostk/astrodynamics/test/test_dataframe.py +1355 -0
- ostk/astrodynamics/test/test_display.py +184 -0
- ostk/astrodynamics/test/test_event_condition.py +80 -0
- ostk/astrodynamics/test/test_import.py +26 -0
- ostk/astrodynamics/test/test_root_solver.py +70 -0
- ostk/astrodynamics/test/test_trajectory.py +126 -0
- ostk/astrodynamics/test/test_utilities.py +338 -0
- ostk/astrodynamics/test/test_viewer.py +318 -0
- ostk/astrodynamics/test/trajectory/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/model/test_nadir_trajectory.py +87 -0
- ostk/astrodynamics/test/trajectory/model/test_tabulated_trajectory.py +303 -0
- ostk/astrodynamics/test/trajectory/model/test_target_scan_trajectory.py +126 -0
- ostk/astrodynamics/test/trajectory/orbit/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/conftest.py +18 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/data/opm_1.yaml +44 -0
- ostk/astrodynamics/test/trajectory/orbit/message/spacex/test_opm.py +108 -0
- ostk/astrodynamics/test/trajectory/orbit/models/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean.py +65 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_long.py +102 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_brouwer_lyddane_mean_short.py +102 -0
- ostk/astrodynamics/test/trajectory/orbit/models/kepler/test_coe.py +305 -0
- ostk/astrodynamics/test/trajectory/orbit/models/sgp4/__init__.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/sgp4/test_tle.py +337 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_kepler.py +130 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_modified_equinoctial.py +142 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_propagated.py +234 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_sgp4.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/models/test_tabulated.py +380 -0
- ostk/astrodynamics/test/trajectory/orbit/test_model.py +1 -0
- ostk/astrodynamics/test/trajectory/orbit/test_pass.py +75 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_angular_velocity.py +30 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_attitude_quaternion.py +18 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_acceleration.py +136 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_position.py +107 -0
- ostk/astrodynamics/test/trajectory/state/coordinate_subset/test_cartesian_velocity.py +115 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_broker.py +84 -0
- ostk/astrodynamics/test/trajectory/state/test_coordinate_subset.py +58 -0
- ostk/astrodynamics/test/trajectory/state/test_numerical_solver.py +316 -0
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_direction.py +81 -0
- ostk/astrodynamics/test/trajectory/test_local_orbital_frame_factory.py +119 -0
- ostk/astrodynamics/test/trajectory/test_model.py +1 -0
- ostk/astrodynamics/test/trajectory/test_orbit.py +212 -0
- ostk/astrodynamics/test/trajectory/test_propagator.py +452 -0
- ostk/astrodynamics/test/trajectory/test_segment.py +694 -0
- ostk/astrodynamics/test/trajectory/test_sequence.py +550 -0
- ostk/astrodynamics/test/trajectory/test_state.py +629 -0
- ostk/astrodynamics/test/trajectory/test_state_builder.py +172 -0
- ostk/astrodynamics/trajectory/__init__.pyi +1982 -0
- ostk/astrodynamics/trajectory/model.pyi +259 -0
- ostk/astrodynamics/trajectory/orbit/__init__.pyi +349 -0
- ostk/astrodynamics/trajectory/orbit/message/__init__.pyi +3 -0
- ostk/astrodynamics/trajectory/orbit/message/spacex.pyi +264 -0
- ostk/astrodynamics/trajectory/orbit/model/__init__.pyi +648 -0
- ostk/astrodynamics/trajectory/orbit/model/brouwerLyddaneMean.pyi +121 -0
- ostk/astrodynamics/trajectory/orbit/model/kepler.pyi +709 -0
- ostk/astrodynamics/trajectory/orbit/model/sgp4.pyi +330 -0
- ostk/astrodynamics/trajectory/state/__init__.pyi +402 -0
- ostk/astrodynamics/trajectory/state/coordinate_subset.pyi +208 -0
- ostk/astrodynamics/utilities.py +396 -0
- 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]
|