Skip to content

Commit b8d3d46

Browse files
committed
ENH: added .rpy file functionality (see issue 668)
This commit add 'save_to_rpy' and 'load_from_rpy' functions, that allows saving and loading flights.
1 parent a4b42c3 commit b8d3d46

7 files changed

Lines changed: 43777 additions & 20 deletions

File tree

rocketpy/_encoders.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import numpy as np
88

99
from rocketpy.mathutils.function import Function
10+
from rocketpy.prints.flight_prints import _FlightPrints
11+
from rocketpy.plots.flight_plots import _FlightPlots
1012

1113

1214
class RocketPyEncoder(json.JSONEncoder):
@@ -75,6 +77,7 @@ class RocketPyDecoder(json.JSONDecoder):
7577
different types of objects from a JSON supported format."""
7678

7779
def __init__(self, *args, **kwargs):
80+
self.resimulate = kwargs.pop("resimulate", False)
7881
super().__init__(object_hook=self.object_hook, *args, **kwargs)
7982

8083
def object_hook(self, obj):
@@ -84,7 +87,50 @@ def object_hook(self, obj):
8487
try:
8588
class_ = get_class_from_signature(signature)
8689

87-
if hasattr(class_, "from_dict"):
90+
if class_.__name__ == "Flight" and not self.resimulate:
91+
new_flight = class_.__new__(class_)
92+
new_flight.prints = _FlightPrints(new_flight)
93+
new_flight.plots = _FlightPlots(new_flight)
94+
new_flight.rocket = obj["rocket"]
95+
new_flight.env = obj["env"]
96+
new_flight.rail_length = obj["rail_length"]
97+
new_flight.inclination = obj["inclination"]
98+
new_flight.heading = obj["heading"]
99+
new_flight.terminate_on_apogee = obj["terminate_on_apogee"]
100+
new_flight.max_time = obj["max_time"]
101+
new_flight.max_time_step = obj["max_time_step"]
102+
new_flight.min_time_step = obj["min_time_step"]
103+
new_flight.rtol = obj["rtol"]
104+
new_flight.atol = obj["atol"]
105+
new_flight.time_overshoot = obj["time_overshoot"]
106+
new_flight.name = obj["name"]
107+
new_flight.solution = obj["solution"]
108+
new_flight.out_of_rail_time = obj["out_of_rail_time"]
109+
new_flight.apogee_time = obj["apogee_time"]
110+
new_flight.apogee = obj["apogee"]
111+
new_flight.parachute_events = obj["parachute_events"]
112+
new_flight.impact_state = obj["impact_state"]
113+
new_flight.impact_velocity = obj["impact_velocity"]
114+
new_flight.x_impact = obj["x_impact"]
115+
new_flight.y_impact = obj["y_impact"]
116+
new_flight.t_final = obj["t_final"]
117+
new_flight.flight_phases = obj["flight_phases"]
118+
new_flight.ax = obj["ax"]
119+
new_flight.ay = obj["ay"]
120+
new_flight.az = obj["az"]
121+
new_flight.out_of_rail_time_index = obj["out_of_rail_time_index"]
122+
new_flight.function_evaluations = obj["function_evaluations"]
123+
new_flight.alpha1 = obj["alpha1"]
124+
new_flight.alpha2 = obj["alpha2"]
125+
new_flight.alpha3 = obj["alpha3"]
126+
new_flight.R1 = obj["R1"]
127+
new_flight.R2 = obj["R2"]
128+
new_flight.R3 = obj["R3"]
129+
new_flight.M1= obj["M1"]
130+
new_flight.M2 = obj["M2"]
131+
new_flight.M3 = obj["M3"]
132+
return new_flight
133+
elif hasattr(class_, "from_dict"):
88134
return class_.from_dict(obj)
89135
else:
90136
# Filter keyword arguments
@@ -118,7 +164,7 @@ def get_class_signature(obj):
118164
Signature of the class.
119165
"""
120166
class_ = obj.__class__
121-
name = getattr(class_, '__qualname__', class_.__name__)
167+
name = getattr(class_, "__qualname__", class_.__name__)
122168

123169
return {"module": class_.__module__, "name": name}
124170

rocketpy/simulation/flight.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3433,24 +3433,42 @@ def to_dict(self, include_outputs=False):
34333433
"time_overshoot": self.time_overshoot,
34343434
"name": self.name,
34353435
"equations_of_motion": self.equations_of_motion,
3436+
"solution": self.solution, ##
3437+
"out_of_rail_time": self.out_of_rail_time, #
3438+
"apogee_time": self.apogee_time, #
3439+
"apogee": self.apogee, #
3440+
"parachute_events": self.parachute_events, ##
3441+
"impact_state": self.impact_state, #
3442+
"impact_velocity": self.impact_velocity, #
3443+
"x_impact": self.x_impact, #
3444+
"y_impact": self.y_impact, #
3445+
"t_final": self.t_final, ##
3446+
"flight_phases": self.flight_phases, ##
3447+
"ax": self.ax, #
3448+
"ay": self.ay, #
3449+
"az": self.az, #
3450+
"out_of_rail_time_index": self.out_of_rail_time_index, ##
3451+
"function_evaluations": self.function_evaluations, ##
3452+
"alpha1": self.alpha1, #
3453+
"alpha2": self.alpha2, #
3454+
"alpha3": self.alpha3, #
3455+
"R1": self.R1, ##
3456+
"R2": self.R2, ##
3457+
"R3": self.R3, ##
3458+
"M1": self.M1, ##
3459+
"M2": self.M2, ##
3460+
"M3": self.M3, ##
34363461
}
34373462

34383463
if include_outputs:
34393464
data.update(
34403465
{
34413466
"time": self.time,
3442-
"out_of_rail_time": self.out_of_rail_time,
34433467
"out_of_rail_velocity": self.out_of_rail_velocity,
34443468
"out_of_rail_state": self.out_of_rail_state,
3445-
"apogee": self.apogee,
3446-
"apogee_time": self.apogee_time,
34473469
"apogee_x": self.apogee_x,
34483470
"apogee_y": self.apogee_y,
34493471
"apogee_state": self.apogee_state,
3450-
"x_impact": self.x_impact,
3451-
"y_impact": self.y_impact,
3452-
"impact_velocity": self.impact_velocity,
3453-
"impact_state": self.impact_state,
34543472
"x": self.x,
34553473
"y": self.y,
34563474
"z": self.z,
@@ -3464,12 +3482,6 @@ def to_dict(self, include_outputs=False):
34643482
"w1": self.w1,
34653483
"w2": self.w2,
34663484
"w3": self.w3,
3467-
"ax": self.ax,
3468-
"ay": self.ay,
3469-
"az": self.az,
3470-
"alpha1": self.alpha1,
3471-
"alpha2": self.alpha2,
3472-
"alpha3": self.alpha3,
34733485
"altitude": self.altitude,
34743486
"mach_number": self.mach_number,
34753487
"stream_velocity_x": self.stream_velocity_x,

rocketpy/utilities.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
import inspect
33
import traceback
44
import warnings
5+
import json
6+
import os
57

8+
from importlib.metadata import version
9+
from datetime import date
610
import matplotlib.pyplot as plt
711
import numpy as np
812
from scipy.integrate import solve_ivp
@@ -12,6 +16,7 @@
1216
from .plots.plot_helpers import show_or_save_plot
1317
from .rocket.aero_surface import TrapezoidalFins
1418
from .simulation.flight import Flight
19+
from ._encoders import RocketPyEncoder, RocketPyDecoder
1520

1621

1722
def compute_cd_s_from_drop_test(
@@ -685,3 +690,63 @@ def get_instance_attributes(instance):
685690
if not inspect.ismethod(member[1]) and not member[0].startswith("__"):
686691
attributes_dict[member[0]] = member[1]
687692
return attributes_dict
693+
694+
695+
def save_to_rpy(flight: Flight, filename: str, include_output = False):
696+
"""Saves a .rpy file into the given path, containing key simulation informations to reproduce the results.
697+
698+
Parameters
699+
----------
700+
flight : rocketpy.Flight
701+
Flight object containing the rocket's flight data
702+
filename : str
703+
Path where the file will be saved in
704+
include_output : bool, optional
705+
If True, the function will include extra outputs into the file, by default False
706+
707+
Returns
708+
-------
709+
None
710+
"""
711+
file = os.path.splitext(filename)[0] + ".rpy"
712+
with open(file, "w") as f:
713+
data = {"date": str(date.today()), "version": version("rocketpy")}
714+
print(flight)
715+
data["simulation"] = flight
716+
print(data)
717+
print(flight.rocket)
718+
json.dump(
719+
data,
720+
f,
721+
cls=RocketPyEncoder,
722+
indent=2,
723+
include_outputs=include_output,
724+
)
725+
726+
727+
def load_from_rpy(filename: str, resimulate = False):
728+
"""Loads the saved data from the .rpy into a Flight object.
729+
730+
Parameters
731+
----------
732+
filename : str
733+
Path where the file to be loaded is
734+
resimulate : bool, optional
735+
If True, the function will resimulate the object Flight, by default False
736+
737+
Returns
738+
-------
739+
rocketpy.Flight
740+
Flight object containing simulation information from the .rpy file
741+
"""
742+
ext = os.path.splitext(os.path.basename(filename))[1]
743+
if ext == ".rpy":
744+
with open(filename, "r") as f:
745+
data = json.load(f)
746+
if data["version"] < version("rocketpy"):
747+
warnings.warn("The file was saved in an older version of RocketPy than the one in the current environment")
748+
simulation = json.dumps(data["simulation"])
749+
flight = json.loads(simulation, cls=RocketPyDecoder, resimulate=resimulate)
750+
return flight
751+
else:
752+
raise ValueError(f"Invalid file extension: {ext}. Allowed: .rpy")

0 commit comments

Comments
 (0)