In this example we will have a look on how we can solve a multiphysics problem in NGSolve and the NGSPy interface. Therefore, we will consider a fluid-structure interaction problem, where the Navier-Stokes equations for the fluid part
and the elastic wave equation for the elastic part
are used.
The Navier-Stokes equations are given in Eulerian form, whereas the elasticity problem is formulated in Lagrangian coordinates. Thus, to couple these two equations we use the Arbitrary Lagrangian Eulerian form (short ALE) for the fluid part.
We will work always on a fixed reference domain. Therefore, we will transform the Navier-Stokes equations back to it. Let
where
The time derivative of the deformation
The elastic wave equation does not have to be transformed. We rewrite it as a system of first order equations in time and the variational formulation is
Beneath the displacement
The interface conditions for fluid-structure interaction have also to be considered before we can couple the equations. The velocity and displacement have to be continuous over the interface and the forces from the fluid and the solid have to be in equilibrium
As we are using a monolithic approach, where both equations are solved together in each time step, this equation is always fulfilled as a natural condition in weak sense. Therefore, we add both, the Navier-Stokes and the elastic wave equation, and then simply neglect the two force terms.
The last important ingredient is the deformation extension. The mesh velocity
where
As an example we will consider the following benchmark propsed by Turek and Hron. It is based on the classical flow around cylinder benchmark, where additionally an elastic flag is "glued" behind the obsticle.
The geometry can be constructed easily with splines,
def GenerateMesh(order, maxh=0.12, L=2.5, H=0.41): r = 0.05 D = 0.1 geom = SplineGeometry() pnts = [ (0,0), (L,0), (L,H), (0,H), (0.2,0.15), (0.240824829046386,0.15), (0.248989794855664,0.19), (0.25,0.2), (0.248989794855664,0.21), (0.240824829046386,0.25), (0.2,0.25), (0.15,0.25), (0.15,0.2), (0.15,0.15), (0.6,0.19), (0.6,0.21), (0.55,0.19), (0.56,0.15), (0.6,0.15), (0.65,0.15), (0.65,0.2),(0.65,0.25), (0.6,0.25), (0.56,0.25), (0.55,0.21), (0.65,0.25),(0.65,0.15) ] pind = [ geom.AppendPoint(*pnt) for pnt in pnts ] geom.Append(['line',pind[0],pind[1]],leftdomain=1,rightdomain=0,bc="wall") geom.Append(['line',pind[1],pind[2]],leftdomain=1,rightdomain=0,bc="outlet") geom.Append(['line',pind[2],pind[3]],leftdomain=1,rightdomain=0,bc="wall") geom.Append(['line',pind[3],pind[0]],leftdomain=1,rightdomain=0,bc="inlet") geom.Append(['spline3',pind[4],pind[5],pind[6]],leftdomain=0,rightdomain=1,bc="circ") geom.Append(['spline3',pind[6],pind[7],pind[8]],leftdomain=0,rightdomain=2,bc="circ2") geom.Append(['spline3',pind[8],pind[9],pind[10]],leftdomain=0,rightdomain=1,bc="circ") geom.Append(['spline3',pind[10],pind[11],pind[12]],leftdomain=0,rightdomain=1,bc="circ") geom.Append(['spline3',pind[12],pind[13],pind[4]],leftdomain=0,rightdomain=1,bc="circ") geom.Append(['line',pind[6],pind[14]],leftdomain=2,rightdomain=1,bc="interface") geom.Append(['line',pind[14],pind[15]],leftdomain=2,rightdomain=1,bc="interface") geom.Append(['line',pind[15],pind[8]],leftdomain=2,rightdomain=1,bc="interface") geom.SetMaterial(1, "fluid") geom.SetMaterial(2, "solid") mesh = Mesh(geom.GenerateMesh(maxh=maxh)) mesh.Curve(order) return mesh
where the boundaries are identified with strings, and also the domains have names.
For the spatial discretization we will use the Taylor-Hood elements for the Navier-Stokes equations and also H1-conforming elements for the elastic wave equation. Thus, we can use one global space for the velocity and the displacement.
V = VectorH1( mesh, order = order, dirichlet = "inlet|wall|circ|circ2" ) Q = H1( mesh, order = order - 1, definedon = "fluid" ) D = VectorH1( mesh, order = order, dirichlet = "inlet|wall|outlet|circ|circ2" )
With the definedon flag, we can tell the pressure space, that it lives only on the fluid domain.
Before we come to the bilinearforms, we define the following functions.
def SolidBFI(term, **args): return SymbolicBFI(term.Compile(), definedon=mesh.Materials("solid"), **args) def FluidBFI(term, **args): return SymbolicBFI(term.Compile(), definedon=mesh.Materials("fluid"), **args)
With the definedon flag, we can tell the bilinearform, that it is only defined on one domain, either on the fluid or on the solid domain.
For the time discretization we will use the Crank-Nicolson method
# M du/dt a += FluidBFI( 0.5*rhof/tau*InnerProduct(J*(u-velocityold), v)+0.5*rhof/tau*InnerProduct(Jold*(u-velocityold), v) ) # Laplace a += FluidBFI( 0.5*rhof*nuf*InnerProduct(J*grad(u)*Finv, grad(v)*Finv)+0.5*rhof*nuf*InnerProduct(Jold*2*Sym(graduold*Finvold), (grad(v)*Finvold)) ) # Convection a += FluidBFI( 0.5*rhof*InnerProduct(J*(grad(u)*Finv)*u, v)+0.5*rhof*InnerProduct(Jold*(graduold*Finvold)*velocityold, v) ) # mesh velocity a += FluidBFI( -0.5*rhof/tau*InnerProduct((J*grad(u)*Finv)*(d-deformationold), v)-0.5*rhof/tau*InnerProduct((Jold*graduold*Finvold)*(d-deformationold), v) ) # Pressure/Constraint a += FluidBFI( -J*(Trace(grad(v)*Finv)*p+Trace(grad(u)*Finv)*q)- 1e-9*p*q )
The elasticity problem is formulated with the following lines.
# M du/dt a += SolidBFI( rhos/tau*InnerProduct(u-velocityold, v) ) # Material law a += SolidBFI( 0.5*InnerProduct(2*F*Stress(E), grad(v))+0.5*InnerProduct(2*Fold*Stress(Eold), grad(v)) ) #dd/dt = u a += SolidBFI( InnerProduct(u+velocityold-2.0/tau*(d-deformationold), w))
Next, we extend the deformation from the solid to the fluid domain.
factor = 1e-20*mus def minCF(a,b) : return IfPos(a-b, b, a) Vdist = H1(mesh, order=2) gfdist = GridFunction(Vdist) gfdist.Set( minCF( (x-0.6)*(x-0.6)+(y-0.19)*(y-0.19), (x-0.6)*(x-0.6)+(y-0.21)*(y-0.21) ) ) def NeoHookExt (C, mu=1,lam=1): return 0.5 * mu * (Trace(C-I) + 2*mu/lam * Pow(Det(C), -lam/2/mu) - 1) a += SymbolicEnergy( (factor*(1/sqrt(gfdist*gfdist+1e-12)) * NeoHookExt(C)).Compile(), definedon=mesh.Materials("fluid"))
To increase the inflow velocity depending on time, we have to extend the new velocity into the domain, before solving the system. This can be done by solving a stokes problem with the new velocity as dirichlet data only on the fluid domain. To tell the solver on which domain it should work, we have to define the according degrees of freedom, which is done in terms of bitarrays.
bts = Y.FreeDofs() & ~Y.GetDofs(mesh.Materials("solid")) bts &= ~Y.GetDofs(mesh.Boundaries("wall|inlet|circ|interface|circ2")) invstoke = stokes.mat.Inverse(bts, inverse = inverse)
Now, we can use Newton's method to solve the arising system.
while t < tend-tau/2.0: #update velocity par.Set(Force(t)-Force(t-tau)) tmp.components[0].Set( uinflow, BND, definedon=mesh.Boundaries("inlet") ) rstokes.data = stokes.mat*tmp.vec tmp.vec.data -= invstoke*rstokes gfu.components[0].vec.data += tmp.components[0].vec gfu.components[1].vec.data += tmp.components[1].vec #Newton's method for it in range(10): a.Apply(gfu.vec, r.vec) a.AssembleLinearization(gfu.vec) inv = a.mat.Inverse(X.FreeDofs(), inverse=inverse) w.data = inv*r.vec err = InnerProduct(w, r.vec) gfu.vec.data -= w if abs(err) < 1e-20: break Redraw(blocking=True) gfuold.vec.data = gfu.vec