26 Solve for m, h, k, psi, t5, t6 such that the rotated parabola goes
27 through N and E with the correct slopes at each end.
31 m, h, k, psi, t5, t6 = u
36 x = -m * s * t**2 + c * t + h
37 y = m * c * t**2 + s * t + k
41 dxdt = -2 * m * s * t + c
42 dydt = 2 * m * c * t + s
52 eq5 = slope(t5) - np.tan(theta_n)
53 eq6 = slope(t6) - np.tan(theta_e)
54 return [eq1, eq2, eq3, eq4, eq5, eq6]
57 L = Ex - Nx
if Ex != Nx
else 1.0
58 m0 = (Ey - Ny) / (L**2)
61 psi0 = 0.5 * (theta_n + theta_e)
65 u0 = [m0, h0, k0, psi0, t50, t60]
66 sol = root(equations, u0, method=
"hybr")
69 raise RuntimeError(f
"Parabola solve failed: {sol.message}")
100 convergent_half_angle_rad=np.pi / 4.0):
103 - "top" : Thrust-Optimized Parabolic (quadratic Bézier N-Q-E)
104 - "garcia" : rotated/canted parabola (RRS Garcia 2023)
105 - "rao_moc" : placeholder hook for a true Rao MoC solver
108 eps = area_exit / area_throat
109 deg2rad =
lambda d: d * np.pi / 180.0
112 theta_n_degree, theta_e_degree = lookup_angles_interp_bell(
113 area_throat, area_exit, bell_percent
115 theta_n = deg2rad(theta_n_degree)
116 theta_e = deg2rad(theta_e_degree)
118 r_t = np.sqrt(area_throat / np.pi)
119 r_e = np.sqrt(area_exit / np.pi)
128 _theta_c = float(convergent_half_angle_rad)
129 theta1 = np.linspace(-(np.pi / 2.0 + _theta_c), deg2rad(-90), steps)
130 x1 = 1.5 * r_t * np.cos(theta1)
131 y1 = 1.5 * r_t * np.sin(theta1) + 1.5 * r_t + r_t
134 theta2 = np.linspace(deg2rad(-90), theta_n - deg2rad(90), steps)
135 x2 = 0.382 * r_t * np.cos(theta2)
136 y2 = 0.382 * r_t * np.sin(theta2) + 0.382 * r_t + r_t
139 Nx, Ny = x2[-1], y2[-1]
142 L_noz = bell_percent * ((np.sqrt(eps) - 1.0) * r_t / np.tan(deg2rad(15.0)))
146 method = method.lower()
159 Qx = (c2 - c1) / (m1 - m2)
160 Qy = (m1 * c2 - m2 * c1) / (m1 - m2)
162 t = np.linspace(0.0, 1.0, steps)
163 x3 = (1 - t)**2 * Nx + 2*(1 - t)*t*Qx + t**2 * Ex
164 y3 = (1 - t)**2 * Ny + 2*(1 - t)*t*Qy + t**2 * Ey
168 f
"N = ({Nx:.4g}, {Ny:.4g})\n"
169 f
"Q = ({Qx:.4g}, {Qy:.4g})\n"
170 f
"E = ({Ex:.4g}, {Ey:.4g})\n"
171 f
"θ_n = {theta_n_degree:.2f}°, θ_e = {theta_e_degree:.2f}°\n"
172 f
"ε = {eps:.4g}, L_noz = {L_noz:.4g}"
175 elif method ==
"garcia":
178 Nx, Ny, Ex, Ey, theta_n, theta_e
180 t_vals = np.linspace(t5, t6, steps)
184 "Garcia rotated parabola\n"
185 f
"N = ({Nx:.4g}, {Ny:.4g})\n"
186 f
"E = ({Ex:.4g}, {Ey:.4g})\n"
187 f
"θ_n = {theta_n_degree:.2f}°, θ_e = {theta_e_degree:.2f}°\n"
188 f
"m = {m:.4g}, h = {h:.4g}, k = {k:.4g}\n"
189 f
"ψ = {np.degrees(psi):.2f}°\n"
190 f
"t5 = {t5:.4g}, t6 = {t6:.4g}"
193 elif method ==
"rao_moc":
196 Nx, Ny, Ex, Ey, theta_n, theta_e,
197 steps=steps, gamma=gamma
200 "Rao MoC (placeholder)\n"
201 f
"N = ({Nx:.4g}, {Ny:.4g})\n"
202 f
"E = ({Ex:.4g}, {Ey:.4g})\n"
203 f
"θ_n = {theta_n_degree:.2f}°, θ_e = {theta_e_degree:.2f}°\n"
204 f
"ε = {eps:.4g}, L_noz = {L_noz:.4g}\n"
205 "MoC contour not yet implemented"
208 raise ValueError(f
"Unknown method '{method}'. "
209 "Use 'top', 'garcia', or 'rao_moc'.")
213 np.column_stack((x1, y1)),
214 np.column_stack((x2[1:], y2[1:])),
215 np.column_stack((x3[1:], y3[1:]))
220 plt.figure(figsize=(8, 4))
223 plt.plot(x1, y1, label=
'Entrance arc', color=
'red')
224 plt.plot(x2, y2, label=
'Throat arc', color=
'green')
225 plt.plot(x3, y3, label=f
'Bell ({method})', color=
'blue')
228 plt.plot(pts[:, 0], pts[:, 1],
'k-', linewidth=2)
230 plt.gca().set_aspect(
'equal',
'box')
231 plt.xlabel(
"Axial distance x")
232 plt.ylabel(
"Radius y")
233 plt.grid(
True, alpha=0.3)
234 plt.title(f
"Rao nozzle – {method.upper()}")
238 0.02, 0.02, param_text,
240 va=
"bottom", ha=
"left",
241 bbox=dict(boxstyle=
"round", facecolor=
"white", alpha=0.8)
247 plt.savefig(
'nozzle_models/rao/rao_nozzle.png', dpi=150, bbox_inches=
'tight')
254 return pts, x_first, y_first
rao(area_throat, area_exit, bell_percent=0.8, steps=200, do_plot=True, color_segments=False, method="garcia", gamma=1.23, convergent_half_angle_rad=np.pi/4.0)