This page was generated from unit-2.6-stokes/stokes.ipynb.

2.6 Stokes equation

Find \(u \in [H^1_D]^2\) and \(p \in L_2\) such that

\[\begin{split}\DeclareMathOperator{\Div}{div} \begin{array}{ccccll} \int \nabla u : \nabla v & + & \int \Div v \, p & = & \int f v & \forall \, v \\ \int \Div u \, q & & & = & 0 & \forall \, q \end{array}\end{split}\]

Define channel geometry and mesh it:

[1]:
from ngsolve import *
import netgen.gui

from netgen.geom2d import SplineGeometry
geo = SplineGeometry()
geo.AddRectangle( (0, 0), (2, 0.41), bcs = ("wall", "outlet", "wall", "inlet"))
geo.AddCircle ( (0.2, 0.2), r=0.05, leftdomain=0, rightdomain=1, bc="cyl")
mesh = Mesh( geo.GenerateMesh(maxh=0.05))
mesh.Curve(3)
Draw (mesh)

Use Taylor Hood finite element pairing: Continuous \(P^2\) elements for velocity, and continuous \(P^1\) for pressure:

[2]:
V = H1(mesh, order=2, dirichlet="wall|inlet|cyl")
Q = H1(mesh, order=1)
X = V*V*Q

Setup bilinear-form for Stokes. We give names for all scalar field components. The divergence is constructed from partial derivatives of the velocity components.

[3]:
ux,uy,p = X.TrialFunction()
vx,vy,q = X.TestFunction()

div_u = grad(ux)[0]+grad(uy)[1]
div_v = grad(vx)[0]+grad(vy)[1]

a = BilinearForm(X)
a += (grad(ux)*grad(vx)+grad(uy)*grad(vy) + div_u*q + div_v*p) * dx
a.Assemble()
[3]:
<ngsolve.comp.BilinearForm at 0x7fc2372724b0>

Set inhomogeneous Dirichlet boundary condition only on inlet boundary:

[4]:
gfu = GridFunction(X)
uin = 1.5*4*y*(0.41-y)/(0.41*0.41)
gfu.components[0].Set(uin, definedon=mesh.Boundaries("inlet"))
velocity = CoefficientFunction(gfu.components[0:2])
Draw(velocity, mesh, "vel")
Draw(Norm(velocity), mesh, "|vel|")
SetVisualization(max=2)

Solve equation:

[5]:
res = gfu.vec.CreateVector()
res.data = -a.mat * gfu.vec
inv = a.mat.Inverse(freedofs=X.FreeDofs(), inverse="umfpack")
gfu.vec.data += inv * res
Redraw()

Testing different velocity-pressure pairs

Now we define a Stokes setup function to test different spaces:

[6]:
def SolveStokes(X):
    ux,uy,p = X.TrialFunction()
    vx,vy,q = X.TestFunction()
    div_u = grad(ux)[0]+grad(uy)[1]
    div_v = grad(vx)[0]+grad(vy)[1]
    a = BilinearForm(X)
    a += (grad(ux)*grad(vx)+grad(uy)*grad(vy) + div_u*q + div_v*p)*dx
    a.Assemble()
    gfu = GridFunction(X)
    uin = 1.5*4*y*(0.41-y)/(0.41*0.41)
    gfu.components[0].Set(uin, definedon=mesh.Boundaries("inlet"))
    res = gfu.vec.CreateVector()
    res.data = -a.mat * gfu.vec
    inv = a.mat.Inverse(freedofs=X.FreeDofs(), inverse="umfpack")
    gfu.vec.data += inv * res


    velocity = CoefficientFunction(gfu.components[0:2])
    Draw(velocity, mesh, "vel")
    Draw(Norm(velocity), mesh, "|vel|")
    SetVisualization(max=2)

    return gfu

Higher order Taylor-Hood elements:

[7]:
V = H1(mesh, order=4, dirichlet="wall|inlet|cyl")
Q = H1(mesh, order=3)
X = V*V*Q

gfu = SolveStokes(X)

With discontinuous pressure elements P2-P1 is unstable:

[8]:
V = H1(mesh, order=2, dirichlet="wall|inlet|cyl")
Q = L2(mesh, order=1)
print ("V.ndof =", V.ndof, ", Q.ndof =", Q.ndof)
X = V*V*Q

gfu = SolveStokes(X)
V.ndof = 1660 , Q.ndof = 2328
---------------------------------------------------------------------------
NgException                               Traceback (most recent call last)
<ipython-input-1-5ecc0da3557f> in <module>
      4 X = V*V*Q
      5
----> 6 gfu = SolveStokes(X)

<ipython-input-1-abe06cca85c3> in SolveStokes(X)
     12     res = gfu.vec.CreateVector()
     13     res.data = -a.mat * gfu.vec
---> 14     inv = a.mat.Inverse(freedofs=X.FreeDofs(), inverse="umfpack")
     15     gfu.vec.data += inv * res
     16

NgException: UmfpackInverse: Numeric factorization failed.

\(P^{2,+} \times P^{1,dc}\) elements:

[9]:
V = H1(mesh, order=2, dirichlet="wall|inlet|cyl")
V.SetOrder(TRIG,3)
V.Update()
Q = L2(mesh, order=1)
X = V*V*Q
print ("V.ndof =", V.ndof, ", Q.ndof =", Q.ndof)

gfu = SolveStokes(X)
V.ndof = 2436 , Q.ndof = 2328

the mini element:

[10]:
V = H1(mesh, order=1, dirichlet="wall|inlet|cyl")
V.SetOrder(TRIG,3)
V.Update()
Q = H1(mesh, order=1)
X = V*V*Q

gfu = SolveStokes(X)

VectorH1

A vector-valued \(H^1\)-space: Less to type and more possibilities to explore structure and optimize.

[11]:
V = VectorH1(mesh, order=2, dirichlet="wall|inlet|cyl")
V.SetOrder(TRIG,3)
V.Update()
Q = L2(mesh, order=1)
X = V*Q

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

a = BilinearForm(X)
a += (InnerProduct(grad(u),grad(v))+div(u)*q+div(v)*p)*dx
a.Assemble()

gfu = GridFunction(X)
uin = CoefficientFunction( (1.5*4*y*(0.41-y)/(0.41*0.41), 0) )
gfu.components[0].Set(uin, definedon=mesh.Boundaries("inlet"))

res = gfu.vec.CreateVector()
res.data = -a.mat * gfu.vec
inv = a.mat.Inverse(freedofs=X.FreeDofs(), inverse="umfpack")
gfu.vec.data += inv * res
Draw(gfu.components[0], mesh, "vel")
Draw(Norm(gfu.components[0]), mesh, "|vel|")
SetVisualization(max=2)

Stokes as a block-system

We can now define separate bilinear-form and matrices for A and B, and combine them to a block-system:

[12]:
V = VectorH1(mesh, order=3, dirichlet="wall|inlet|cyl")
Q = H1(mesh, order=2)

u,v = V.TnT()
p,q = Q.TnT()

a = BilinearForm(V)
a += InnerProduct(Grad(u),Grad(v))*dx

b = BilinearForm(trialspace=V, testspace=Q)
b += div(u)*q*dx

a.Assemble()
b.Assemble()
[12]:
<ngsolve.comp.BilinearForm at 0x7fc1fc05b430>

Needed as preconditioner for the pressure:

[13]:
mp = BilinearForm(Q)
mp += SymbolicBFI(p*q)
mp.Assemble()
[13]:
<ngsolve.comp.BilinearForm at 0x7fc1fc05be30>

Two right hand sides for the two spaces:

[14]:
f = LinearForm(V)
f += CoefficientFunction((0,x-0.5)) * v * dx
f.Assemble()

g = LinearForm(Q)
g.Assemble()
[14]:
<ngsolve.comp.LinearForm at 0x7fc23728cd30>

Two GridFunctions for velocity and pressure:

[15]:
gfu = GridFunction(V, name="u")
gfp = GridFunction(Q, name="p")
uin = CoefficientFunction( (1.5*4*y*(0.41-y)/(0.41*0.41), 0) )
gfu.Set(uin, definedon=mesh.Boundaries("inlet"))

Combine everything to a block-system. BlockMatrix and BlockVector store references to the original matrices and vectors, no new large matrices are allocated. The same for the transpose matrix b.mat.T. It stores a wrapper for the original matrix, and replaces the call of the Mult function by MultTrans.

[16]:
K = BlockMatrix( [ [a.mat, b.mat.T], [b.mat, None] ] )
C = BlockMatrix( [ [a.mat.Inverse(V.FreeDofs()), None], [None, mp.mat.Inverse()] ] )

rhs = BlockVector ( [f.vec, g.vec] )
sol = BlockVector( [gfu.vec, gfp.vec] )

solvers.MinRes (mat=K, pre=C, rhs=rhs, sol=sol, initialize=False)
it =  0  err =  4.330331719284449
it =  1  err =  2.263376252368188
it =  2  err =  1.892596018794661
it =  3  err =  1.5277218937090973
it =  4  err =  1.477239523481512
it =  5  err =  1.274735684725765
it =  6  err =  1.2273603845592391
it =  7  err =  1.0796300650361754
it =  8  err =  0.9926584398274219
it =  9  err =  0.8697378675127282
it =  10  err =  0.8161936202843121
it =  11  err =  0.7258884768970278
it =  12  err =  0.7046076566530918
it =  13  err =  0.6436324016636952
it =  14  err =  0.6266221984432979
it =  15  err =  0.5916810219232755
it =  16  err =  0.5778715871111323
it =  17  err =  0.549176046330226
it =  18  err =  0.5321141023361267
it =  19  err =  0.5051363045380457
it =  20  err =  0.49384479466562703
it =  21  err =  0.4774895455235826
it =  22  err =  0.4599902550854831
it =  23  err =  0.4364898094139756
it =  24  err =  0.4133799170989269
it =  25  err =  0.3794172698051347
it =  26  err =  0.3467212727075729
it =  27  err =  0.30515210982508734
it =  28  err =  0.2562184622778722
it =  29  err =  0.19298119618589246
it =  30  err =  0.14828872631351997
it =  31  err =  0.09777554647013943
it =  32  err =  0.0826691522026137
it =  33  err =  0.04791189436702945
it =  34  err =  0.046501316676194455
it =  35  err =  0.02451652342523642
it =  36  err =  0.024258262573804402
it =  37  err =  0.013783413304190122
it =  38  err =  0.01377870798914304
it =  39  err =  0.00929990015205672
it =  40  err =  0.009197896010403769
it =  41  err =  0.007032853176071403
it =  42  err =  0.006969959341190438
it =  43  err =  0.004349282016210588
it =  44  err =  0.004333346370701045
it =  45  err =  0.002611549066276336
it =  46  err =  0.0026041412805825233
it =  47  err =  0.0016431158913758459
it =  48  err =  0.0016155902987664604
it =  49  err =  0.0010770904760433794
it =  50  err =  0.001051408620379137
it =  51  err =  0.0005992805940423727
it =  52  err =  0.0005973146813557097
it =  53  err =  0.00040500285097324094
it =  54  err =  0.00040241116621764016
it =  55  err =  0.0002548798923739004
it =  56  err =  0.0002532556657858948
it =  57  err =  0.0001356741878066668
it =  58  err =  0.00013399840971268397
it =  59  err =  5.157781413888791e-05
it =  60  err =  5.1376698525620814e-05
it =  61  err =  2.4344845664788426e-05
it =  62  err =  2.4115681238272087e-05
it =  63  err =  1.117162711433075e-05
it =  64  err =  1.1166553736312179e-05
it =  65  err =  4.212375869590145e-06
it =  66  err =  4.208513731812889e-06
it =  67  err =  1.8401603860654895e-06
it =  68  err =  1.8401398380931368e-06
it =  69  err =  6.559763740182316e-07
it =  70  err =  6.558824933133116e-07
it =  71  err =  2.0404301979287836e-07
it =  72  err =  2.0386422019701638e-07
it =  73  err =  9.496875179504776e-08
[16]:
basevector
[17]:
Draw (gfu)
[ ]:

[ ]: