NACA airfoil

23.4. NACA airfoil#

The NACA airfoil series is a set of standardized airfoil shapes, which became widely used in the design of aircraft wings. See https://en.wikipedia.org/wiki/NACA_airfoil

We use the Python code naca.py by Dirk Gorissen, and adapted by Xaver Mooslechner and Edoardo Bonetti to Netgen-OCC geometry.

from ngsolve import *
from netgen.occ import *
from ngsolve.webgui import Draw
import matplotlib.pyplot as plt
from naca import naca4
import numpy as np

def occ_naca_profile(alpha = 12, h=0.03):

    xs, ys = naca4("2412", 600)
    pnts = list (zip ( xs, ys, (0,)*len(xs) ))
    
    curve = Wire(SplineApproximation(pnts))
    wing = Face(curve)
    wing.edges.name = "wing"
    wing.edges.maxh = h

    rect = Rectangle(6, 2).Face().Move((-1.5, -1, 0))
    rect.edges.name = "wall"
    rect.edges.Min(X).name="inlet"
    rect.edges.Max(X).name = "outlet"
    wing = wing.Rotate(Axis((0,0,0), Z),-alpha)
    air =  rect - wing
    
    return air
with TaskManager():
    mesh = Mesh(OCCGeometry(occ_naca_profile(alpha=12, h=0.01), dim=2).GenerateMesh(maxh=0.1))
    mesh.Curve(3)
Draw(mesh);
print ("ne = ", mesh.GetNE(VOL))
ne =  4155
def NavierStokes(R, tau , tend):

    V = VectorH1(mesh, order=1, dirichlet="wall|wing|inlet")
    V.SetOrder(TRIG, 3)
    V.Update()

    Q = H1(mesh, order=1)

    X = V*Q

    drag_x_test = GridFunction(X)
    drag_x_test.components[0].Set(CoefficientFunction(
        (-1, 0)), definedon=mesh.Boundaries("wing"))
    drag_y_test = GridFunction(X)
    drag_y_test.components[0].Set(CoefficientFunction(
        (0, -1)), definedon=mesh.Boundaries("wing"))
    time_vals, drag_x_vals, drag_y_vals = [], [], []


    u, p = X.TrialFunction()
    v, q = X.TestFunction()

    stokes = 1/R*InnerProduct(grad(u), grad(v))+div(u)*q+div(v)*p - 1e-10*p*q
    a = BilinearForm(X, symmetric=True)
    a += stokes*dx
    a.Assemble()

    # nothing here ...
    f = LinearForm(X)
    f.Assemble()

    # gridfunction for the solution
    gfu = GridFunction(X)

    # parabolic inflow at inlet:
    uin = CoefficientFunction(((1-y)*(1+y), 0))
    gfu.components[0].Set(uin, definedon=mesh.Boundaries("inlet"))

    # solve Stokes problem for initial conditions:
    inv_stokes = a.mat.Inverse(X.FreeDofs())

    res = f.vec.CreateVector()
    res.data = f.vec - a.mat*gfu.vec
    gfu.vec.data += inv_stokes * res


    # matrix for implicit Euler
    mstar = BilinearForm(X, symmetric=True)
    mstar += (u*v + tau*stokes)*dx
    mstar.Assemble()
    inv = mstar.mat.Inverse(X.FreeDofs(), inverse="sparsecholesky")

    # the non-linear term
    conv = BilinearForm(X, nonassemble=True)
    conv += (grad(u) * u) * v * dx

    # for visualization
    sceneu = Draw(Norm(gfu.components[0]), mesh, "velocity", sd=3)    
    #scenep = Draw(Norm(gfu.components[1]), mesh, "pressure", sd=3)


    # implicit Euler/explicit Euler splitting method:
    t = 0
    i = 0
    with TaskManager():
        while t < tend:
            # print("t=", t, end="\r")

            conv.Apply(gfu.vec, res)
            res.data += a.mat*gfu.vec
            gfu.vec.data -= tau * inv * res

            time_vals.append(t)
            drag_x_vals.append(InnerProduct(res, drag_x_test.vec))
            drag_y_vals.append(InnerProduct(res, drag_y_test.vec))
            t = t + tau
            i = i+1

            if i % 30 == 0:
                sceneu.Redraw()
                #scenep.Redraw()

    
    plt.plot(time_vals, drag_x_vals)
    plt.show()
    plt.plot(time_vals, drag_y_vals)
    plt.show()
R = 1800
tau = 2e-3
tend = 50

NavierStokes(R, tau, tend)
../../_images/3cec77cb013ddb747f7bf921f9322274e76abba65483721de30379d881e34453.png ../../_images/116cd45231c5273841c518f0a34dad03d3b47b7a8f368456de202f91a00ec83c.png