codexnaca-2412-normal-offset-geometry.py
naca-2412-normal-offset-geometry.py
"""Original NACA 2412 normal-offset evaluation; writes white PNG in cwd."""
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
def mean_line(x,m=.02,p=.4):
x=np.asarray(x)
if m==0:return np.zeros_like(x),np.zeros_like(x)
assert 0<p<1
y=np.where(x<=p,m/p**2*(2*p*x-x*x),m/(1-p)**2*(1-2*p+2*p*x-x*x))
slope=np.where(x<=p,2*m/p**2*(p-x),2*m/(1-p)**2*(p-x))
return y,slope
def coordinates(x):
yc,slope=mean_line(x)
yt=.6*(.2969*np.sqrt(x)-.126*x-.3516*x*x+.2843*x**3-.1015*x**4)
theta=np.arctan(slope)
return x-yt*np.sin(theta),yc+yt*np.cos(theta),x+yt*np.sin(theta),yc-yt*np.cos(theta),yc,yt,theta
def main():
x=(1-np.cos(np.linspace(0,np.pi,1001)))/2
xu,yu,xl,yl,yc,yt,theta=coordinates(x)
np.testing.assert_allclose((xu+xl)/2,x,atol=1e-15)
np.testing.assert_allclose((yu+yl)/2,yc,atol=1e-15)
np.testing.assert_allclose(np.hypot(xu-xl,yu-yl),2*yt,atol=1e-15)
# Offset is perpendicular to the mean-line tangent.
np.testing.assert_allclose((xu-x)+(yu-yc)*np.tan(theta),0,atol=1e-15)
a=coordinates(np.array([.4]))
np.testing.assert_allclose([a[1][0],a[3][0]],[.07803010847647902,-.038030108476479016])
np.testing.assert_allclose(mean_line(np.array([.4]))[1],0)
np.testing.assert_allclose(mean_line(x,m=0,p=0)[0],0)
assert np.isclose(.0005**2/(2*.00004),.003125)
assert np.isclose(.001**2/(2*.00021),.002380952380952381)
fig,axes=plt.subplots(1,2,figsize=(9.6,3.6),dpi=100,facecolor='white',gridspec_kw={'width_ratios':[1.5,1]})
ax=axes[0]
ax.plot(xu,yu,color='#2475ad',lw=2,label='Airfoil surfaces')
ax.plot(xl,yl,color='#2475ad',lw=2)
ax.plot(x,yc,color='#c47c22',ls='--',lw=1.8,label='Mean camber line')
ax.axhline(0,color='#555555',ls=':',lw=1,label='Chord reference')
ax.scatter([.4,.4],[a[1][0],a[3][0]],color='#b43f32',s=25,zorder=3)
ax.set(xlim=(-.025,1.025),ylim=(-.08,.13),xlabel='Chordwise coordinate x/c',ylabel='Surface coordinate y/c',title='NACA 2412, normal-offset surfaces')
ax.set_aspect('equal',adjustable='box')
axes[1].plot(x,yc,color='#c47c22',lw=2)
axes[1].axvline(.4,color='#888888',ls='--',lw=1)
axes[1].axhline(.02,color='#888888',ls='--',lw=1)
axes[1].scatter([.4],[.02],color='#b43f32',s=30,zorder=3)
axes[1].set(xlim=(0,1),ylim=(0,.025),xlabel='Mean-line parameter ξ',ylabel='Mean-line ordinate y_c/c',title='Camber function (vertical scale expanded)')
axes[1].text(.48,.021,'m = 0.02; p = 0.4',fontsize=10)
for axis in axes:axis.grid(alpha=.2)
handles,labels=ax.get_legend_handles_labels()
fig.legend(handles,labels,loc='lower center',ncol=3,fontsize=9)
fig.tight_layout(rect=(0,.1,1,1))
fig.savefig(Path.cwd()/(Path(__file__).stem+'.png'),facecolor='white')
plt.close(fig)
if __name__=='__main__':main()