70 Look up nozzle angles for given expansion ratio and bell percentage.
74 expansion_ratio : float
75 Nozzle expansion ratio (eps = area_exit / area_throat)
77 Bell percentage (0.60, 0.70, 0.80, 0.90, or 1.00)
82 Nozzle entrance angle in degrees
84 Nozzle exit angle in degrees
87 eps =
max(3.0,
min(100.0, expansion_ratio))
90 valid_percents = [0.60, 0.70, 0.80, 0.90, 1.00]
91 bell_pct =
min(valid_percents, key=
lambda x: abs(x - bell_percent))
94 eps_keys = sorted(NOZZLE_ANGLES_TABLE.keys())
98 return NOZZLE_ANGLES_TABLE[eps][bell_pct]
101 if eps < eps_keys[0]:
103 eps1, eps2 = eps_keys[0], eps_keys[1]
104 theta_n1, theta_e1 = NOZZLE_ANGLES_TABLE[eps1][bell_pct]
105 theta_n2, theta_e2 = NOZZLE_ANGLES_TABLE[eps2][bell_pct]
107 log_eps = np.log(eps)
108 log_eps1 = np.log(eps1)
109 log_eps2 = np.log(eps2)
110 t = (log_eps - log_eps1) / (log_eps2 - log_eps1)
111 theta_n = theta_n1 + t * (theta_n2 - theta_n1)
112 theta_e = theta_e1 + t * (theta_e2 - theta_e1)
113 return theta_n, theta_e
115 if eps > eps_keys[-1]:
117 eps1, eps2 = eps_keys[-2], eps_keys[-1]
118 theta_n1, theta_e1 = NOZZLE_ANGLES_TABLE[eps1][bell_pct]
119 theta_n2, theta_e2 = NOZZLE_ANGLES_TABLE[eps2][bell_pct]
121 log_eps = np.log(eps)
122 log_eps1 = np.log(eps1)
123 log_eps2 = np.log(eps2)
124 t = (log_eps - log_eps1) / (log_eps2 - log_eps1)
125 theta_n = theta_n1 + t * (theta_n2 - theta_n1)
126 theta_e = theta_e1 + t * (theta_e2 - theta_e1)
127 return theta_n, theta_e
130 for i
in range(len(eps_keys) - 1):
131 if eps_keys[i] <= eps < eps_keys[i + 1]:
132 eps1, eps2 = eps_keys[i], eps_keys[i + 1]
133 theta_n1, theta_e1 = NOZZLE_ANGLES_TABLE[eps1][bell_pct]
134 theta_n2, theta_e2 = NOZZLE_ANGLES_TABLE[eps2][bell_pct]
136 log_eps = np.log(eps)
137 log_eps1 = np.log(eps1)
138 log_eps2 = np.log(eps2)
139 t = (log_eps - log_eps1) / (log_eps2 - log_eps1)
140 theta_n = theta_n1 + t * (theta_n2 - theta_n1)
141 theta_e = theta_e1 + t * (theta_e2 - theta_e1)
142 return theta_n, theta_e
145 return NOZZLE_ANGLES_TABLE[eps_keys[-1]][bell_pct]
150 Look up nozzle angles with interpolation in both expansion ratio and bell percentage.
154 expansion_ratio : float
155 Nozzle expansion ratio (eps = area_exit / area_throat)
157 Bell percentage (0.60 to 1.00)
162 Nozzle entrance angle in degrees
164 Nozzle exit angle in degrees
166 expansion_ratio = area_exit / area_throat
169 bell_pct =
max(0.60,
min(1.00, bell_percent))
172 eps_keys = sorted(NOZZLE_ANGLES_TABLE.keys())
173 valid_percents = [0.60, 0.70, 0.80, 0.90, 1.00]
176 eps =
max(3.0,
min(100.0, expansion_ratio))
179 if bell_pct
in valid_percents:
186 for i
in range(len(valid_percents) - 1):
187 if valid_percents[i] <= bell_pct < valid_percents[i + 1]:
188 bell_low = valid_percents[i]
189 bell_high = valid_percents[i + 1]
194 if bell_pct <= valid_percents[0]:
195 bell_low = bell_high = valid_percents[0]
197 bell_low = bell_high = valid_percents[-1]
204 if bell_low == bell_high:
205 return theta_n_low, theta_e_low
207 t = (bell_pct - bell_low) / (bell_high - bell_low)
208 theta_n = theta_n_low + t * (theta_n_high - theta_n_low)
209 theta_e = theta_e_low + t * (theta_e_high - theta_e_low)
211 return theta_n, theta_e
216 Plot the nozzle angle lookup table to match the original graph format.
220 save_path : str, optional
221 Path to save the plot (e.g., 'nozzle_angles_plot.png')
223 Whether to display the plot (default: True)
227 fig, ax : matplotlib figure and axes objects
230 bell_percents = [0.60, 0.70, 0.80, 0.90, 1.00]
243 eps_plot = np.logspace(np.log10(eps_min), np.log10(eps_max), 200)
246 fig, ax = plt.subplots(figsize=(10, 6))
249 for bell_pct
in bell_percents:
250 theta_n_values = [
lookup_angles(eps, bell_pct)[0]
for eps
in eps_plot]
251 label = f
'{int(bell_pct * 100)}%'
252 ax.semilogx(eps_plot, theta_n_values, color=colors[bell_pct],
253 linewidth=2, label=label)
256 for bell_pct
in bell_percents:
257 theta_e_values = [
lookup_angles(eps, bell_pct)[1]
for eps
in eps_plot]
258 label = f
'{int(bell_pct * 100)}%'
259 ax.semilogx(eps_plot, theta_e_values, color=colors[bell_pct],
260 linewidth=2, label=label)
269 ax.annotate(
'', xy=(eps_max * 1.15, theta_n_mid + 5),
270 xytext=(eps_max * 1.15, theta_n_mid - 5),
271 arrowprops=dict(arrowstyle=
'<->', lw=1.5, color=
'black'))
272 ax.text(eps_max * 1.2, theta_n_mid,
r'$\theta_n$',
273 fontsize=14, va=
'center', ha=
'left')
276 ax.annotate(
'', xy=(eps_max * 1.15, theta_e_mid + 3),
277 xytext=(eps_max * 1.15, theta_e_mid - 3),
278 arrowprops=dict(arrowstyle=
'<->', lw=1.5, color=
'black'))
279 ax.text(eps_max * 1.2, theta_e_mid,
r'$\theta_e$',
280 fontsize=14, va=
'center', ha=
'left')
283 ax.set_xlabel(
'Nozzle expansion ratio', fontsize=12)
284 ax.set_ylabel(
'Parabola angles, degs', fontsize=12)
285 ax.set_xlim(eps_min, eps_max * 1.3)
287 ax.grid(
True, alpha=0.3, which=
'both')
288 ax.set_title(
'Parabola angles vs Nozzle expansion ratio', fontsize=14, fontweight=
'bold')
295 for bell_pct
in bell_percents:
298 label_text = f
'{int(bell_pct * 100)}%'
299 ax.text(eps_label_pos, theta_n_val, label_text,
300 fontsize=8, ha=
'left', va=
'center', color=colors[bell_pct])
301 ax.text(eps_label_pos, theta_e_val, label_text,
302 fontsize=8, ha=
'left', va=
'center', color=colors[bell_pct])
307 plt.savefig(save_path, dpi=150, bbox_inches=
'tight')
308 print(f
"Plot saved to {save_path}")