It looks like you’re observing suboptimal convergence of the Stokes system in a non-convex domain. This is a pretty well known result and is typically noticed by singularities in the pressure approximation.
Consider my modification of your example. Let
\Vert \mathbf{v} \Vert_\text{noslip} = \sqrt{\int_{\partial\Omega_\text{wall}} \mathbf{v}^2 \mathrm{d} s}
such that we measure \Vert \mathbf{u}_h \Vert_\text{noslip} and \Vert \bar{\mathbf{u}}_h \Vert_\text{noslip}.
You should see that \Vert \bar{\mathbf{u}}_h \Vert_\text{noslip} = 0 by virtue of the strong imposition of boundary data, however \Vert \mathbf{u}_h \Vert_\text{noslip} \leq C h converges at the suboptimal rate. I’d expect to see this in typical FE schemes that weakly impose boundary data in non-convex computational domains, e.g: Nitsche’s method, DG methods, Langrange multiplier methods etc.
import numpy as np
import geopart.stokes.incompressible
import dolfin
import dolfin_dg as dg
dolfin.parameters["refinement_algorithm"] = "plaza_with_parent_facets"
#Load mesh and boundary
mesh = dolfin.Mesh()
hdf = dolfin.HDF5File(mesh.mpi_comm(), "mesh_boundaries.h5", "r")
hdf.read(mesh, "/mesh", False)
boundaries = dolfin.MeshFunction('size_t', mesh, mesh.topology().dim() - 1)
mark = {"Internal":0, "wall": 1,"inlet": 2,"outlet": 3 }
hdf.read(boundaries, "/boundaries")
hdf.close()
m_max = 3
u_noslip_l2 = np.zeros(m_max, dtype=np.double)
ubar_noslip_l2 = np.zeros(m_max, dtype=np.double)
hmaxs = np.zeros(m_max, dtype=np.double)
for m in range(m_max):
for j in range(m):
cf = dolfin.MeshFunction("bool", mesh, mesh.topology().dim(), True)
mesh2 = dolfin.refine(mesh, cf, False)
boundaries = dolfin.adapt(boundaries, mesh2)
mesh = mesh2
#Define HDG element and function space
element_cls = geopart.stokes.incompressible.HDG2()
W = element_cls.function_space(mesh)
ds = dolfin.Measure('ds',domain=mesh,subdomain_data=boundaries)
n = dolfin.FacetNormal(mesh)
#Define boundary condition
U = element_cls.solution_variable(W)
mu = dolfin.Constant(0.001) # dynamic viscosity
p_in = dolfin.Constant(0.1) # pressure inlet
p_out = dolfin.Constant(0.0) # pressure outlet
noslip = dolfin.Constant([0.0]*mesh.geometry().dim()) # no-slip wall
#Boundary conditions
gN1 = (- p_out*dolfin.Identity(mesh.geometry().dim())) * n
Neumann_outlet=dg.DGNeumannBC(ds(mark["outlet"]), gN1)
gN2 = (- p_in*dolfin.Identity(mesh.geometry().dim())) * n
Neumann_inlet=dg.DGNeumannBC(ds(mark["inlet"]), gN2)
Dirichlet_wall=dg.DGDirichletBC(ds(mark["wall"]), noslip)
weak_bcs = [Dirichlet_wall,Neumann_inlet,Neumann_outlet]
#Body force term
f = dolfin.Constant([0.0]*mesh.geometry().dim())
model = geopart.stokes.incompressible.StokesModel(eta=mu,f=f)
#Form Stokes
A, b = dolfin.PETScMatrix(), dolfin.PETScVector()
element_cls.solve_stokes(W, U, (A, b), weak_bcs, model)
uh, ph = element_cls.get_velocity(U), element_cls.get_pressure(U)
uhbar = U[1].sub(0)
u_noslip_l2[m] = dolfin.assemble(uh ** 2 * ds(mark["wall"])) ** 0.5
ubar_noslip_l2[m] = dolfin.assemble(uhbar ** 2 * ds(mark["wall"])) ** 0.5
hmaxs[m] = dolfin.MPI.max(mesh.mpi_comm(), mesh.hmax())
dolfin.XDMFFile("ph.xdmf").write_checkpoint(ph, "p")
print("||u||_noslip", u_noslip_l2)
print("||u||_noslip rates",
np.log(u_noslip_l2[1:] / u_noslip_l2[:-1])
/ np.log(hmaxs[1:] / hmaxs[:-1]))
print("||ubar||_noslip", ubar_noslip_l2)
The pressure solution p_h on cells. Note the singularities at the re-entrant corners.
I can’t find a reference in the context of HDG problems, but perhaps this paper regarding the DG approximation of Stokes in a non convex domain is a good place to start.
That all being said. A key result of HDG methods is the error in the velocity approximation is independent of the pressure approximation. So perhaps there’s something I’m missing about EDG-HDG vs HDG.