Singular Stokes Problem

Dear All,

I’m trying to solve the stokes equation with Pure Neumann boundary conditions or Traction boundary conditions at the boundaries.


This problem is singular and similiar to (https://fenicsproject.org/olddocs/dolfin/1.3.0/python/demo/documented/singular-poisson/python/documentation.html#) because the velocity field and the pressure field are not determined up to a constant .

mesh=UnitSquareMesh(20,20)
V = VectorFunctionSpace(mesh,‘P’, 1)
p_element=FiniteElement(‘P’, mesh.ufl_cell(),1)
v_element=VectorElement(‘P’,mesh.ufl_cell(),2)
element = MixedElement([p_element, v_element])
FS=FunctionSpace(mesh,element)

q,v=TestFunctions(FS)
P,u=TrialFunctions(FS)

force=df.Expression((‘x[0]’,‘0’),degree=1)
a = inner(grad(u), grad(v))df.dx + div(v)pdf.dx + qdiv(u)*df.dx
L=inner(force,v)*df.dx

  1. Can someone help me with how to remove the null space of the Matrix (a), such that i can eliminate constant velocity and pressure solutions ? similiar to (https://fenicsproject.org/qa/4121/help-parallelising-space-method-eliminating-rigid-motion/) .
  2. For the elasticity problem however there is only one function space. How can one do this procedure for both the pressure and the velocity?
1 Like

Have you looked at this post ?
https://fenicsproject.org/qa/13927/pressure-nullspace-for-stokes-problem/

Hello,

I am also trying to solve the Stokes equation with pure Neumann boundary condition, but I don’t manage to define the correct nullspace.
The problem I am trying to solve is

\begin{align} \Delta {\bf u} - \nabla p = 0 \text{ in } \Omega \\ \text{div} \, {\bf u} = 0 \text{ in } \Omega \\ (\nabla {\bf u}) \cdot {\bf n} - p \, {\bf n} = {\bf g} \text{ on } \partial \Omega \end{align}

and g satisfies the compatibility condition \int_{\partial \Omega} {\bf g} = 0.

Here is the minimal working example with a tentative nullspace:

from mpi4py import MPI
from petsc4py import PETSc
dtype = PETSc.ScalarType  
import ufl
from basix.ufl import element, mixed_element
import dolfinx
from dolfinx.fem.petsc import assemble_matrix_block, assemble_vector_block

domain = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 40, 40)

V_el = element("Lagrange", domain.basix_cell(), 2, shape=(2,)) 
Q_el = element("Lagrange", domain.basix_cell(), 1) 
W_el = mixed_element([V_el,Q_el])
W = dolfinx.fem.functionspace(domain, W_el)
V,V_to_W = W.sub(0).collapse()
Q,Q_to_W = W.sub(1).collapse()

## Definition of the linear forms
(u, p) = ufl.TrialFunction(V), ufl.TrialFunction(Q)
(v, q) = ufl.TestFunction(V), ufl.TestFunction(Q)
a = dolfinx.fem.form([[ ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx , ufl.inner(p, ufl.div(v)) * ufl.dx ], [ufl.inner(ufl.div(u), q) * ufl.dx, None]])

## Neumann BC
x = ufl.SpatialCoordinate(domain)
g = ufl.as_vector([2*x[0]-1, 0.2*x[1]-0.1])
L  = dolfinx.fem.form([ ufl.inner(g, v) * ufl.ds, ufl.inner(dolfinx.fem.Constant(domain, dtype(0)), q) * ufl.dx ])

# Assemble the block operator and RHS vector
A = assemble_matrix_block(a)
A.assemble()
b = assemble_vector_block(L, a)

## Set the nullspace 
bs = W.dofmap.index_map_bs
length0 = W.dofmap.index_map.size_local

basis = [dolfinx.la.vector(W.dofmap.index_map, bs=bs, dtype=dtype) for i in range(3)]
basis_arrays = [_b.array for _b in basis]
dofs_v = [V.sub(i).dofmap.list.flatten() for i in range(2)]
dofs_p = [Q.dofmap.list.flatten()]
basis_arrays[0][dofs_v[0]] = 1.0
basis_arrays[1][dofs_v[1]] = 1.0
basis_arrays[2][dofs_p] = 1.0
dolfinx.la.orthonormalize(basis)
basis_petsc = [
    PETSc.Vec().createWithArray(x[: bs * length0], bsize=bs, comm=W.mesh.comm) 
    for x in basis_arrays
]
nullspace = PETSc.NullSpace().create(vectors=basis_petsc)  

assert nullspace.test(A)
A.setNearNullSpace(nullspace)

# Create a block vector (x) to store the full solution, and solve
# Create a solver
ksp = PETSc.KSP().create(domain.comm)
ksp.setOperators(A)
ksp.setType("preonly")

# Set the solver type to MUMPS (LU solver)
pc = ksp.getPC()
pc.setType("lu")
pc.setFactorSolverType("superlu_dist")
pc.setFactorSetUpSolverType()

# Create a block vector (x) to store the full solution, and solve
x = A.createVecLeft()
ksp.solve(b, x)

# Create Functions and scatter x solution
u, p = dolfinx.fem.Function(V), dolfinx.fem.Function(Q)
offset = V.dofmap.index_map.size_local * V.dofmap.index_map_bs
u.x.array[:offset] = x.array_r[:offset]
p.x.array[: (len(x.array_r) - offset)] = x.array_r[offset:]

The assert nullspace.test(A) raises an error. Is there something wrong with the way I am creating the nullspace? I am quite new to dolfinx.

Any help is appreciated!

In your problem formulation, there is no remaining null space. The constant of the pressure is fixed due to the natural boundary condition: p+const no-longer satisfies the PDE.

Thank you for your quick answer! When you say that there is no nullspace, do you mean only for the pressure or also for the velocity ? The problem seems to be satisfied by u and by u + cst.

I tried changing the nullspace to only take into account constant velocities, i.e. keep the first two basis vectors. The nullspace assert does not raise any errors anymore, but the solution does not converge when I refine the mesh. Is this a well-known issue and if so, do you have any pointers?

Ah, you’re right. For most flow problems (with advection operators and inflow conditions) it is the pressure nullspace that is the issue, hence my automatic fixation. This relates more closely to the null-spaces observed in solids. Is this demo of any use to you? Elasticity using algebraic multigrid — DOLFINx 0.9.0 documentation

Thank you for the pointer. I tried adapting the example to the 2D case:

## Set the nullspace 
bs = W.dofmap.index_map_bs
length0 = W.dofmap.index_map.size_local

basis = [dolfinx.la.vector(W.dofmap.index_map, bs=bs, dtype=dtype) for i in range(3)]
basis_arrays = [_b.array for _b in basis]
dofs_v = [V.sub(i).dofmap.list.flatten() for i in range(2)]
# translation modes
basis_arrays[0][dofs_v[0]] = 1.0
basis_arrays[1][dofs_v[1]] = 1.0

# rotation modes
xdof = V.tabulate_dof_coordinates()
dofs_block = V.dofmap.list.flatten()
x0, x1= xdof[dofs_block, 0], xdof[dofs_block, 1]
basis_arrays[2][dofs_v[0]] = - x1
basis_arrays[2][dofs_v[1]] =   x0 

dolfinx.la.orthonormalize(basis)
basis_petsc = [
    PETSc.Vec().createWithArray(_x[: bs * length0], bsize=bs, comm=W.mesh.comm) 
    for _x in basis_arrays
]
nullspace = PETSc.NullSpace().create(vectors=basis_petsc)  

But the assert raises an error again.
From a mathematical point of view though, including the rigid body motions in the nullspace seems to be the good approach. Maybe I am not defining them correctly?