Skip to content

Commit 8e2127b

Browse files
committed
ENH: enhance modeling and usage of orbital features.
1 parent 86fa64a commit 8e2127b

50 files changed

Lines changed: 9240 additions & 2532 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/notebooks/orbital_flight_playground_hohman_geo.ipynb

Lines changed: 1657 additions & 0 deletions
Large diffs are not rendered by default.

docs/notebooks/orbital_flight_playground_ssto.ipynb

Lines changed: 1105 additions & 0 deletions
Large diffs are not rendered by default.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ requests
66
pytz
77
simplekml
88
dill
9+
spiceypy>=6.0

rocketpy/__init__.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,48 @@
22
from .control import _Controller
33
from .environment import (
44
Atmosphere,
5-
AnalyticalEphemeris,
5+
AtmosphereLayer,
66
AtmosphericState,
7-
CelestialBody,
87
DefaultGravity,
8+
Earth,
9+
EarthRadiationPressure,
910
Environment,
1011
EnvironmentAnalysis,
11-
ExponentialAtmosphere,
12+
ExponentialAtmosphereLayer,
1213
Gravity,
13-
HarrisPriesterAtmosphere,
14-
LayeredAtmosphere,
15-
NRLMSISE00,
14+
HarrisPriesterAtmosphereLayer,
15+
NRLMSISE00AtmosphereLayer,
16+
SomiglianaGravity,
17+
Space,
1618
SphericalGravity,
1719
SphericalHarmonicGravity,
1820
SpiceEphemeris,
19-
VacuumAtmosphere,
21+
ThirdBody,
2022
VerticalGravity,
23+
ZeroAtmosphereLayer,
2124
ZeroGravity,
2225
ZonalGravity,
2326
)
2427
from .mathutils import (
28+
FLAT_WGS84,
29+
GRS80,
30+
NUMBA_AVAILABLE,
31+
SIMPLE_WGS84,
32+
WGS72,
33+
WGS84,
34+
Datum,
2535
EarthDatum,
2636
Epoch,
37+
FlatEarthDatum,
2738
FlightState,
39+
FrameVector,
2840
Function,
29-
NUMBA_AVAILABLE,
3041
OrbitalElements,
3142
PiecewiseFunction,
3243
ReferenceFrame,
33-
WGS84,
44+
SimpleDatum,
3445
VectorFunction,
3546
funcify_method,
36-
gcrf_to_rtn_matrix,
37-
itrf_to_topocentric,
3847
numbify,
3948
reset_funcified_methods,
4049
)
@@ -75,23 +84,21 @@
7584
PointMassRocket,
7685
RailButtons,
7786
Rocket,
87+
Spacecraft,
7888
Tail,
7989
TrapezoidalFin,
8090
TrapezoidalFins,
91+
Vehicle,
8192
)
8293
from .sensitivity import SensitivityModel
8394
from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope
8495
from .simulation import (
85-
EarthRadiationPressure,
8696
Event,
8797
Flight,
8898
FlightOrbit,
99+
Mission,
89100
MonteCarlo,
90101
MultivariateRejectionSampler,
91-
PlanetaryRadiationPressure,
92-
RelativisticCorrection,
93-
SolarRadiationPressure,
94-
ThirdBodyGravity,
95102
)
96103
from .stochastic import (
97104
CustomSampler,

rocketpy/_encoders.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ def object_hook(self, obj):
140140

141141

142142
def set_minimal_flight_attributes(flight, obj):
143+
from rocketpy.environment.environment import Environment
144+
from rocketpy.environment.models import Earth
145+
from rocketpy.mathutils.epoch import Epoch
146+
from rocketpy.mathutils.flight_state import FlightState
147+
from rocketpy.mathutils.reference_frame import FlatEarthDatum
148+
143149
attributes = (
144150
"rocket",
145151
"env",
@@ -200,6 +206,42 @@ def set_minimal_flight_attributes(flight, obj):
200206
flight.ode_solver = obj.get("ode_solver", "LSODA")
201207
flight.simulation_mode = obj.get("simulation_mode", "6DOF")
202208

209+
# Restore the shared Flight contract for decoded objects that deliberately
210+
# bypass ``Flight.__init__``. Frame and domain ownership can be derived
211+
# from the serialized environment, avoiding duplicated datum state.
212+
flight.initial_environment = flight.env
213+
flight.earth = flight.env if isinstance(flight.env, Earth) else None
214+
flight.space = obj.get("space")
215+
flight.vehicle = flight.rocket
216+
if isinstance(flight.env, Earth):
217+
flight.datum = flight.env.datum
218+
elif isinstance(flight.env, Environment):
219+
weather_datum = flight.env.earth_datum
220+
flight.datum = FlatEarthDatum(
221+
name=f"Flat {weather_datum.name}",
222+
semi_major_axis=weather_datum.semi_major_axis,
223+
flattening=weather_datum.flattening,
224+
angular_velocity=weather_datum.angular_velocity,
225+
gravitational_parameter=weather_datum.gravitational_parameter,
226+
)
227+
else: # pragma: no cover - defensive support for old custom environments
228+
flight.datum = FlatEarthDatum()
229+
flight.reference_frame = flight.datum.integration_frame
230+
flight.initial_state = obj.get("initial_state")
231+
flight.start_epoch = (
232+
flight.initial_state.epoch
233+
if isinstance(flight.initial_state, FlightState)
234+
else getattr(flight.env, "epoch", Epoch.relative_origin())
235+
)
236+
flight.mission_time_offset = (
237+
flight.initial_state.elapsed_time
238+
if isinstance(flight.initial_state, FlightState)
239+
else 0.0
240+
)
241+
flight.forces = list(obj.get("forces") or ())
242+
flight._state_type = FlightState
243+
flight._launch_with_earth = False
244+
203245
# Controllers, sensors and custom events are derived by Flight.__init__
204246
# from the (already-decoded) rocket and constructor args; they are not
205247
# serialized separately. Mirror that derivation here so prints/plots that
@@ -208,6 +250,10 @@ def set_minimal_flight_attributes(flight, obj):
208250
flight._controllers = getattr(flight.rocket, "_controllers", [])[:]
209251
flight.sensors = flight.rocket.sensors.get_components()
210252
flight.sensors_by_name = flight.rocket.sensors_by_name
253+
flight.sensor_data = {
254+
sensor: list(getattr(sensor, "measured_data", ()))
255+
for sensor in flight.sensors
256+
}
211257
# TODO: custom_events are lost when loading from .rpy because they hold
212258
# user-defined callables that are not currently serialized. Add proper
213259
# serialization/deserialization for custom_events and restore them here

rocketpy/environment/__init__.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,56 @@
33
considered private and should be used with caution.
44
"""
55

6+
from .albedo import EarthRadiationPressure
67
from .atmosphere import (
78
Atmosphere,
9+
AtmosphereLayer,
810
AtmosphericState,
9-
ExponentialAtmosphere,
10-
FunctionAtmosphere,
11-
HarrisPriesterAtmosphere,
12-
LayeredAtmosphere,
13-
NRLMSISE00,
14-
VacuumAtmosphere,
11+
ExponentialAtmosphereLayer,
12+
FunctionAtmosphereLayer,
13+
HarrisPriesterAtmosphereLayer,
14+
NRLMSISE00AtmosphereLayer,
15+
ZeroAtmosphereLayer,
1516
)
16-
from .celestial_body import AnalyticalEphemeris, CelestialBody, SpiceEphemeris
1717
from .environment import Environment
1818
from .environment_analysis import EnvironmentAnalysis
1919
from .gravity import (
2020
DefaultGravity,
2121
Gravity,
22+
SomiglianaGravity,
2223
SphericalGravity,
2324
SphericalHarmonicGravity,
2425
VerticalGravity,
2526
ZeroGravity,
2627
ZonalGravity,
2728
)
29+
from .models import Earth, Space
30+
from .third_body import (
31+
SpiceEphemeris,
32+
ThirdBody,
33+
)
2834

2935
__all__ = [
30-
"AnalyticalEphemeris",
3136
"Atmosphere",
37+
"AtmosphereLayer",
3238
"AtmosphericState",
33-
"CelestialBody",
3439
"DefaultGravity",
3540
"Environment",
41+
"Earth",
42+
"EarthRadiationPressure",
3643
"EnvironmentAnalysis",
37-
"ExponentialAtmosphere",
38-
"FunctionAtmosphere",
44+
"ExponentialAtmosphereLayer",
45+
"FunctionAtmosphereLayer",
3946
"Gravity",
40-
"HarrisPriesterAtmosphere",
41-
"LayeredAtmosphere",
42-
"NRLMSISE00",
47+
"HarrisPriesterAtmosphereLayer",
48+
"NRLMSISE00AtmosphereLayer",
4349
"SphericalGravity",
50+
"SomiglianaGravity",
4451
"SphericalHarmonicGravity",
4552
"SpiceEphemeris",
46-
"VacuumAtmosphere",
53+
"Space",
54+
"ThirdBody",
55+
"ZeroAtmosphereLayer",
4756
"VerticalGravity",
4857
"ZeroGravity",
4958
"ZonalGravity",

rocketpy/environment/albedo.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""Earth radiation-pressure models."""
2+
3+
from __future__ import annotations
4+
5+
import math
6+
7+
import numpy as np
8+
9+
from rocketpy.environment.third_body import ASTRONOMICAL_UNIT, ThirdBody
10+
from rocketpy.mathutils.compilation import numbify
11+
from rocketpy.mathutils.frame_vector import FrameVector
12+
from rocketpy.mathutils.reference_frame import ReferenceFrame
13+
14+
15+
class EarthRadiationPressure:
16+
"""Knocke finite-element Earth albedo and infrared pressure model.
17+
18+
The visible terrestrial cap is integrated over a fixed 10-by-20 surface
19+
grid. Reflected shortwave radiation and emitted longwave radiation are
20+
both included.
21+
"""
22+
23+
name = "earth_radiation_pressure"
24+
25+
def __init__(self, sun):
26+
if not isinstance(sun, ThirdBody):
27+
raise TypeError("sun must be an explicit ThirdBody.")
28+
self.sun = sun
29+
30+
def acceleration(self, epoch, state, vehicle, earth):
31+
"""Return Earth radiation-pressure acceleration in GCRF."""
32+
sun_position = self.sun.position(epoch)
33+
incident_direction = FrameVector(-state.position, ReferenceFrame.GCRF)
34+
coefficient = vehicle.evaluate_radiation_coefficient(
35+
epoch, state, incident_direction
36+
)
37+
area = vehicle.evaluate_radiation_area(epoch, state, incident_direction)
38+
mass = vehicle.mass_at(state.elapsed_time)
39+
return self._evaluate_knocke_erp(
40+
epoch.jd_tdb,
41+
np.array(state.position, dtype=float, copy=True),
42+
np.array(sun_position, dtype=float, copy=True),
43+
float(area),
44+
float(coefficient),
45+
mass,
46+
)
47+
48+
@staticmethod
49+
@numbify(
50+
nopython=True,
51+
signature=(
52+
"float64[:](float64, float64[:], float64[:], float64, float64, float64)"
53+
),
54+
)
55+
def _evaluate_knocke_erp(jd, r_sat, r_sun, area_sat, coefficient, mass):
56+
"""Evaluate the finite-element Earth-radiation integration."""
57+
earth_radius = 6378136.0
58+
satellite_norm = math.sqrt(r_sat[0] ** 2 + r_sat[1] ** 2 + r_sat[2] ** 2)
59+
sun_norm = math.sqrt(r_sun[0] ** 2 + r_sun[1] ** 2 + r_sun[2] ** 2)
60+
if satellite_norm <= earth_radius:
61+
return np.zeros(3, dtype=np.float64)
62+
63+
sun_unit = r_sun / sun_norm
64+
satellite_unit = r_sat / satellite_norm
65+
solar_pressure = 4.56e-6 / (sun_norm / ASTRONOMICAL_UNIT) ** 2
66+
cap_angle = math.asin(earth_radius / satellite_norm)
67+
local_z = satellite_unit
68+
auxiliary = np.zeros(3, dtype=np.float64)
69+
if abs(local_z[2]) < 0.99:
70+
auxiliary[2] = 1.0
71+
else:
72+
auxiliary[0] = 1.0
73+
local_x = np.array(
74+
[
75+
local_z[1] * auxiliary[2] - local_z[2] * auxiliary[1],
76+
local_z[2] * auxiliary[0] - local_z[0] * auxiliary[2],
77+
local_z[0] * auxiliary[1] - local_z[1] * auxiliary[0],
78+
],
79+
dtype=np.float64,
80+
)
81+
local_x /= math.sqrt(local_x[0] ** 2 + local_x[1] ** 2 + local_x[2] ** 2)
82+
local_y = np.array(
83+
[
84+
local_z[1] * local_x[2] - local_z[2] * local_x[1],
85+
local_z[2] * local_x[0] - local_z[0] * local_x[2],
86+
local_z[0] * local_x[1] - local_z[1] * local_x[0],
87+
],
88+
dtype=np.float64,
89+
)
90+
91+
cosine_phase = math.cos(math.tau / 365.25 * (jd - 2451535.0))
92+
rings = 10
93+
sectors = 20
94+
delta_alpha = cap_angle / rings
95+
delta_beta = math.tau / sectors
96+
pressure_vector = np.zeros(3, dtype=np.float64)
97+
for ring in range(rings):
98+
alpha = (ring + 0.5) * delta_alpha
99+
sin_alpha = math.sin(alpha)
100+
cos_alpha = math.cos(alpha)
101+
area_element = earth_radius**2 * sin_alpha * delta_alpha * delta_beta
102+
for sector in range(sectors):
103+
beta = (sector + 0.5) * delta_beta
104+
normal = (
105+
sin_alpha * math.cos(beta) * local_x
106+
+ sin_alpha * math.sin(beta) * local_y
107+
+ cos_alpha * local_z
108+
)
109+
element_to_satellite = r_sat - earth_radius * normal
110+
distance = math.sqrt(
111+
element_to_satellite[0] ** 2
112+
+ element_to_satellite[1] ** 2
113+
+ element_to_satellite[2] ** 2
114+
)
115+
direction = element_to_satellite / distance
116+
satellite_cosine = (
117+
normal[0] * direction[0]
118+
+ normal[1] * direction[1]
119+
+ normal[2] * direction[2]
120+
)
121+
if satellite_cosine <= 0.0:
122+
continue
123+
latitude_sine = normal[2]
124+
legendre_p2 = 0.5 * (3.0 * latitude_sine**2 - 1.0)
125+
emissivity = max(
126+
0.0,
127+
min(
128+
1.0,
129+
0.68 - 0.07 * cosine_phase * latitude_sine - 0.18 * legendre_p2,
130+
),
131+
)
132+
surface_pressure = emissivity * solar_pressure / 4.0
133+
sun_cosine = (
134+
normal[0] * sun_unit[0]
135+
+ normal[1] * sun_unit[1]
136+
+ normal[2] * sun_unit[2]
137+
)
138+
if sun_cosine > 0.0:
139+
albedo = max(
140+
0.0,
141+
min(
142+
1.0,
143+
0.34
144+
+ 0.10 * cosine_phase * latitude_sine
145+
+ 0.29 * legendre_p2,
146+
),
147+
)
148+
surface_pressure += albedo * solar_pressure * sun_cosine
149+
pressure_vector += (
150+
surface_pressure
151+
/ math.pi
152+
* area_element
153+
* satellite_cosine
154+
/ distance**2
155+
* direction
156+
)
157+
return pressure_vector * (coefficient * area_sat / mass)

0 commit comments

Comments
 (0)