diff --git a/rocketpy/Flight.py b/rocketpy/Flight.py index be3da7ad2..9611395ed 100644 --- a/rocketpy/Flight.py +++ b/rocketpy/Flight.py @@ -17,6 +17,7 @@ from scipy import integrate from .Function import Function, funcify_method +from .plots.flight_plots import _FlightPlots from .prints.flight_prints import _FlightPrints try: @@ -648,6 +649,7 @@ def __init__( # Initialize prints and plots objects self.prints = _FlightPrints(self) + self.plots = _FlightPlots(self) # Initialize solver monitors self.functionEvaluations = [] @@ -2485,8 +2487,7 @@ def calculate_rail_button_forces(self): return F11, F12, F21, F22 - @cached_property - def __calculate_pressure_signal(self): + def _calculate_pressure_signal(self): """Calculate the pressure signal from the pressure sensor. It creates a SignalFunction attribute in the parachute object. Parachute works as a subclass of Rocket class. @@ -2594,606 +2595,6 @@ def calculateStallWindVelocity(self, stallAngle): return None - def plot3dTrajectory(self): - """Plot a 3D graph of the trajectory - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Get max and min x and y - maxZ = max(self.z[:, 1] - self.env.elevation) - maxX = max(self.x[:, 1]) - minX = min(self.x[:, 1]) - maxY = max(self.y[:, 1]) - minY = min(self.y[:, 1]) - maxXY = max(maxX, maxY) - minXY = min(minX, minY) - - # Create figure - fig1 = plt.figure(figsize=(9, 9)) - ax1 = plt.subplot(111, projection="3d") - ax1.plot(self.x[:, 1], self.y[:, 1], zs=0, zdir="z", linestyle="--") - ax1.plot( - self.x[:, 1], - self.z[:, 1] - self.env.elevation, - zs=minXY, - zdir="y", - linestyle="--", - ) - ax1.plot( - self.y[:, 1], - self.z[:, 1] - self.env.elevation, - zs=minXY, - zdir="x", - linestyle="--", - ) - ax1.plot( - self.x[:, 1], self.y[:, 1], self.z[:, 1] - self.env.elevation, linewidth="2" - ) - ax1.scatter(0, 0, 0) - ax1.set_xlabel("X - East (m)") - ax1.set_ylabel("Y - North (m)") - ax1.set_zlabel("Z - Altitude Above Ground Level (m)") - ax1.set_title("Flight Trajectory") - ax1.set_zlim3d([0, maxZ]) - ax1.set_ylim3d([minXY, maxXY]) - ax1.set_xlim3d([minXY, maxXY]) - ax1.view_init(15, 45) - plt.show() - - return None - - def plotLinearKinematicsData(self): - """Prints out all Kinematics graphs available about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Velocity and acceleration plots - fig2 = plt.figure(figsize=(9, 12)) - - ax1 = plt.subplot(414) - ax1.plot(self.vx[:, 0], self.vx[:, 1], color="#ff7f0e") - ax1.set_xlim(0, self.tFinal) - ax1.set_title("Velocity X | Acceleration X") - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Velocity X (m/s)", color="#ff7f0e") - ax1.tick_params("y", colors="#ff7f0e") - ax1.grid(True) - - ax1up = ax1.twinx() - ax1up.plot(self.ax[:, 0], self.ax[:, 1], color="#1f77b4") - ax1up.set_ylabel("Acceleration X (m/s²)", color="#1f77b4") - ax1up.tick_params("y", colors="#1f77b4") - - ax2 = plt.subplot(413) - ax2.plot(self.vy[:, 0], self.vy[:, 1], color="#ff7f0e") - ax2.set_xlim(0, self.tFinal) - ax2.set_title("Velocity Y | Acceleration Y") - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Velocity Y (m/s)", color="#ff7f0e") - ax2.tick_params("y", colors="#ff7f0e") - ax2.grid(True) - - ax2up = ax2.twinx() - ax2up.plot(self.ay[:, 0], self.ay[:, 1], color="#1f77b4") - ax2up.set_ylabel("Acceleration Y (m/s²)", color="#1f77b4") - ax2up.tick_params("y", colors="#1f77b4") - - ax3 = plt.subplot(412) - ax3.plot(self.vz[:, 0], self.vz[:, 1], color="#ff7f0e") - ax3.set_xlim(0, self.tFinal) - ax3.set_title("Velocity Z | Acceleration Z") - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Velocity Z (m/s)", color="#ff7f0e") - ax3.tick_params("y", colors="#ff7f0e") - ax3.grid(True) - - ax3up = ax3.twinx() - ax3up.plot(self.az[:, 0], self.az[:, 1], color="#1f77b4") - ax3up.set_ylabel("Acceleration Z (m/s²)", color="#1f77b4") - ax3up.tick_params("y", colors="#1f77b4") - - ax4 = plt.subplot(411) - ax4.plot(self.speed[:, 0], self.speed[:, 1], color="#ff7f0e") - ax4.set_xlim(0, self.tFinal) - ax4.set_title("Velocity Magnitude | Acceleration Magnitude") - ax4.set_xlabel("Time (s)") - ax4.set_ylabel("Velocity (m/s)", color="#ff7f0e") - ax4.tick_params("y", colors="#ff7f0e") - ax4.grid(True) - - ax4up = ax4.twinx() - ax4up.plot(self.acceleration[:, 0], self.acceleration[:, 1], color="#1f77b4") - ax4up.set_ylabel("Acceleration (m/s²)", color="#1f77b4") - ax4up.tick_params("y", colors="#1f77b4") - - plt.subplots_adjust(hspace=0.5) - plt.show() - return None - - def plotAttitudeData(self): - """Prints out all Angular position graphs available about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Get index of time before parachute event - if len(self.parachuteEvents) > 0: - eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag - eventTimeIndex = np.nonzero(self.time == eventTime)[0][0] - else: - eventTime = self.tFinal - eventTimeIndex = -1 - - # Angular position plots - fig3 = plt.figure(figsize=(9, 12)) - - ax1 = plt.subplot(411) - ax1.plot(self.e0[:, 0], self.e0[:, 1], label="$e_0$") - ax1.plot(self.e1[:, 0], self.e1[:, 1], label="$e_1$") - ax1.plot(self.e2[:, 0], self.e2[:, 1], label="$e_2$") - ax1.plot(self.e3[:, 0], self.e3[:, 1], label="$e_3$") - ax1.set_xlim(0, eventTime) - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Euler Parameters") - ax1.set_title("Euler Parameters") - ax1.legend() - ax1.grid(True) - - ax2 = plt.subplot(412) - ax2.plot(self.psi[:, 0], self.psi[:, 1]) - ax2.set_xlim(0, eventTime) - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("ψ (°)") - ax2.set_title("Euler Precession Angle") - ax2.grid(True) - - ax3 = plt.subplot(413) - ax3.plot(self.theta[:, 0], self.theta[:, 1], label="θ - Nutation") - ax3.set_xlim(0, eventTime) - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("θ (°)") - ax3.set_title("Euler Nutation Angle") - ax3.grid(True) - - ax4 = plt.subplot(414) - ax4.plot(self.phi[:, 0], self.phi[:, 1], label="φ - Spin") - ax4.set_xlim(0, eventTime) - ax4.set_xlabel("Time (s)") - ax4.set_ylabel("φ (°)") - ax4.set_title("Euler Spin Angle") - ax4.grid(True) - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - - def plotFlightPathAngleData(self): - """Prints out Flight path and Rocket Attitude angle graphs available - about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Get index of time before parachute event - if len(self.parachuteEvents) > 0: - eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag - eventTimeIndex = np.nonzero(self.time == eventTime)[0][0] - else: - eventTime = self.tFinal - eventTimeIndex = -1 - - # Path, Attitude and Lateral Attitude Angle - # Angular position plots - fig5 = plt.figure(figsize=(9, 6)) - - ax1 = plt.subplot(211) - ax1.plot(self.pathAngle[:, 0], self.pathAngle[:, 1], label="Flight Path Angle") - ax1.plot( - self.attitudeAngle[:, 0], - self.attitudeAngle[:, 1], - label="Rocket Attitude Angle", - ) - ax1.set_xlim(0, eventTime) - ax1.legend() - ax1.grid(True) - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Angle (°)") - ax1.set_title("Flight Path and Attitude Angle") - - ax2 = plt.subplot(212) - ax2.plot(self.lateralAttitudeAngle[:, 0], self.lateralAttitudeAngle[:, 1]) - ax2.set_xlim(0, eventTime) - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Lateral Attitude Angle (°)") - ax2.set_title("Lateral Attitude Angle") - ax2.grid(True) - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - - def plotAngularKinematicsData(self): - """Prints out all Angular velocity and acceleration graphs available - about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Get index of time before parachute event - if len(self.parachuteEvents) > 0: - eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag - eventTimeIndex = np.nonzero(self.time == eventTime)[0][0] - else: - eventTime = self.tFinal - eventTimeIndex = -1 - - # Angular velocity and acceleration plots - fig4 = plt.figure(figsize=(9, 9)) - ax1 = plt.subplot(311) - ax1.plot(self.w1[:, 0], self.w1[:, 1], color="#ff7f0e") - ax1.set_xlim(0, eventTime) - ax1.set_xlabel("Time (s)") - ax1.set_ylabel(r"Angular Velocity - ${\omega_1}$ (rad/s)", color="#ff7f0e") - ax1.set_title( - r"Angular Velocity ${\omega_1}$ | Angular Acceleration ${\alpha_1}$" - ) - ax1.tick_params("y", colors="#ff7f0e") - ax1.grid(True) - - ax1up = ax1.twinx() - ax1up.plot(self.alpha1[:, 0], self.alpha1[:, 1], color="#1f77b4") - ax1up.set_ylabel( - r"Angular Acceleration - ${\alpha_1}$ (rad/s²)", color="#1f77b4" - ) - ax1up.tick_params("y", colors="#1f77b4") - - ax2 = plt.subplot(312) - ax2.plot(self.w2[:, 0], self.w2[:, 1], color="#ff7f0e") - ax2.set_xlim(0, eventTime) - ax2.set_xlabel("Time (s)") - ax2.set_ylabel(r"Angular Velocity - ${\omega_2}$ (rad/s)", color="#ff7f0e") - ax2.set_title( - r"Angular Velocity ${\omega_2}$ | Angular Acceleration ${\alpha_2}$" - ) - ax2.tick_params("y", colors="#ff7f0e") - ax2.grid(True) - - ax2up = ax2.twinx() - ax2up.plot(self.alpha2[:, 0], self.alpha2[:, 1], color="#1f77b4") - ax2up.set_ylabel( - r"Angular Acceleration - ${\alpha_2}$ (rad/s²)", color="#1f77b4" - ) - ax2up.tick_params("y", colors="#1f77b4") - - ax3 = plt.subplot(313) - ax3.plot(self.w3[:, 0], self.w3[:, 1], color="#ff7f0e") - ax3.set_xlim(0, eventTime) - ax3.set_xlabel("Time (s)") - ax3.set_ylabel(r"Angular Velocity - ${\omega_3}$ (rad/s)", color="#ff7f0e") - ax3.set_title( - r"Angular Velocity ${\omega_3}$ | Angular Acceleration ${\alpha_3}$" - ) - ax3.tick_params("y", colors="#ff7f0e") - ax3.grid(True) - - ax3up = ax3.twinx() - ax3up.plot(self.alpha3[:, 0], self.alpha3[:, 1], color="#1f77b4") - ax3up.set_ylabel( - r"Angular Acceleration - ${\alpha_3}$ (rad/s²)", color="#1f77b4" - ) - ax3up.tick_params("y", colors="#1f77b4") - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - - def plotTrajectoryForceData(self): - """Prints out all Forces and Moments graphs available about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Get index of time before parachute event - if len(self.parachuteEvents) > 0: - eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag - eventTimeIndex = np.nonzero(self.time == eventTime)[0][0] - else: - eventTime = self.tFinal - eventTimeIndex = -1 - - # Rail Button Forces - fig6 = plt.figure(figsize=(9, 6)) - - ax1 = plt.subplot(211) - ax1.plot( - self.railButton1NormalForce[:, 0], - self.railButton1NormalForce[:, 1], - label="Upper Rail Button", - ) - ax1.plot( - self.railButton2NormalForce[:, 0], - self.railButton2NormalForce[:, 1], - label="Lower Rail Button", - ) - ax1.set_xlim(0, self.outOfRailTime if self.outOfRailTime > 0 else self.tFinal) - ax1.legend() - ax1.grid(True) - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Normal Force (N)") - ax1.set_title("Rail Buttons Normal Force") - - ax2 = plt.subplot(212) - ax2.plot( - self.railButton1ShearForce[:, 0], - self.railButton1ShearForce[:, 1], - label="Upper Rail Button", - ) - ax2.plot( - self.railButton2ShearForce[:, 0], - self.railButton2ShearForce[:, 1], - label="Lower Rail Button", - ) - ax2.set_xlim(0, self.outOfRailTime if self.outOfRailTime > 0 else self.tFinal) - ax2.legend() - ax2.grid(True) - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Shear Force (N)") - ax2.set_title("Rail Buttons Shear Force") - - plt.subplots_adjust(hspace=0.5) - plt.show() - - # Aerodynamic force and moment plots - fig7 = plt.figure(figsize=(9, 12)) - - ax1 = plt.subplot(411) - ax1.plot( - self.aerodynamicLift[:eventTimeIndex, 0], - self.aerodynamicLift[:eventTimeIndex, 1], - label="Resultant", - ) - ax1.plot(self.R1[:eventTimeIndex, 0], self.R1[:eventTimeIndex, 1], label="R1") - ax1.plot(self.R2[:eventTimeIndex, 0], self.R2[:eventTimeIndex, 1], label="R2") - ax1.set_xlim(0, eventTime) - ax1.legend() - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Lift Force (N)") - ax1.set_title("Aerodynamic Lift Resultant Force") - ax1.grid() - - ax2 = plt.subplot(412) - ax2.plot( - self.aerodynamicDrag[:eventTimeIndex, 0], - self.aerodynamicDrag[:eventTimeIndex, 1], - ) - ax2.set_xlim(0, eventTime) - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Drag Force (N)") - ax2.set_title("Aerodynamic Drag Force") - ax2.grid() - - ax3 = plt.subplot(413) - ax3.plot( - self.aerodynamicBendingMoment[:eventTimeIndex, 0], - self.aerodynamicBendingMoment[:eventTimeIndex, 1], - label="Resultant", - ) - ax3.plot(self.M1[:eventTimeIndex, 0], self.M1[:eventTimeIndex, 1], label="M1") - ax3.plot(self.M2[:eventTimeIndex, 0], self.M2[:eventTimeIndex, 1], label="M2") - ax3.set_xlim(0, eventTime) - ax3.legend() - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Bending Moment (N m)") - ax3.set_title("Aerodynamic Bending Resultant Moment") - ax3.grid() - - ax4 = plt.subplot(414) - ax4.plot( - self.aerodynamicSpinMoment[:eventTimeIndex, 0], - self.aerodynamicSpinMoment[:eventTimeIndex, 1], - ) - ax4.set_xlim(0, eventTime) - ax4.set_xlabel("Time (s)") - ax4.set_ylabel("Spin Moment (N m)") - ax4.set_title("Aerodynamic Spin Moment") - ax4.grid() - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - - def plotEnergyData(self): - """Prints out all Energy components graphs available about the Flight - - Returns - ------- - None - """ - # Get index of time before parachute event - if len(self.parachuteEvents) > 0: - eventTime = self.parachuteEvents[0][0] + self.parachuteEvents[0][1].lag - eventTimeIndex = np.nonzero(self.time == eventTime)[0][0] - else: - eventTime = self.tFinal - eventTimeIndex = -1 - - fig8 = plt.figure(figsize=(9, 9)) - - ax1 = plt.subplot(411) - ax1.plot( - self.kineticEnergy[:, 0], self.kineticEnergy[:, 1], label="Kinetic Energy" - ) - ax1.plot( - self.rotationalEnergy[:, 0], - self.rotationalEnergy[:, 1], - label="Rotational Energy", - ) - ax1.plot( - self.translationalEnergy[:, 0], - self.translationalEnergy[:, 1], - label="Translational Energy", - ) - ax1.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal) - ax1.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax1.set_title("Kinetic Energy Components") - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Energy (J)") - - ax1.legend() - ax1.grid() - - ax2 = plt.subplot(412) - ax2.plot(self.totalEnergy[:, 0], self.totalEnergy[:, 1], label="Total Energy") - ax2.plot( - self.kineticEnergy[:, 0], self.kineticEnergy[:, 1], label="Kinetic Energy" - ) - ax2.plot( - self.potentialEnergy[:, 0], - self.potentialEnergy[:, 1], - label="Potential Energy", - ) - ax2.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal) - ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax2.set_title("Total Mechanical Energy Components") - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Energy (J)") - ax2.legend() - ax2.grid() - - ax3 = plt.subplot(413) - ax3.plot(self.thrustPower[:, 0], self.thrustPower[:, 1], label="|Thrust Power|") - ax3.set_xlim(0, self.rocket.motor.burnOutTime) - ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax3.set_title("Thrust Absolute Power") - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Power (W)") - ax3.legend() - ax3.grid() - - ax4 = plt.subplot(414) - ax4.plot(self.dragPower[:, 0], -self.dragPower[:, 1], label="|Drag Power|") - ax4.set_xlim(0, self.apogeeTime if self.apogeeTime != 0.0 else self.tFinal) - ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax4.set_title("Drag Absolute Power") - ax4.set_xlabel("Time (s)") - ax4.set_ylabel("Power (W)") - ax4.legend() - ax4.grid() - - plt.subplots_adjust(hspace=1) - plt.show() - - return None - - def plotFluidMechanicsData(self): - """Prints out a summary of the Fluid Mechanics graphs available about - the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - # Trajectory Fluid Mechanics Plots - fig10 = plt.figure(figsize=(9, 12)) - - ax1 = plt.subplot(411) - ax1.plot(self.MachNumber[:, 0], self.MachNumber[:, 1]) - ax1.set_xlim(0, self.tFinal) - ax1.set_title("Mach Number") - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Mach Number") - ax1.grid() - - ax2 = plt.subplot(412) - ax2.plot(self.ReynoldsNumber[:, 0], self.ReynoldsNumber[:, 1]) - ax2.set_xlim(0, self.tFinal) - ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax2.set_title("Reynolds Number") - ax2.set_xlabel("Time (s)") - ax2.set_ylabel("Reynolds Number") - ax2.grid() - - ax3 = plt.subplot(413) - ax3.plot( - self.dynamicPressure[:, 0], - self.dynamicPressure[:, 1], - label="Dynamic Pressure", - ) - ax3.plot( - self.totalPressure[:, 0], self.totalPressure[:, 1], label="Total Pressure" - ) - ax3.plot(self.pressure[:, 0], self.pressure[:, 1], label="Static Pressure") - ax3.set_xlim(0, self.tFinal) - ax3.legend() - ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) - ax3.set_title("Total and Dynamic Pressure") - ax3.set_xlabel("Time (s)") - ax3.set_ylabel("Pressure (Pa)") - ax3.grid() - - ax4 = plt.subplot(414) - ax4.plot(self.angleOfAttack[:, 0], self.angleOfAttack[:, 1]) - # Make sure bottom and top limits are different - if self.outOfRailTime * self.angleOfAttack(self.outOfRailTime) != 0: - ax4.set_xlim(self.outOfRailTime, 10 * self.outOfRailTime + 1) - ax4.set_ylim(0, self.angleOfAttack(self.outOfRailTime)) - ax4.set_title("Angle of Attack") - ax4.set_xlabel("Time (s)") - ax4.set_ylabel("Angle of Attack (°)") - ax4.grid() - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - def calculateFinFlutterAnalysis(self, finThickness, shearModulus): """Calculate, create and plot the Fin Flutter velocity, based on the pressure profile provided by Atmospheric model selected. It considers the @@ -3348,111 +2749,6 @@ def calculateFinFlutterAnalysis(self, finThickness, shearModulus): return None - def plotStabilityAndControlData(self): - """Prints out Rocket Stability and Control parameters graphs available - about the Flight - - Parameters - ---------- - None - - Return - ------ - None - """ - - fig9 = plt.figure(figsize=(9, 6)) - - ax1 = plt.subplot(211) - ax1.plot(self.staticMargin[:, 0], self.staticMargin[:, 1]) - ax1.set_xlim(0, self.staticMargin[:, 0][-1]) - ax1.set_title("Static Margin") - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Static Margin (c)") - ax1.grid() - - ax2 = plt.subplot(212) - maxAttitude = max(self.attitudeFrequencyResponse[:, 1]) - maxAttitude = maxAttitude if maxAttitude != 0 else 1 - ax2.plot( - self.attitudeFrequencyResponse[:, 0], - self.attitudeFrequencyResponse[:, 1] / maxAttitude, - label="Attitude Angle", - ) - maxOmega1 = max(self.omega1FrequencyResponse[:, 1]) - maxOmega1 = maxOmega1 if maxOmega1 != 0 else 1 - ax2.plot( - self.omega1FrequencyResponse[:, 0], - self.omega1FrequencyResponse[:, 1] / maxOmega1, - label=r"$\omega_1$", - ) - maxOmega2 = max(self.omega2FrequencyResponse[:, 1]) - maxOmega2 = maxOmega2 if maxOmega2 != 0 else 1 - ax2.plot( - self.omega2FrequencyResponse[:, 0], - self.omega2FrequencyResponse[:, 1] / maxOmega2, - label=r"$\omega_2$", - ) - maxOmega3 = max(self.omega3FrequencyResponse[:, 1]) - maxOmega3 = maxOmega3 if maxOmega3 != 0 else 1 - ax2.plot( - self.omega3FrequencyResponse[:, 0], - self.omega3FrequencyResponse[:, 1] / maxOmega3, - label=r"$\omega_3$", - ) - ax2.set_title("Frequency Response") - ax2.set_xlabel("Frequency (Hz)") - ax2.set_ylabel("Amplitude Magnitude Normalized") - ax2.set_xlim(0, 5) - ax2.legend() - ax2.grid() - - plt.subplots_adjust(hspace=0.5) - plt.show() - - return None - - def plotPressureSignals(self): - """Prints out all Parachute Trigger Pressure Signals. - This function can be called also for plot pressure data for flights - without Parachutes, in this case the Pressure Signals will be simply - the pressure provided by the atmosphericModel, at Flight z positions. - This means that no noise will be considered if at least one parachute - has not been added. - - This function aims to help the engineer to visually check if there - are anomalies with the Flight Simulation. - - Parameters - ---------- - None - - Return - ------ - None - """ - - if len(self.rocket.parachutes) == 0: - plt.figure() - ax1 = plt.subplot(111) - ax1.plot(self.z[:, 0], self.env.pressure(self.z[:, 1])) - ax1.set_title("Pressure at Rocket's Altitude") - ax1.set_xlabel("Time (s)") - ax1.set_ylabel("Pressure (Pa)") - ax1.set_xlim(0, self.tFinal) - ax1.grid() - - plt.show() - - else: - for parachute in self.rocket.parachutes: - print("Parachute: ", parachute.name) - parachute.noiseSignalFunction() - parachute.noisyPressureSignalFunction() - parachute.cleanPressureSignalFunction() - - return None - def exportPressures(self, fileName, timeStep): """Exports the pressure experienced by the rocket during the flight to an external file, the '.csv' format is recommended, as the columns will @@ -3492,8 +2788,18 @@ def exportPressures(self, fileName, timeStep): else: for parachute in self.rocket.parachutes: for i in range(0, timePoints.size, 1): - pCl = parachute.cleanPressureSignalFunction(timePoints[i]) - pNs = parachute.noisyPressureSignalFunction(timePoints[i]) + pCl = Function( + parachute.cleanPressureSignal, + "Time (s)", + "Pressure - Without Noise (Pa)", + "linear", + )(timePoints[i]) + pNs = Function( + parachute.noisyPressureSignal, + "Time (s)", + "Pressure - With Noise (Pa)", + "linear", + )(timePoints[i]) file.write("{:f}, {:.5f}, {:.5f}\n".format(timePoints[i], pCl, pNs)) # We need to save only 1 parachute data pass @@ -3698,32 +3004,7 @@ def allInfo(self): # Print a summary of data about the flight self.info() - print("\n\nTrajectory 3d Plot\n") - self.plot3dTrajectory() - - print("\n\nTrajectory Kinematic Plots\n") - self.plotLinearKinematicsData() - - print("\n\nAngular Position Plots\n") - self.plotFlightPathAngleData() - - print("\n\nPath, Attitude and Lateral Attitude Angle plots\n") - self.plotAttitudeData() - - print("\n\nTrajectory Angular Velocity and Acceleration Plots\n") - self.plotAngularKinematicsData() - - print("\n\nTrajectory Force Plots\n") - self.plotTrajectoryForceData() - - print("\n\nTrajectory Energy Plots\n") - self.plotEnergyData() - - print("\n\nTrajectory Fluid Mechanics Plots\n") - self.plotFluidMechanicsData() - - print("\n\nTrajectory Stability and Control Plots\n") - self.plotStabilityAndControlData() + self.plots.all() return None diff --git a/rocketpy/Parachute.py b/rocketpy/Parachute.py index 5be11a815..cc45f0624 100644 --- a/rocketpy/Parachute.py +++ b/rocketpy/Parachute.py @@ -4,6 +4,8 @@ import numpy as np +from .Function import Function + class Parachute: """Keeps parachute information. @@ -107,6 +109,10 @@ def __init__( self.noiseBias = noise[0] self.noiseDeviation = noise[1] self.noiseCorr = (noise[2], (1 - noise[2] ** 2) ** 0.5) + self.cleanPressureSignalFunction = Function(0) + self.noisyPressureSignalFunction = Function(0) + self.noiseSignalFunction = Function(0) + alpha, beta = self.noiseCorr self.noiseFunction = lambda: alpha * self.noiseSignal[-1][ 1 diff --git a/rocketpy/plots/flight_plots.py b/rocketpy/plots/flight_plots.py index 6470e2641..b2f6f5c0e 100644 --- a/rocketpy/plots/flight_plots.py +++ b/rocketpy/plots/flight_plots.py @@ -1,8 +1,874 @@ -__author__ = " " +__author__ = "Guilherme Fernandes Alves, Mateus Stano Junqueira" __copyright__ = "Copyright 20XX, RocketPy Team" __license__ = "MIT" +import matplotlib.pyplot as plt +import numpy as np + +try: + from functools import cached_property +except ImportError: + from ..tools import cached_property + + class _FlightPlots: - def __init__(self) -> None: - pass + """Class that holds plot methods for Flight class. + + Attributes + ---------- + _FlightPlots.flight : Flight + Flight object that will be used for the plots. + + _FlightPlots.first_event_time : float + Time of first event. + + _FlightPlots.first_event_time_index : int + Time index of first event. + """ + + def __init__(self, flight): + """Initializes _FlightPlots class. + + Parameters + ---------- + flight : Flight + Instance of the Flight class + + Returns + ------- + None + """ + self.flight = flight + return None + + @cached_property + def first_event_time(self): + """Time of the first flight event.""" + if len(self.flight.parachuteEvents) > 0: + return ( + self.flight.parachuteEvents[0][0] + + self.flight.parachuteEvents[0][1].lag + ) + else: + return self.flight.tFinal + + @cached_property + def first_event_time_index(self): + """Time index of the first flight event.""" + if len(self.flight.parachuteEvents) > 0: + return np.nonzero(self.flight.x[:, 0] == self.first_event_time)[0][0] + else: + return -1 + + def trajectory_3d(self): + """Plot a 3D graph of the trajectory + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Get max and min x and y + maxZ = max(self.flight.z[:, 1] - self.flight.env.elevation) + maxX = max(self.flight.x[:, 1]) + minX = min(self.flight.x[:, 1]) + maxY = max(self.flight.y[:, 1]) + minY = min(self.flight.y[:, 1]) + maxXY = max(maxX, maxY) + minXY = min(minX, minY) + + # Create figure + fig1 = plt.figure(figsize=(9, 9)) + ax1 = plt.subplot(111, projection="3d") + ax1.plot( + self.flight.x[:, 1], self.flight.y[:, 1], zs=0, zdir="z", linestyle="--" + ) + ax1.plot( + self.flight.x[:, 1], + self.flight.z[:, 1] - self.flight.env.elevation, + zs=minXY, + zdir="y", + linestyle="--", + ) + ax1.plot( + self.flight.y[:, 1], + self.flight.z[:, 1] - self.flight.env.elevation, + zs=minXY, + zdir="x", + linestyle="--", + ) + ax1.plot( + self.flight.x[:, 1], + self.flight.y[:, 1], + self.flight.z[:, 1] - self.flight.env.elevation, + linewidth="2", + ) + ax1.scatter(0, 0, 0) + ax1.set_xlabel("X - East (m)") + ax1.set_ylabel("Y - North (m)") + ax1.set_zlabel("Z - Altitude Above Ground Level (m)") + ax1.set_title("Flight Trajectory") + ax1.set_zlim3d([0, maxZ]) + ax1.set_ylim3d([minXY, maxXY]) + ax1.set_xlim3d([minXY, maxXY]) + ax1.view_init(15, 45) + plt.show() + + return None + + def linear_kinematics_data(self): + """Prints out all Kinematics graphs available about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Velocity and acceleration plots + fig2 = plt.figure(figsize=(9, 12)) + + ax1 = plt.subplot(414) + ax1.plot(self.flight.vx[:, 0], self.flight.vx[:, 1], color="#ff7f0e") + ax1.set_xlim(0, self.flight.tFinal) + ax1.set_title("Velocity X | Acceleration X") + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Velocity X (m/s)", color="#ff7f0e") + ax1.tick_params("y", colors="#ff7f0e") + ax1.grid(True) + + ax1up = ax1.twinx() + ax1up.plot(self.flight.ax[:, 0], self.flight.ax[:, 1], color="#1f77b4") + ax1up.set_ylabel("Acceleration X (m/s²)", color="#1f77b4") + ax1up.tick_params("y", colors="#1f77b4") + + ax2 = plt.subplot(413) + ax2.plot(self.flight.vy[:, 0], self.flight.vy[:, 1], color="#ff7f0e") + ax2.set_xlim(0, self.flight.tFinal) + ax2.set_title("Velocity Y | Acceleration Y") + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Velocity Y (m/s)", color="#ff7f0e") + ax2.tick_params("y", colors="#ff7f0e") + ax2.grid(True) + + ax2up = ax2.twinx() + ax2up.plot(self.flight.ay[:, 0], self.flight.ay[:, 1], color="#1f77b4") + ax2up.set_ylabel("Acceleration Y (m/s²)", color="#1f77b4") + ax2up.tick_params("y", colors="#1f77b4") + + ax3 = plt.subplot(412) + ax3.plot(self.flight.vz[:, 0], self.flight.vz[:, 1], color="#ff7f0e") + ax3.set_xlim(0, self.flight.tFinal) + ax3.set_title("Velocity Z | Acceleration Z") + ax3.set_xlabel("Time (s)") + ax3.set_ylabel("Velocity Z (m/s)", color="#ff7f0e") + ax3.tick_params("y", colors="#ff7f0e") + ax3.grid(True) + + ax3up = ax3.twinx() + ax3up.plot(self.flight.az[:, 0], self.flight.az[:, 1], color="#1f77b4") + ax3up.set_ylabel("Acceleration Z (m/s²)", color="#1f77b4") + ax3up.tick_params("y", colors="#1f77b4") + + ax4 = plt.subplot(411) + ax4.plot(self.flight.speed[:, 0], self.flight.speed[:, 1], color="#ff7f0e") + ax4.set_xlim(0, self.flight.tFinal) + ax4.set_title("Velocity Magnitude | Acceleration Magnitude") + ax4.set_xlabel("Time (s)") + ax4.set_ylabel("Velocity (m/s)", color="#ff7f0e") + ax4.tick_params("y", colors="#ff7f0e") + ax4.grid(True) + + ax4up = ax4.twinx() + ax4up.plot( + self.flight.acceleration[:, 0], + self.flight.acceleration[:, 1], + color="#1f77b4", + ) + ax4up.set_ylabel("Acceleration (m/s²)", color="#1f77b4") + ax4up.tick_params("y", colors="#1f77b4") + + plt.subplots_adjust(hspace=0.5) + plt.show() + return None + + def attitude_data(self): + """Prints out all Angular position graphs available about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Angular position plots + fig3 = plt.figure(figsize=(9, 12)) + + ax1 = plt.subplot(411) + ax1.plot(self.flight.e0[:, 0], self.flight.e0[:, 1], label="$e_0$") + ax1.plot(self.flight.e1[:, 0], self.flight.e1[:, 1], label="$e_1$") + ax1.plot(self.flight.e2[:, 0], self.flight.e2[:, 1], label="$e_2$") + ax1.plot(self.flight.e3[:, 0], self.flight.e3[:, 1], label="$e_3$") + ax1.set_xlim(0, self.first_event_time) + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Euler Parameters") + ax1.set_title("Euler Parameters") + ax1.legend() + ax1.grid(True) + + ax2 = plt.subplot(412) + ax2.plot(self.flight.psi[:, 0], self.flight.psi[:, 1]) + ax2.set_xlim(0, self.first_event_time) + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("ψ (°)") + ax2.set_title("Euler Precession Angle") + ax2.grid(True) + + ax3 = plt.subplot(413) + ax3.plot(self.flight.theta[:, 0], self.flight.theta[:, 1], label="θ - Nutation") + ax3.set_xlim(0, self.first_event_time) + ax3.set_xlabel("Time (s)") + ax3.set_ylabel("θ (°)") + ax3.set_title("Euler Nutation Angle") + ax3.grid(True) + + ax4 = plt.subplot(414) + ax4.plot(self.flight.phi[:, 0], self.flight.phi[:, 1], label="φ - Spin") + ax4.set_xlim(0, self.first_event_time) + ax4.set_xlabel("Time (s)") + ax4.set_ylabel("φ (°)") + ax4.set_title("Euler Spin Angle") + ax4.grid(True) + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def flight_path_angle_data(self): + """Prints out Flight path and Rocket Attitude angle graphs available + about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Path, Attitude and Lateral Attitude Angle + # Angular position plots + fig5 = plt.figure(figsize=(9, 6)) + + ax1 = plt.subplot(211) + ax1.plot( + self.flight.pathAngle[:, 0], + self.flight.pathAngle[:, 1], + label="Flight Path Angle", + ) + ax1.plot( + self.flight.attitudeAngle[:, 0], + self.flight.attitudeAngle[:, 1], + label="Rocket Attitude Angle", + ) + ax1.set_xlim(0, self.first_event_time) + ax1.legend() + ax1.grid(True) + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Angle (°)") + ax1.set_title("Flight Path and Attitude Angle") + + ax2 = plt.subplot(212) + ax2.plot( + self.flight.lateralAttitudeAngle[:, 0], + self.flight.lateralAttitudeAngle[:, 1], + ) + ax2.set_xlim(0, self.first_event_time) + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Lateral Attitude Angle (°)") + ax2.set_title("Lateral Attitude Angle") + ax2.grid(True) + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def angular_kinematics_data(self): + """Prints out all Angular velocity and acceleration graphs available + about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Angular velocity and acceleration plots + fig4 = plt.figure(figsize=(9, 9)) + ax1 = plt.subplot(311) + ax1.plot(self.flight.w1[:, 0], self.flight.w1[:, 1], color="#ff7f0e") + ax1.set_xlim(0, self.first_event_time) + ax1.set_xlabel("Time (s)") + ax1.set_ylabel(r"Angular Velocity - ${\omega_1}$ (rad/s)", color="#ff7f0e") + ax1.set_title( + r"Angular Velocity ${\omega_1}$ | Angular Acceleration ${\alpha_1}$" + ) + ax1.tick_params("y", colors="#ff7f0e") + ax1.grid(True) + + ax1up = ax1.twinx() + ax1up.plot(self.flight.alpha1[:, 0], self.flight.alpha1[:, 1], color="#1f77b4") + ax1up.set_ylabel( + r"Angular Acceleration - ${\alpha_1}$ (rad/s²)", color="#1f77b4" + ) + ax1up.tick_params("y", colors="#1f77b4") + + ax2 = plt.subplot(312) + ax2.plot(self.flight.w2[:, 0], self.flight.w2[:, 1], color="#ff7f0e") + ax2.set_xlim(0, self.first_event_time) + ax2.set_xlabel("Time (s)") + ax2.set_ylabel(r"Angular Velocity - ${\omega_2}$ (rad/s)", color="#ff7f0e") + ax2.set_title( + r"Angular Velocity ${\omega_2}$ | Angular Acceleration ${\alpha_2}$" + ) + ax2.tick_params("y", colors="#ff7f0e") + ax2.grid(True) + + ax2up = ax2.twinx() + ax2up.plot(self.flight.alpha2[:, 0], self.flight.alpha2[:, 1], color="#1f77b4") + ax2up.set_ylabel( + r"Angular Acceleration - ${\alpha_2}$ (rad/s²)", color="#1f77b4" + ) + ax2up.tick_params("y", colors="#1f77b4") + + ax3 = plt.subplot(313) + ax3.plot(self.flight.w3[:, 0], self.flight.w3[:, 1], color="#ff7f0e") + ax3.set_xlim(0, self.first_event_time) + ax3.set_xlabel("Time (s)") + ax3.set_ylabel(r"Angular Velocity - ${\omega_3}$ (rad/s)", color="#ff7f0e") + ax3.set_title( + r"Angular Velocity ${\omega_3}$ | Angular Acceleration ${\alpha_3}$" + ) + ax3.tick_params("y", colors="#ff7f0e") + ax3.grid(True) + + ax3up = ax3.twinx() + ax3up.plot(self.flight.alpha3[:, 0], self.flight.alpha3[:, 1], color="#1f77b4") + ax3up.set_ylabel( + r"Angular Acceleration - ${\alpha_3}$ (rad/s²)", color="#1f77b4" + ) + ax3up.tick_params("y", colors="#1f77b4") + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def trajectory_force_data(self): + """Prints out all Forces and Moments graphs available about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Rail Button Forces + fig6 = plt.figure(figsize=(9, 6)) + + ax1 = plt.subplot(211) + ax1.plot( + self.flight.railButton1NormalForce[: self.flight.outOfRailTimeIndex, 0], + self.flight.railButton1NormalForce[: self.flight.outOfRailTimeIndex, 1], + label="Upper Rail Button", + ) + ax1.plot( + self.flight.railButton2NormalForce[: self.flight.outOfRailTimeIndex, 0], + self.flight.railButton2NormalForce[: self.flight.outOfRailTimeIndex, 1], + label="Lower Rail Button", + ) + ax1.set_xlim( + 0, + self.flight.outOfRailTime + if self.flight.outOfRailTime > 0 + else self.flight.tFinal, + ) + ax1.legend() + ax1.grid(True) + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Normal Force (N)") + ax1.set_title("Rail Buttons Normal Force") + + ax2 = plt.subplot(212) + ax2.plot( + self.flight.railButton1ShearForce[: self.flight.outOfRailTimeIndex, 0], + self.flight.railButton1ShearForce[: self.flight.outOfRailTimeIndex, 1], + label="Upper Rail Button", + ) + ax2.plot( + self.flight.railButton2ShearForce[: self.flight.outOfRailTimeIndex, 0], + self.flight.railButton2ShearForce[: self.flight.outOfRailTimeIndex, 1], + label="Lower Rail Button", + ) + ax2.set_xlim( + 0, + self.flight.outOfRailTime + if self.flight.outOfRailTime > 0 + else self.flight.tFinal, + ) + ax2.legend() + ax2.grid(True) + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Shear Force (N)") + ax2.set_title("Rail Buttons Shear Force") + + plt.subplots_adjust(hspace=0.5) + plt.show() + + # Aerodynamic force and moment plots + fig7 = plt.figure(figsize=(9, 12)) + + ax1 = plt.subplot(411) + ax1.plot( + self.flight.aerodynamicLift[: self.first_event_time_index, 0], + self.flight.aerodynamicLift[: self.first_event_time_index, 1], + label="Resultant", + ) + ax1.plot( + self.flight.R1[: self.first_event_time_index, 0], + self.flight.R1[: self.first_event_time_index, 1], + label="R1", + ) + ax1.plot( + self.flight.R2[: self.first_event_time_index, 0], + self.flight.R2[: self.first_event_time_index, 1], + label="R2", + ) + ax1.set_xlim(0, self.first_event_time) + ax1.legend() + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Lift Force (N)") + ax1.set_title("Aerodynamic Lift Resultant Force") + ax1.grid() + + ax2 = plt.subplot(412) + ax2.plot( + self.flight.aerodynamicDrag[: self.first_event_time_index, 0], + self.flight.aerodynamicDrag[: self.first_event_time_index, 1], + ) + ax2.set_xlim(0, self.first_event_time) + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Drag Force (N)") + ax2.set_title("Aerodynamic Drag Force") + ax2.grid() + + ax3 = plt.subplot(413) + ax3.plot( + self.flight.aerodynamicBendingMoment[: self.first_event_time_index, 0], + self.flight.aerodynamicBendingMoment[: self.first_event_time_index, 1], + label="Resultant", + ) + ax3.plot( + self.flight.M1[: self.first_event_time_index, 0], + self.flight.M1[: self.first_event_time_index, 1], + label="M1", + ) + ax3.plot( + self.flight.M2[: self.first_event_time_index, 0], + self.flight.M2[: self.first_event_time_index, 1], + label="M2", + ) + ax3.set_xlim(0, self.first_event_time) + ax3.legend() + ax3.set_xlabel("Time (s)") + ax3.set_ylabel("Bending Moment (N m)") + ax3.set_title("Aerodynamic Bending Resultant Moment") + ax3.grid() + + ax4 = plt.subplot(414) + ax4.plot( + self.flight.aerodynamicSpinMoment[: self.first_event_time_index, 0], + self.flight.aerodynamicSpinMoment[: self.first_event_time_index, 1], + ) + ax4.set_xlim(0, self.first_event_time) + ax4.set_xlabel("Time (s)") + ax4.set_ylabel("Spin Moment (N m)") + ax4.set_title("Aerodynamic Spin Moment") + ax4.grid() + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def energy_data(self): + """Prints out all Energy components graphs available about the Flight + + Returns + ------- + None + """ + + fig8 = plt.figure(figsize=(9, 9)) + + ax1 = plt.subplot(411) + ax1.plot( + self.flight.kineticEnergy[:, 0], + self.flight.kineticEnergy[:, 1], + label="Kinetic Energy", + ) + ax1.plot( + self.flight.rotationalEnergy[:, 0], + self.flight.rotationalEnergy[:, 1], + label="Rotational Energy", + ) + ax1.plot( + self.flight.translationalEnergy[:, 0], + self.flight.translationalEnergy[:, 1], + label="Translational Energy", + ) + ax1.set_xlim( + 0, + self.flight.apogeeTime + if self.flight.apogeeTime != 0.0 + else self.flight.tFinal, + ) + ax1.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax1.set_title("Kinetic Energy Components") + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Energy (J)") + + ax1.legend() + ax1.grid() + + ax2 = plt.subplot(412) + ax2.plot( + self.flight.totalEnergy[:, 0], + self.flight.totalEnergy[:, 1], + label="Total Energy", + ) + ax2.plot( + self.flight.kineticEnergy[:, 0], + self.flight.kineticEnergy[:, 1], + label="Kinetic Energy", + ) + ax2.plot( + self.flight.potentialEnergy[:, 0], + self.flight.potentialEnergy[:, 1], + label="Potential Energy", + ) + ax2.set_xlim( + 0, + self.flight.apogeeTime + if self.flight.apogeeTime != 0.0 + else self.flight.tFinal, + ) + ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax2.set_title("Total Mechanical Energy Components") + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Energy (J)") + ax2.legend() + ax2.grid() + + ax3 = plt.subplot(413) + ax3.plot( + self.flight.thrustPower[:, 0], + self.flight.thrustPower[:, 1], + label="|Thrust Power|", + ) + ax3.set_xlim(0, self.flight.rocket.motor.burnOutTime) + ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax3.set_title("Thrust Absolute Power") + ax3.set_xlabel("Time (s)") + ax3.set_ylabel("Power (W)") + ax3.legend() + ax3.grid() + + ax4 = plt.subplot(414) + ax4.plot( + self.flight.dragPower[:, 0], + -self.flight.dragPower[:, 1], + label="|Drag Power|", + ) + ax4.set_xlim( + 0, + self.flight.apogeeTime + if self.flight.apogeeTime != 0.0 + else self.flight.tFinal, + ) + ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax4.set_title("Drag Absolute Power") + ax4.set_xlabel("Time (s)") + ax4.set_ylabel("Power (W)") + ax4.legend() + ax4.grid() + + plt.subplots_adjust(hspace=1) + plt.show() + + return None + + def fluid_mechanics_data(self): + """Prints out a summary of the Fluid Mechanics graphs available about + the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + # Trajectory Fluid Mechanics Plots + fig10 = plt.figure(figsize=(9, 12)) + + ax1 = plt.subplot(411) + ax1.plot(self.flight.MachNumber[:, 0], self.flight.MachNumber[:, 1]) + ax1.set_xlim(0, self.flight.tFinal) + ax1.set_title("Mach Number") + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Mach Number") + ax1.grid() + + ax2 = plt.subplot(412) + ax2.plot(self.flight.ReynoldsNumber[:, 0], self.flight.ReynoldsNumber[:, 1]) + ax2.set_xlim(0, self.flight.tFinal) + ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax2.set_title("Reynolds Number") + ax2.set_xlabel("Time (s)") + ax2.set_ylabel("Reynolds Number") + ax2.grid() + + ax3 = plt.subplot(413) + ax3.plot( + self.flight.dynamicPressure[:, 0], + self.flight.dynamicPressure[:, 1], + label="Dynamic Pressure", + ) + ax3.plot( + self.flight.totalPressure[:, 0], + self.flight.totalPressure[:, 1], + label="Total Pressure", + ) + ax3.plot( + self.flight.pressure[:, 0], + self.flight.pressure[:, 1], + label="Static Pressure", + ) + ax3.set_xlim(0, self.flight.tFinal) + ax3.legend() + ax3.ticklabel_format(style="sci", axis="y", scilimits=(0, 0)) + ax3.set_title("Total and Dynamic Pressure") + ax3.set_xlabel("Time (s)") + ax3.set_ylabel("Pressure (Pa)") + ax3.grid() + + ax4 = plt.subplot(414) + ax4.plot(self.flight.angleOfAttack[:, 0], self.flight.angleOfAttack[:, 1]) + # Make sure bottom and top limits are different + if ( + self.flight.outOfRailTime + * self.flight.angleOfAttack(self.flight.outOfRailTime) + != 0 + ): + ax4.set_xlim(self.flight.outOfRailTime, 10 * self.flight.outOfRailTime + 1) + ax4.set_ylim(0, self.flight.angleOfAttack(self.flight.outOfRailTime)) + ax4.set_title("Angle of Attack") + ax4.set_xlabel("Time (s)") + ax4.set_ylabel("Angle of Attack (°)") + ax4.grid() + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def stability_and_control_data(self): + """Prints out Rocket Stability and Control parameters graphs available + about the Flight + + Parameters + ---------- + None + + Return + ------ + None + """ + + fig9 = plt.figure(figsize=(9, 6)) + + ax1 = plt.subplot(211) + ax1.plot(self.flight.staticMargin[:, 0], self.flight.staticMargin[:, 1]) + ax1.set_xlim(0, self.flight.staticMargin[:, 0][-1]) + ax1.set_title("Static Margin") + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Static Margin (c)") + ax1.grid() + + ax2 = plt.subplot(212) + maxAttitude = max(self.flight.attitudeFrequencyResponse[:, 1]) + maxAttitude = maxAttitude if maxAttitude != 0 else 1 + ax2.plot( + self.flight.attitudeFrequencyResponse[:, 0], + self.flight.attitudeFrequencyResponse[:, 1] / maxAttitude, + label="Attitude Angle", + ) + maxOmega1 = max(self.flight.omega1FrequencyResponse[:, 1]) + maxOmega1 = maxOmega1 if maxOmega1 != 0 else 1 + ax2.plot( + self.flight.omega1FrequencyResponse[:, 0], + self.flight.omega1FrequencyResponse[:, 1] / maxOmega1, + label=r"$\omega_1$", + ) + maxOmega2 = max(self.flight.omega2FrequencyResponse[:, 1]) + maxOmega2 = maxOmega2 if maxOmega2 != 0 else 1 + ax2.plot( + self.flight.omega2FrequencyResponse[:, 0], + self.flight.omega2FrequencyResponse[:, 1] / maxOmega2, + label=r"$\omega_2$", + ) + maxOmega3 = max(self.flight.omega3FrequencyResponse[:, 1]) + maxOmega3 = maxOmega3 if maxOmega3 != 0 else 1 + ax2.plot( + self.flight.omega3FrequencyResponse[:, 0], + self.flight.omega3FrequencyResponse[:, 1] / maxOmega3, + label=r"$\omega_3$", + ) + ax2.set_title("Frequency Response") + ax2.set_xlabel("Frequency (Hz)") + ax2.set_ylabel("Amplitude Magnitude Normalized") + ax2.set_xlim(0, 5) + ax2.legend() + ax2.grid() + + plt.subplots_adjust(hspace=0.5) + plt.show() + + return None + + def pressure_rocket_altitude(self): + """Plots out pressure at rocket's altitude. + + Parameters + ---------- + None + + Return + ------ + None + """ + + # self.flight.pressure() + + plt.figure() + ax1 = plt.subplot(111) + ax1.plot(self.flight.pressure[:, 0], self.flight.pressure[:, 1]) + ax1.set_title("Pressure at Rocket's Altitude") + ax1.set_xlabel("Time (s)") + ax1.set_ylabel("Pressure (Pa)") + ax1.set_xlim(0, self.flight.tFinal) + ax1.grid() + + plt.show() + + return None + + def pressure_signals(self): + """Plots out all Parachute Trigger Pressure Signals. + This function can be called also for plot pressure data for flights + without Parachutes, in this case the Pressure Signals will be simply + the pressure provided by the atmosphericModel, at Flight z positions. + This means that no noise will be considered if at least one parachute + has not been added. + + This function aims to help the engineer to visually check if there + are anomalies with the Flight Simulation. + + Parameters + ---------- + None + + Return + ------ + None + """ + + if len(self.flight.parachuteEvents) > 0: + for parachute in self.flight.rocket.parachutes: + print("\nParachute: ", parachute.name) + self.flight._calculate_pressure_signal() + parachute.noiseSignalFunction() + parachute.noisyPressureSignalFunction() + parachute.cleanPressureSignalFunction() + else: + print("\nRocket has no parachutes. No parachute plots available") + + return None + + def all(self): + """Prints out all plots available about the Flight. + + Parameters + ---------- + None + + Return + ------ + None + """ + + print("\n\nTrajectory 3d Plot\n") + self.trajectory_3d() + + print("\n\nTrajectory Kinematic Plots\n") + self.linear_kinematics_data() + + print("\n\nAngular Position Plots\n") + self.flight_path_angle_data() + + print("\n\nPath, Attitude and Lateral Attitude Angle plots\n") + self.attitude_data() + + print("\n\nTrajectory Angular Velocity and Acceleration Plots\n") + self.angular_kinematics_data() + + print("\n\nTrajectory Force Plots\n") + self.trajectory_force_data() + + print("\n\nTrajectory Energy Plots\n") + self.energy_data() + + print("\n\nTrajectory Fluid Mechanics Plots\n") + self.fluid_mechanics_data() + + print("\n\nTrajectory Stability and Control Plots\n") + self.stability_and_control_data() + + print("\n\nRocket and Parachute Pressure Plots\n") + self.pressure_rocket_altitude() + self.pressure_signals() + + return None