Pressure distribution on the Apollo Command Module
In the following notebook we are quickly going to check the pressure distribution on the Apollo Command Module computed with the modified Newtonian theory, and compare it with laboratory data.
For more information check:
“Hypersonic Aerothermodynamics”, Chapter 6, by John J. Bertin
“The effect of protuberances, cavities and angle of attack on the wind-tunnel pressure and heat-transfer distribution for the Apollo Command Module”, John J. Bertin
Note: the results will not be commented here. Refer to the above source to better understand the differences between computed results and lab-data.
Apollo Command Module geometry
[1]:
import matplotlib.pyplot as plt
import numpy as np
R1 = 4.694
R2 = 0.196
R3 = 0.39
Rb = 1.956
# half-angle of the conical section
theta1 = 33
# angle of the intersection between arc of radius R1 with
# arc of radius R2
theta2 = 23.045
A quick drawing to visualize the profile:
[2]:
t = np.deg2rad(np.linspace(0, theta2, 100))
x1 = R1 * np.cos(t)
y1 = R1 * np.sin(t)
t = np.deg2rad(np.linspace(theta2, 90 + theta1, 100))
x2 = R2 * np.cos(t) + x1[-1] - R2 * np.cos(np.deg2rad(theta2))
y2 = R2 * np.sin(t) + y1[-1] - R2 * np.sin(np.deg2rad(theta2))
t = np.deg2rad(np.linspace(90 + theta1, 180, 100))
d = 1.91627
x3 = R3 * np.cos(t) + d
y3 = R3 * np.sin(t)
x = np.concatenate([x1, x2, x3])
x = np.concatenate([x, np.flip(x)])
y = np.concatenate([y1, y2, y3])
y = np.concatenate([y, -np.flip(y)])
plt.figure()
plt.plot(x, y)
plt.arrow(0, 0, R1 - 0.12, 0, width=0.0005, head_width=0.08)
plt.annotate("$R_{1}$", (2 * R1 / 3, 0.1))
plt.arrow(4.13937, 1.760, 0, R2, width=0.0005)
plt.arrow(4.13937, 1.760 + 3 * R2 + 0.1, 0, -2 * R2, width=0.0005, head_width=0.08)
plt.annotate("$R_{2}$", (4.13937 + 0.05, 1.760 + 2 * R2))
plt.arrow(4.13937, 0, 0, -Rb + 0.12, width=0.0005, head_width=0.08)
plt.annotate("$R_{b}$", (4.13937 + 0.05, -Rb / 2))
t = np.deg2rad(np.linspace(0, theta1))
x4 = 1.06934 + (2.2 - 1.06934) * np.cos(t)
y4 = (2.2 - 1.06934) * np.sin(t)
plt.plot(x4, y4, 'k:')
plt.annotate(r"$\theta_{1}$", (2.25, 0.35))
plt.plot([0, R1 * np.cos(np.deg2rad(theta2))], [0, R1 * np.sin(np.deg2rad(theta2))], 'k--', alpha=0.5)
t = np.deg2rad(np.linspace(0, theta2))
x5, y5 = 3.85 * np.cos(t), 3.85 * np.sin(t)
plt.plot(x5, y5, 'k:')
plt.annotate(r"$\theta_{2}$", (3.85, 0.75))
t = np.deg2rad(np.linspace(0, 5))
x6, y6 = 4.8 * np.cos(t), 4.8 * np.sin(t)
plt.plot(x6, y6, 'k')
plt.arrow(x6[-1], y6[-1], -0.005, 0.1, head_width=0.08)
plt.annotate("s", (4.85, 0.25))
plt.xlabel("x [m]")
plt.ylabel("y [m]")
plt.title("Apollo Command Module profile")
plt.axis('scaled')
plt.show()
Let’s now compute the length \(s\) and the associated local body angles \(\theta_{b}\):
[3]:
theta3 = 90 - theta2
# arc length of radius R1
sR1 = np.deg2rad(theta2) * R1
# total length up to the maximum body radius Rb
s = sR1 + np.deg2rad(theta3) * R2
# discretize the length
s_arr = np.linspace(0, s, 200)
# local body angle
theta_b = np.zeros_like(s_arr)
for i, v in enumerate(s_arr):
if v <= sR1:
theta_b[i] = np.pi / 2 - s_arr[i] / R1
else:
theta_b[i] = np.pi / 2 - ((s_arr[i] - sR1) / R2 + np.deg2rad(theta2))
s_arr = np.concatenate([np.flip(-s_arr), s_arr])
theta_b = np.concatenate([np.pi - np.flip(theta_b), theta_b])
Note:
\(s / R_{b} = 0.965\) defines the tangency point between the arc of radius \(R_{1}\) and the arc of radius \(R_{2}\).
\(s / R_{b} = 1.082\) corresponds to the maximum body radius \(R_{b}\).
0deg angle of attack
The experimental data is given here. The first column represents \(s / R_{B}\) while the second column is \(p_{s} / p_{t2}\).
[4]:
# free stream Mach number
Minf = 10.18
data_0deg = np.array([
[-1.027237354085603, 0.1834808259587022],
[-0.9478599221789883, 0.5775811209439529],
[-0.7844357976653697, 0.8442477876106195],
[-0.2334630350194553, 0.983480825958702],
[0.03268482490272362, 0.9929203539823008],
[0.2894941634241248, 0.966961651917404],
[0.5649805447470819, 0.9244837758112094],
[0.7003891050583655, 0.8418879056047198],
[0.8357976653696495, 0.8135693215339233],
[0.891828793774319, 0.7734513274336283],
[0.943190661478599, 0.7097345132743362],
[0.9992217898832685, 0.5256637168141592],
[1.045914396887159, 0.17640117994100302],
[1.0972762645914398, 0.018289085545722727]
])
Let’s now compute the pressure distribution with a modified newtonian flow model:
[5]:
from pygasflow.newton import modified_newtonian_pressure_ratio
ps_pt2 = modified_newtonian_pressure_ratio(Minf, theta_b)
plt.figure()
plt.plot(data_0deg[:, 0], data_0deg[:, 1], 's', label="lab data")
plt.plot(s_arr / Rb, ps_pt2, label="Modified Newtonian theory")
plt.xlabel(r"$s / R_{b}$")
plt.ylabel(r"$p_{s} / p_{t2}$")
plt.title(r"$M_{\infty} = 10.18 \qquad \alpha = 0°$")
plt.legend()
plt.show()
33deg angle of attack
The experimental data is given here. The first column represents \(s / R_{B}\) while the second column is \(p_{s} / p_{t2}\).
[6]:
data_33deg = np.array([
[-1.0217821782178214, 0.06461538461538452],
[-0.9623762376237623, 0.24615384615384595],
[-0.7960396039603954, 0.36307692307692285],
[-0.2554455445544548, 0.6030769230769228],
[0.00594059405940639, 0.723076923076923],
[0.27326732673267395, 0.8307692307692307],
[0.5465346534653475, 0.9353846153846151],
[0.6831683168316838, 0.9969230769230768],
[0.8198019801980205, 1.006153846153846],
[0.8613861386138622, 1.003076923076923],
[0.9267326732673276, 0.9938461538461537],
[0.9801980198019811, 0.9323076923076921],
[1.0455445544554465, 0.6492307692307691],
[1.0990099009901, 0.2338461538461536]
])
Let’s now compute the pressure distribution with a modified newtonian flow model:
[7]:
ps_pt2 = modified_newtonian_pressure_ratio(Minf, theta_b, alpha=np.deg2rad(33), beta=np.deg2rad(0))
plt.figure()
plt.plot(data_33deg[:, 0], data_33deg[:, 1], 's', label="lab data")
plt.plot(s_arr / Rb, ps_pt2, label="Modified Newtonian theory")
plt.xlabel(r"$s / R_{b}$")
plt.ylabel(r"$p_{s} / p_{t2}$")
plt.title(r"$M_{\infty} = 10.18 \qquad \alpha = 33°$")
plt.legend()
plt.show()
Location of Stagnation Point as a function of the angle of attack
First, let’s load a few experimental data:
[8]:
data1 = np.array([
[0, -0.11709601873536712],
[0.5274418604651163, 15.05854800936767],
[0.5302325581395348, 20.257611241217788],
[0.6697674418604653, 25.17564402810305],
[0.6697674418604653, 31.21779859484777],
[0.8037209302325581, 25.31615925058547],
[0.8009302325581396, 28.266978922716618],
[0.798139534883721, 30.093676814988285],
[0.8009302325581396, 31.49882903981264],
[0.8065116279069766, 33.18501170960187],
[0.8483720930232559, 33.18501170960187],
[0.8762790697674419, 33.18501170960187],
[0.8483720930232559, 30.23419203747072],
[0.9069767441860466, 39.929742388758775],
[0.9683720930232558, 44.98829039812646],
[0.9627906976744187, 50.46838407494145]
])
data2 = np.array([
[0, -0.11709601873536712],
[0.041860465116279055, 2.4121779859484747],
[0.08372093023255811, 4.941451990632316],
[0.10883720930232554, 6.065573770491795],
[0.1590697674418604, 8.032786885245898],
[0.22046511627906962, 9.578454332552681],
[0.2790697674418603, 10.983606557377044],
[0.32651162790697663, 12.107728337236523],
[0.37953488372093025, 13.793911007025756],
[0.43255813953488387, 15.761124121779858],
[0.4800000000000002, 17.728337236533946],
[0.521860465116279, 19.69555035128805],
[0.5804651162790697, 22.646370023419195],
[0.6418604651162791, 26.018735362997646],
[0.6948837209302325, 29.39110070257611],
[0.7479069767441859, 32.903981264637],
[0.798139534883721, 36.557377049180324],
[0.8483720930232559, 40.35128805620609],
])
Now, we are going to use the modified newtonian theory to estimate the location of the stagnation point:
[9]:
from scipy.optimize import minimize
Mfs = 10.18
beta = 0
gamma = 1.4
[10]:
def location_of_stagnation_point(s_arr, theta_b, theta):
j = 0
go_on = True
t1, t2 = None, None
s1, s2 = None, None
while (j < len(theta_b) - 1) and go_on:
if (theta <= theta_b[j]) and (theta >= theta_b[j + 1]):
t1 = theta_b[j]
t2 = theta_b[j + 1]
s1 = s_arr[j]
s2 = s_arr[j + 1]
go_on = False
j += 1
if s1 == s2:
return s1
# linear interpolation: the finer the discretization the more
# accurate the result
t_percent = (theta - t1) / (t2 - t1)
return s1 + t_percent * (s2 - s1)
All we have to do is to find the \(\theta_{b}\) value at which \(P_{s} / P_{t2}\) is maximum.
[11]:
# function to minimize
func = lambda theta_b, M, alpha=0, beta=0, gamma=1.4: 1.0 / modified_newtonian_pressure_ratio(M, theta_b, alpha=alpha, beta=beta, gamma=gamma)
alpha = np.deg2rad(np.linspace(0, 70, 100))
loc = np.zeros_like(alpha)
for i, a in enumerate(alpha):
res = minimize(func, np.pi/2, bounds=[(0, np.pi)], args=(Mfs, a), tol=1e-10)
loc[i] = location_of_stagnation_point(s_arr, theta_b, res.x[0])
plt.figure()
plt.plot(data1[:, 0], data1[:, 1], 's', label="lab data 1")
plt.plot(data2[:, 0], data2[:, 1], '--', label="lab data 2")
plt.plot(loc / Rb, np.rad2deg(alpha), label="Modified Newtonian theory")
plt.xlabel("$s / R_{b}$")
plt.ylabel(r"$\alpha$ [deg]")
plt.title("Location of the Stagnation Point")
plt.legend()
plt.show()
Again, the exaplanation of the result can be found in the aforementioned sources.
[ ]: