Skip to content

Commit 43fdd8a

Browse files
authored
Merge pull request #377 from RocketPy-Team/enh/new-stability-margin
ENH: Adding Stability Margin with Mach dependency
2 parents 41bb0ac + 95792f7 commit 43fdd8a

File tree

9 files changed

+304
-98
lines changed

9 files changed

+304
-98
lines changed

rocketpy/mathutils/function.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,8 @@ def plot2D(
11521152
samples=[30, 30],
11531153
force_data=True,
11541154
disp_type="surface",
1155+
alpha=0.6,
1156+
cmap="viridis",
11551157
):
11561158
"""Plot 2-Dimensional Function, from a lower limit to an upper limit,
11571159
by sampling the Function several times in the interval. The title of
@@ -1185,6 +1187,12 @@ def plot2D(
11851187
disp_type : string, optional
11861188
Display type of plotted graph, which can be surface, wireframe,
11871189
contour, or contourf. Default value is surface.
1190+
alpha : float, optional
1191+
Transparency of plotted graph, which can be a value between 0 and
1192+
1. Default value is 0.6.
1193+
cmap : string, optional
1194+
Colormap of plotted graph, which can be any of the colormaps
1195+
available in matplotlib. Default value is viridis.
11881196
11891197
Returns
11901198
-------
@@ -1221,6 +1229,9 @@ def plot2D(
12211229
mesh = [[mesh_x_flat[i], mesh_y_flat[i]] for i in range(len(mesh_x_flat))]
12221230
# Evaluate function at all mesh nodes and convert it to matrix
12231231
z = np.array(self.get_value(mesh)).reshape(mesh_x.shape)
1232+
z_min, z_max = z.min(), z.max()
1233+
color_map = plt.cm.get_cmap(cmap)
1234+
norm = plt.Normalize(z_min, z_max)
12241235
# Plot function
12251236
if disp_type == "surface":
12261237
surf = axes.plot_surface(
@@ -1229,9 +1240,11 @@ def plot2D(
12291240
z,
12301241
rstride=1,
12311242
cstride=1,
1232-
# cmap=cm.coolwarm,
1243+
cmap=color_map,
12331244
linewidth=0,
1234-
alpha=0.6,
1245+
alpha=alpha,
1246+
vmin=z_min,
1247+
vmax=z_max,
12351248
)
12361249
figure.colorbar(surf)
12371250
elif disp_type == "wireframe":

rocketpy/plots/flight_plots.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -682,19 +682,11 @@ def fluid_mechanics_data(self):
682682

683683
ax4 = plt.subplot(414)
684684
ax4.plot(self.flight.angle_of_attack[:, 0], self.flight.angle_of_attack[:, 1])
685-
# Make sure bottom and top limits are different
686-
if (
687-
self.flight.out_of_rail_time
688-
* self.flight.angle_of_attack(self.flight.out_of_rail_time)
689-
!= 0
690-
):
691-
ax4.set_xlim(
692-
self.flight.out_of_rail_time, 10 * self.flight.out_of_rail_time + 1
693-
)
694-
ax4.set_ylim(0, self.flight.angle_of_attack(self.flight.out_of_rail_time))
695685
ax4.set_title("Angle of Attack")
696686
ax4.set_xlabel("Time (s)")
697687
ax4.set_ylabel("Angle of Attack (°)")
688+
ax4.set_xlim(self.flight.out_of_rail_time, self.first_event_time)
689+
ax4.set_ylim(0, self.flight.angle_of_attack(self.flight.out_of_rail_time) + 15)
698690
ax4.grid()
699691

700692
plt.subplots_adjust(hspace=0.5)
@@ -714,11 +706,32 @@ def stability_and_control_data(self):
714706
fig9 = plt.figure(figsize=(9, 6))
715707

716708
ax1 = plt.subplot(211)
717-
ax1.plot(self.flight.static_margin[:, 0], self.flight.static_margin[:, 1])
718-
ax1.set_xlim(0, self.flight.static_margin[:, 0][-1])
719-
ax1.set_title("Static Margin")
709+
ax1.plot(self.flight.stability_margin[:, 0], self.flight.stability_margin[:, 1])
710+
ax1.set_xlim(0, self.flight.stability_margin[:, 0][-1])
711+
ax1.set_title("Stability Margin")
720712
ax1.set_xlabel("Time (s)")
721-
ax1.set_ylabel("Static Margin (c)")
713+
ax1.set_ylabel("Stability Margin (c)")
714+
ax1.set_xlim(0, self.first_event_time)
715+
ax1.axvline(
716+
x=self.flight.out_of_rail_time,
717+
color="r",
718+
linestyle="--",
719+
label="Out of Rail Time",
720+
)
721+
ax1.axvline(
722+
x=self.flight.rocket.motor.burn_out_time,
723+
color="g",
724+
linestyle="--",
725+
label="Burn Out Time",
726+
)
727+
728+
ax1.axvline(
729+
x=self.flight.apogee_time,
730+
color="m",
731+
linestyle="--",
732+
label="Apogee Time",
733+
)
734+
ax1.legend()
722735
ax1.grid()
723736

724737
ax2 = plt.subplot(212)

rocketpy/plots/rocket_plots.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class _RocketPlots:
1212
1313
"""
1414

15-
def __init__(self, rocket) -> None:
15+
def __init__(self, rocket):
1616
"""Initializes _RocketPlots class.
1717
1818
Parameters
@@ -65,6 +65,24 @@ def static_margin(self):
6565

6666
return None
6767

68+
def stability_margin(self):
69+
"""Plots static margin of the rocket as a function of time.
70+
71+
Returns
72+
-------
73+
None
74+
"""
75+
76+
self.rocket.stability_margin.plot2D(
77+
lower=0,
78+
upper=[2, self.rocket.motor.burn_out_time], # Mach 2 and burnout
79+
samples=[20, 20],
80+
disp_type="surface",
81+
alpha=1,
82+
)
83+
84+
return None
85+
6886
def power_on_drag(self):
6987
"""Plots power on drag of the rocket as a function of time.
7088
@@ -113,14 +131,31 @@ def all(self):
113131
None
114132
"""
115133

116-
# Show plots
134+
# Mass Plots
117135
print("\nMass Plots")
136+
print("-" * 40)
118137
self.total_mass()
119138
self.reduced_mass()
139+
140+
# Aerodynamics Plots
120141
print("\nAerodynamics Plots")
121-
self.static_margin()
142+
print("-" * 40)
143+
144+
# Drag Plots
145+
print("Drag Plots")
146+
print("-" * 20) # Separator for Drag Plots
122147
self.power_on_drag()
123148
self.power_off_drag()
149+
150+
# Stability Plots
151+
print("\nStability Plots")
152+
print("-" * 20) # Separator for Stability Plots
153+
self.static_margin()
154+
self.stability_margin()
155+
156+
# Thrust-to-Weight Plot
157+
print("\nThrust-to-Weight Plot")
158+
print("-" * 40)
124159
self.thrust_to_weight()
125160

126161
return None

rocketpy/prints/flight_prints.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ def out_of_rail_conditions(self):
155155
)
156156
)
157157
print(
158-
"Rail Departure Static Margin: {:.3f} c".format(
159-
self.flight.static_margin(self.flight.out_of_rail_time)
158+
"Rail Departure Stability Margin: {:.3f} c".format(
159+
self.flight.stability_margin(self.flight.out_of_rail_time)
160160
)
161161
)
162162
print(
@@ -301,12 +301,19 @@ def impact_conditions(self):
301301
print("\nImpact Conditions\n")
302302
print("X Impact: {:.3f} m".format(self.flight.x_impact))
303303
print("Y Impact: {:.3f} m".format(self.flight.y_impact))
304+
print("Latitude: {:.7f}°".format(self.flight.latitude(self.flight.t_final)))
305+
print(
306+
"Longitude: {:.7f}°".format(self.flight.longitude(self.flight.t_final))
307+
)
304308
print("Time of Impact: {:.3f} s".format(self.flight.t_final))
305309
print("Velocity at Impact: {:.3f} m/s".format(self.flight.impact_velocity))
306310
elif self.flight.terminate_on_apogee is False:
307311
print("End of Simulation")
308-
print("Time: {:.3f} s".format(self.flight.solution[-1][0]))
312+
t_final = self.flight.solution[-1][0]
313+
print("Time: {:.3f} s".format(t_final))
309314
print("Altitude: {:.3f} m".format(self.flight.solution[-1][3]))
315+
print("Latitude: {:.3f}°".format(self.flight.latitude(t_final)))
316+
print("Longitude: {:.3f}°".format(self.flight.longitude(t_final)))
310317

311318
return None
312319

@@ -362,6 +369,11 @@ def maximum_values(self):
362369
self.flight.max_acceleration_power_off_time,
363370
)
364371
)
372+
print(
373+
"Maximum Stability Margin: {:.3f} c at {:.2f} s".format(
374+
self.flight.max_stability_margin, self.flight.max_stability_margin_time
375+
)
376+
)
365377

366378
if (
367379
len(self.flight.rocket.rail_buttons) == 0
@@ -391,6 +403,22 @@ def maximum_values(self):
391403
)
392404
return None
393405

406+
def stability_margin(self):
407+
"""Prints out the maximum and minimum stability margin available
408+
about the flight."""
409+
print("\nStability Margin\n")
410+
print(
411+
"Maximum Stability Margin: {:.3f} c at {:.2f} s".format(
412+
self.flight.max_stability_margin, self.flight.max_stability_margin_time
413+
)
414+
)
415+
print(
416+
"Minimum Stability Margin: {:.3f} c at {:.2f} s".format(
417+
self.flight.min_stability_margin, self.flight.min_stability_margin_time
418+
)
419+
)
420+
return None
421+
394422
def all(self):
395423
"""Prints out all data available about the Flight.
396424
@@ -431,6 +459,10 @@ def all(self):
431459
self.impact_conditions()
432460
print()
433461

462+
# Print stability margin
463+
self.stability_margin()
464+
print()
465+
434466
# Print maximum values
435467
self.maximum_values()
436468
print()

rocketpy/prints/rocket_prints.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def inertia_details(self):
6363
)
6464
)
6565
print(
66-
"Rocket Inertia (with motor, but without propellant) 23: {:.3f} kg*m2".format(
66+
"Rocket Inertia (with motor, but without propellant) 23: {:.3f} kg*m2\n".format(
6767
self.rocket.dry_I_23
6868
)
6969
)
@@ -82,7 +82,7 @@ def rocket_geometrical_parameters(self):
8282
print("Rocket Frontal Area: " + "{:.6f}".format(self.rocket.area) + " m2")
8383
print("\nRocket Distances")
8484
print(
85-
"Rocket Center of Dry Mass - Center of Mass withour Motor: "
85+
"Rocket Center of Dry Mass - Center of Mass without Motor: "
8686
+ "{:.3f} m".format(
8787
abs(
8888
self.rocket.center_of_mass_without_motor
@@ -91,7 +91,7 @@ def rocket_geometrical_parameters(self):
9191
)
9292
)
9393
print(
94-
"Rocket Center of Dry Mass - Nozzle Exit Distance: "
94+
"Rocket Center of Dry Mass - Nozzle Exit: "
9595
+ "{:.3f} m".format(
9696
abs(
9797
self.rocket.center_of_dry_mass_position - self.rocket.motor_position
@@ -109,7 +109,7 @@ def rocket_geometrical_parameters(self):
109109
)
110110
print(
111111
"Rocket Center of Mass - Rocket Loaded Center of Mass: "
112-
+ "{:.3f} m".format(
112+
+ "{:.3f} m\n".format(
113113
abs(
114114
self.rocket.center_of_mass(0)
115115
- self.rocket.center_of_dry_mass_position
@@ -135,34 +135,40 @@ def rocket_aerodynamics_quantities(self):
135135
+ "/rad"
136136
)
137137

138-
print("\nAerodynamics Center of Pressure\n")
138+
print("\nCenter of Pressure\n")
139139
for surface, position in self.rocket.aerodynamic_surfaces:
140140
name = surface.name
141-
cpz = surface.cp[2]
141+
cpz = surface.cp[2] # relative to the user defined coordinate system
142142
print(
143143
name
144-
+ " Center of Pressure to CM: {:.3f}".format(
144+
+ " Center of Pressure position: {:.3f}".format(
145145
position - self.rocket._csys * cpz
146146
)
147147
+ " m"
148148
)
149+
print("\nStability\n")
149150
print(
150-
"Distance - Center of Pressure to Center of Dry Mass: "
151-
+ "{:.3f}".format(self.rocket.center_of_mass(0) - self.rocket.cp_position)
152-
+ " m"
151+
f"Center of Mass position (time=0): {self.rocket.center_of_mass(0):.3f} m"
153152
)
154153
print(
155-
"Initial Static Margin: "
154+
"Initial Static Margin (mach=0, time=0): "
156155
+ "{:.3f}".format(self.rocket.static_margin(0))
157156
+ " c"
158157
)
159158
print(
160-
"Final Static Margin: "
159+
"Final Static Margin (mach=0, time=burn_out): "
161160
+ "{:.3f}".format(
162161
self.rocket.static_margin(self.rocket.motor.burn_out_time)
163162
)
164163
+ " c"
165164
)
165+
print(
166+
"Rocket Center of Mass (time=0) - Center of Pressure (mach=0): "
167+
+ "{:.3f}".format(
168+
abs(self.rocket.center_of_mass(0) - self.rocket.cp_position(0))
169+
)
170+
+ " m\n"
171+
)
166172

167173
return None
168174

@@ -186,18 +192,14 @@ def all(self):
186192
"""
187193
# Print inertia details
188194
self.inertia_details()
189-
print()
190195

191196
# Print rocket geometrical parameters
192197
self.rocket_geometrical_parameters()
193-
print()
194198

195199
# Print rocket aerodynamics quantities
196200
self.rocket_aerodynamics_quantities()
197-
print()
198201

199202
# Print parachute data
200203
self.parachute_data()
201-
print()
202204

203205
return None

0 commit comments

Comments
 (0)