Skip to content

Commit c5a1258

Browse files
committed
add axial_accelaration and test.
1 parent 170e89c commit c5a1258

File tree

2 files changed

+289
-53
lines changed

2 files changed

+289
-53
lines changed

rocketpy/simulation/flight.py

Lines changed: 258 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
# pylint: disable=too-many-lines
2+
import json
23
import math
34
import warnings
45
from copy import deepcopy
56
from functools import cached_property
67

78
import numpy as np
9+
import simplekml
810
from scipy.integrate import BDF, DOP853, LSODA, RK23, RK45, OdeSolver, Radau
911

10-
from rocketpy.simulation.flight_data_exporter import FlightDataExporter
11-
1212
from ..mathutils.function import Function, funcify_method
1313
from ..mathutils.vector_matrix import Matrix, Vector
1414
from ..plots.flight_plots import _FlightPlots
1515
from ..prints.flight_prints import _FlightPrints
1616
from ..tools import (
1717
calculate_cubic_hermite_coefficients,
18-
deprecated,
1918
euler313_to_quaternions,
2019
find_closest,
2120
find_root_linear_interpolation,
@@ -1130,7 +1129,6 @@ def __init_solution_monitors(self):
11301129
self.out_of_rail_time_index = 0
11311130
self.out_of_rail_state = np.array([0])
11321131
self.apogee_state = np.array([0])
1133-
self.apogee = 0
11341132
self.apogee_time = 0
11351133
self.x_impact = 0
11361134
self.y_impact = 0
@@ -2470,6 +2468,15 @@ def max_acceleration(self):
24702468
"""Maximum acceleration reached by the rocket."""
24712469
max_acceleration_time_index = np.argmax(self.acceleration[:, 1])
24722470
return self.acceleration[max_acceleration_time_index, 1]
2471+
2472+
@cached_property
2473+
def axial_acceleration(self):
2474+
"""Axial acceleration magnitude as a function of time."""
2475+
return (
2476+
self.ax * self.attitude_vector_x
2477+
+ self.ay * self.attitude_vector_y
2478+
+ self.az * self.attitude_vector_z
2479+
)
24732480

24742481
@funcify_method("Time (s)", "Horizontal Speed (m/s)")
24752482
def horizontal_speed(self):
@@ -3266,72 +3273,270 @@ def calculate_stall_wind_velocity(self, stall_angle): # TODO: move to utilities
32663273
+ f" of attack exceeds {stall_angle:.3f}°: {w_v:.3f} m/s"
32673274
)
32683275

3269-
@deprecated(
3270-
reason="Moved to FlightDataExporter.export_pressures()",
3271-
version="v1.12.0",
3272-
alternative="rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_pressures",
3273-
)
3274-
def export_pressures(self, file_name, time_step):
3275-
"""
3276-
.. deprecated:: 1.11
3277-
Use :class:`rocketpy.simulation.flight_data_exporter.FlightDataExporter`
3278-
and call ``.export_pressures(...)``.
3276+
def export_pressures(self, file_name, time_step): # TODO: move out
3277+
"""Exports the pressure experienced by the rocket during the flight to
3278+
an external file, the '.csv' format is recommended, as the columns will
3279+
be separated by commas. It can handle flights with or without
3280+
parachutes, although it is not possible to get a noisy pressure signal
3281+
if no parachute is added.
3282+
3283+
If a parachute is added, the file will contain 3 columns: time in
3284+
seconds, clean pressure in Pascals and noisy pressure in Pascals.
3285+
For flights without parachutes, the third column will be discarded
3286+
3287+
This function was created especially for the 'Projeto Jupiter'
3288+
Electronics Subsystems team and aims to help in configuring
3289+
micro-controllers.
3290+
3291+
Parameters
3292+
----------
3293+
file_name : string
3294+
The final file name,
3295+
time_step : float
3296+
Time step desired for the final file
3297+
3298+
Return
3299+
------
3300+
None
32793301
"""
3280-
return FlightDataExporter(self).export_pressures(file_name, time_step)
3302+
time_points = np.arange(0, self.t_final, time_step)
3303+
# pylint: disable=W1514, E1121
3304+
with open(file_name, "w") as file:
3305+
if len(self.rocket.parachutes) == 0:
3306+
print("No parachutes in the rocket, saving static pressure.")
3307+
for t in time_points:
3308+
file.write(f"{t:f}, {self.pressure.get_value_opt(t):.5f}\n")
3309+
else:
3310+
for parachute in self.rocket.parachutes:
3311+
for t in time_points:
3312+
p_cl = parachute.clean_pressure_signal_function.get_value_opt(t)
3313+
p_ns = parachute.noisy_pressure_signal_function.get_value_opt(t)
3314+
file.write(f"{t:f}, {p_cl:.5f}, {p_ns:.5f}\n")
3315+
# We need to save only 1 parachute data
3316+
break
32813317

3282-
@deprecated(
3283-
reason="Moved to FlightDataExporter.export_data()",
3284-
version="v1.12.0",
3285-
alternative="rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_data",
3286-
)
32873318
def export_data(self, file_name, *variables, time_step=None):
3319+
"""Exports flight data to a comma separated value file (.csv).
3320+
3321+
Data is exported in columns, with the first column representing time
3322+
steps. The first line of the file is a header line, specifying the
3323+
meaning of each column and its units.
3324+
3325+
Parameters
3326+
----------
3327+
file_name : string
3328+
The file name or path of the exported file. Example: flight_data.csv
3329+
Do not use forbidden characters, such as / in Linux/Unix and
3330+
`<, >, :, ", /, \\, | ?, *` in Windows.
3331+
variables : strings, optional
3332+
Names of the data variables which shall be exported. Must be Flight
3333+
class attributes which are instances of the Function class. Usage
3334+
example: test_flight.export_data('test.csv', 'z', 'angle_of_attack',
3335+
'mach_number').
3336+
time_step : float, optional
3337+
Time step desired for the data. If None, all integration time steps
3338+
will be exported. Otherwise, linear interpolation is carried out to
3339+
calculate values at the desired time steps. Example: 0.001.
32883340
"""
3289-
.. deprecated:: 1.11
3290-
Use :class:`rocketpy.simulation.flight_data_exporter.FlightDataExporter`
3291-
and call ``.export_data(...)``.
3292-
"""
3293-
return FlightDataExporter(self).export_data(
3294-
file_name, *variables, time_step=time_step
3341+
# TODO: we should move this method to outside of class.
3342+
3343+
# Fast evaluation for the most basic scenario
3344+
if time_step is None and len(variables) == 0:
3345+
np.savetxt(
3346+
file_name,
3347+
self.solution,
3348+
fmt="%.6f",
3349+
delimiter=",",
3350+
header=""
3351+
"Time (s),"
3352+
"X (m),"
3353+
"Y (m),"
3354+
"Z (m),"
3355+
"E0,"
3356+
"E1,"
3357+
"E2,"
3358+
"E3,"
3359+
"W1 (rad/s),"
3360+
"W2 (rad/s),"
3361+
"W3 (rad/s)",
3362+
)
3363+
return
3364+
3365+
# Not so fast evaluation for general case
3366+
if variables is None:
3367+
variables = [
3368+
"x",
3369+
"y",
3370+
"z",
3371+
"vx",
3372+
"vy",
3373+
"vz",
3374+
"e0",
3375+
"e1",
3376+
"e2",
3377+
"e3",
3378+
"w1",
3379+
"w2",
3380+
"w3",
3381+
]
3382+
3383+
if time_step is None:
3384+
time_points = self.time
3385+
else:
3386+
time_points = np.arange(self.t_initial, self.t_final, time_step)
3387+
3388+
exported_matrix = [time_points]
3389+
exported_header = "Time (s)"
3390+
3391+
# Loop through variables, get points and names (for the header)
3392+
for variable in variables:
3393+
if variable in self.__dict__:
3394+
variable_function = self.__dict__[variable]
3395+
# Deal with decorated Flight methods
3396+
else:
3397+
try:
3398+
obj = getattr(self.__class__, variable)
3399+
variable_function = obj.__get__(self, self.__class__)
3400+
except AttributeError as exc:
3401+
raise AttributeError(
3402+
f"Variable '{variable}' not found in Flight class"
3403+
) from exc
3404+
variable_points = variable_function(time_points)
3405+
exported_matrix += [variable_points]
3406+
exported_header += f", {variable_function.__outputs__[0]}"
3407+
3408+
exported_matrix = np.array(exported_matrix).T # Fix matrix orientation
3409+
3410+
np.savetxt(
3411+
file_name,
3412+
exported_matrix,
3413+
fmt="%.6f",
3414+
delimiter=",",
3415+
header=exported_header,
3416+
encoding="utf-8",
32953417
)
32963418

3297-
@deprecated(
3298-
reason="Moved to FlightDataExporter.export_sensor_data()",
3299-
version="v1.12.0",
3300-
alternative="rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_sensor_data",
3301-
)
33023419
def export_sensor_data(self, file_name, sensor=None):
3420+
"""Exports sensors data to a file. The file format can be either .csv or
3421+
.json.
3422+
3423+
Parameters
3424+
----------
3425+
file_name : str
3426+
The file name or path of the exported file. Example: flight_data.csv
3427+
Do not use forbidden characters, such as / in Linux/Unix and
3428+
`<, >, :, ", /, \\, | ?, *` in Windows.
3429+
sensor : Sensor, string, optional
3430+
The sensor to export data from. Can be given as a Sensor object or
3431+
as a string with the sensor name. If None, all sensors data will be
3432+
exported. Default is None.
33033433
"""
3304-
.. deprecated:: 1.11
3305-
Use :class:`rocketpy.simulation.flight_data_exporter.FlightDataExporter`
3306-
and call ``.export_sensor_data(...)``.
3307-
"""
3308-
return FlightDataExporter(self).export_sensor_data(file_name, sensor=sensor)
3434+
if sensor is None:
3435+
data_dict = {}
3436+
for used_sensor, measured_data in self.sensor_data.items():
3437+
data_dict[used_sensor.name] = measured_data
3438+
else:
3439+
# export data of only that sensor
3440+
data_dict = {}
3441+
3442+
if not isinstance(sensor, str):
3443+
data_dict[sensor.name] = self.sensor_data[sensor]
3444+
else: # sensor is a string
3445+
matching_sensors = [s for s in self.sensor_data if s.name == sensor]
3446+
3447+
if len(matching_sensors) > 1:
3448+
data_dict[sensor] = []
3449+
for s in matching_sensors:
3450+
data_dict[s.name].append(self.sensor_data[s])
3451+
elif len(matching_sensors) == 1:
3452+
data_dict[sensor] = self.sensor_data[matching_sensors[0]]
3453+
else:
3454+
raise ValueError("Sensor not found in the Flight.sensor_data.")
33093455

3310-
@deprecated(
3311-
reason="Moved to FlightDataExporter.export_kml()",
3312-
version="v1.12.0",
3313-
alternative="rocketpy.simulation.flight_data_exporter.FlightDataExporter.export_kml",
3314-
)
3315-
def export_kml(
3456+
with open(file_name, "w") as file:
3457+
json.dump(data_dict, file)
3458+
print("Sensor data exported to: ", file_name)
3459+
3460+
def export_kml( # TODO: should be moved out of this class.
33163461
self,
33173462
file_name="trajectory.kml",
33183463
time_step=None,
33193464
extrude=True,
33203465
color="641400F0",
33213466
altitude_mode="absolute",
33223467
):
3468+
"""Exports flight data to a .kml file, which can be opened with Google
3469+
Earth to display the rocket's trajectory.
3470+
3471+
Parameters
3472+
----------
3473+
file_name : string
3474+
The file name or path of the exported file. Example: flight_data.csv
3475+
time_step : float, optional
3476+
Time step desired for the data. If None, all integration time steps
3477+
will be exported. Otherwise, linear interpolation is carried out to
3478+
calculate values at the desired time steps. Example: 0.001.
3479+
extrude: bool, optional
3480+
To be used if you want to project the path over ground by using an
3481+
extruded polygon. In case False only the linestring containing the
3482+
flight path will be created. Default is True.
3483+
color : str, optional
3484+
Color of your trajectory path, need to be used in specific kml
3485+
format. Refer to http://www.zonums.com/gmaps/kml_color/ for more
3486+
info.
3487+
altitude_mode: str
3488+
Select elevation values format to be used on the kml file. Use
3489+
'relativetoground' if you want use Above Ground Level elevation, or
3490+
'absolute' if you want to parse elevation using Above Sea Level.
3491+
Default is 'relativetoground'. Only works properly if the ground
3492+
level is flat. Change to 'absolute' if the terrain is to irregular
3493+
or contains mountains.
33233494
"""
3324-
.. deprecated:: 1.11
3325-
Use :class:`rocketpy.simulation.flight_data_exporter.FlightDataExporter`
3326-
and call ``.export_kml(...)``.
3327-
"""
3328-
return FlightDataExporter(self).export_kml(
3329-
file_name=file_name,
3330-
time_step=time_step,
3331-
extrude=extrude,
3332-
color=color,
3333-
altitude_mode=altitude_mode,
3334-
)
3495+
# Define time points vector
3496+
if time_step is None:
3497+
time_points = self.time
3498+
else:
3499+
time_points = np.arange(self.t_initial, self.t_final + time_step, time_step)
3500+
# Open kml file with simplekml library
3501+
kml = simplekml.Kml(open=1)
3502+
trajectory = kml.newlinestring(name="Rocket Trajectory - Powered by RocketPy")
3503+
3504+
if altitude_mode == "relativetoground":
3505+
# In this mode the elevation data will be the Above Ground Level
3506+
# elevation. Only works properly if the ground level is similar to
3507+
# a plane, i.e. it might not work well if the terrain has mountains
3508+
coords = [
3509+
(
3510+
self.longitude.get_value_opt(t),
3511+
self.latitude.get_value_opt(t),
3512+
self.altitude.get_value_opt(t),
3513+
)
3514+
for t in time_points
3515+
]
3516+
trajectory.coords = coords
3517+
trajectory.altitudemode = simplekml.AltitudeMode.relativetoground
3518+
else: # altitude_mode == 'absolute'
3519+
# In this case the elevation data will be the Above Sea Level elevation
3520+
# Ensure you use the correct value on self.env.elevation, otherwise
3521+
# the trajectory path can be offset from ground
3522+
coords = [
3523+
(
3524+
self.longitude.get_value_opt(t),
3525+
self.latitude.get_value_opt(t),
3526+
self.z.get_value_opt(t),
3527+
)
3528+
for t in time_points
3529+
]
3530+
trajectory.coords = coords
3531+
trajectory.altitudemode = simplekml.AltitudeMode.absolute
3532+
# Modify style of trajectory linestring
3533+
trajectory.style.linestyle.color = color
3534+
trajectory.style.polystyle.color = color
3535+
if extrude:
3536+
trajectory.extrude = 1
3537+
# Save the KML
3538+
kml.save(file_name)
3539+
print("File ", file_name, " saved with success!")
33353540

33363541
def info(self):
33373542
"""Prints out a summary of the data available about the Flight."""

0 commit comments

Comments
 (0)