Issues with Defining Boundary Conditions for Stokes Problem Using GMSH Mesh Tags

I’m currently working on solving a Stokes flow problem and have encountered difficulties in defining the boundary conditions accurately. My workflow involves importing a mesh file generated in Gmsh, and then reading the meshtags for defining boundary conditions. Here’s a snippet of my code for context:

python code

# Load mesh and markers
mesh, cell_markers, facet_markers = gmshio.read_from_msh('path/mesh.msh', MPI.COMM_WORLD, gdim=3)

# Define function spaces
P2 = ufl.VectorElement('CG', mesh.ufl_cell(), 2)
P1 = ufl.FiniteElement('CG', mesh.ufl_cell(), 1)
element = ufl.MixedElement([P2, P1])
W = fem.FunctionSpace(mesh, element)

V, V_to_W = W.sub(0).collapse()
Q, Q_to_W = W.sub(1).collapse()

# Define boundary conditions using facet markers
inlet_facets = facet_markers.indices[facet_markers.values == boundary_numbers['inlet']]
inlet_dofs = locate_dofs_topological(V, mesh.topology.dim - 1, inlet_facets)
bcu_inflow = dirichletbc(PETSc.ScalarType((0.0,0.0,-1)), inlet_dofs, V)

walls_facets = facet_markers.indices[facet_markers.values == boundary_numbers['walls']]
walls_dofs = locate_dofs_topological(V, mesh.topology.dim - 1, walls_facets)
bcu_walls = dirichletbc(PETSc.ScalarType((0.0,0.0,0.0)), walls_dofs, V)

bcs = [bcu_inflow, bcu_walls]

While the code executes without errors, the results I obtain are unexpected, leading me to suspect that the boundary conditions might not be defined properly. Adjusting the mesh.topology.dim - 1 value to 1 or 3 does not influence the outcome, and I continue to get the same results without any errors.

How can I solve this issue and ensure that the boundary conditions are applied correctly?

I appreciate any insights or advice you could provide to help resolve this issue.

Thank you!

The snippet looks good, but without the actual mesh it’s unlikely anyone will be able to understand what may be wrong.

Thank you for your response. Below, I’ve provided a part of my .msh file along with the modified code snippet that includes more detailed boundary condition definitions and the use of boundary markers.

Part of the .msh File:

$MeshFormat
2.2 0 8
$EndMeshFormat
$PhysicalNames
9
2 92 “inlet1”
2 93 “inlet2”
2 94 “outlet1”
2 95 “outlet2”
2 96 “interface1”
2 97 “interface2”
2 98 “walls”
3 90 “channels”
3 91 “chamber”
$EndPhysicalNames
$Nodes
1340
1 5 2.5 4.19999999925
2 4.5 3 4.19999999925
3 5 -2.5 4.19999999925
4 4.5 -3 4.19999999925
5 -4.5 -3 4.19999999925
6 -5 -2.5 4.2
7 -5 2.5 4.19999999925
8 -4.5 3 4.19999999925
9 -0.3 -7.347638122934e-17 4.2
10 4.49999999925 3 0.2

code:

import dolfinx
import meshio
from dolfinx import fem
from dolfinx.io import gmshio
from mpi4py import MPI
from dolfinx.io.gmshio import model_to_mesh
import ufl
from dolfinx.cpp.mesh import cell_entity_type
from dolfinx.cpp.mesh import to_type
from dolfinx.fem import (Constant,  Function, FunctionSpace, assemble_scalar, 
                         dirichletbc, form, locate_dofs_topological,
                         locate_dofs_geometrical)
from dolfinx.io import XDMFFile
from petsc4py import PETSc
from ufl import (FacetNormal, FiniteElement, Identity, TestFunction, TrialFunction, VectorElement,
                 div, dot, ds, dx, inner, lhs, grad, rhs, sym)

from dolfinx.fem.petsc import LinearProblem
from dolfinx import default_scalar_type
from dolfinx.mesh import CellType, create_rectangle, locate_entities_boundary


import numpy as np

proc = MPI.COMM_WORLD.rank


path = '/path/'
filename = 'mesh'


mesh, cell_markers, facet_markers = gmshio.read_from_msh(path + filename + '.msh', MPI.COMM_WORLD, gdim=3)

# parameters
mu = 0.001003e-6
rho = 1.0e-9
nu = mu/rho
flow_rate = 1.0e-8

# Define boundary conditions using facet markers
boundary_numbers = {'inlet1': 92, 'inlet2': 93, 'walls': 98, 'outlet1': 94, 'outlet2': 95}

dx = ufl.Measure("dx", domain=mesh, subdomain_data=cell_markers)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_markers)

inlet1_area = fem.assemble_scalar(fem.form(1 * ds(boundary_numbers['inlet1'])))
inl_velocity = flow_rate/(rho*inlet1_area)


tdim = mesh.topology.dim
fdim = tdim - 1


# Define function spaces for velocity and pressure
P2 = ufl.VectorElement('CG', mesh.ufl_cell(), 2)
P1 = ufl.FiniteElement('CG', mesh.ufl_cell(), 1)
element = ufl.MixedElement([P2, P1])
W = fem.FunctionSpace(mesh, element)

V, V_to_W = W.sub(0).collapse()
Q, Q_to_W = W.sub(1).collapse()


# Define the velocity part of the boundary condition
velocity_bc_value = fem.Constant(mesh, (0.0, 0.0, -1.0))
noslip_bc_value = fem.Constant(mesh, (0.0, 0.0, 0.0))
pressure_bc_value = fem.Constant(mesh, 0.0)  

# Locate degrees of freedom associated with the specified boundary
bcs = []
boundary_indices = fem.locate_dofs_topological(V, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['inlet1']])
bcs.append(fem.dirichletbc(velocity_bc_value, boundary_indices, V))
boundary_indices = fem.locate_dofs_topological(V, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['inlet2']])
bcs.append(fem.dirichletbc(velocity_bc_value, boundary_indices, V))
boundary_indices = fem.locate_dofs_topological(V, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['walls']])
bcs.append(fem.dirichletbc(noslip_bc_value, boundary_indices, V))

boundary_indices = fem.locate_dofs_topological(Q, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['outlet1']])
bcs.append(fem.dirichletbc(pressure_bc_value, boundary_indices, Q))
boundary_indices = fem.locate_dofs_topological(Q, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['outlet2']])
bcs.append(fem.dirichletbc(pressure_bc_value, boundary_indices, Q))

# Define variational problem
(u, p) = ufl.TrialFunctions(W)
(v, q) = ufl.TestFunctions(W)


f = Constant(mesh, PETSc.ScalarType((0, 0, 0)))


a = (
    + nu * inner(grad(u), grad(v))*dx
    - 1/rho * inner(p, div(v))*dx
    - q * div(u) * dx
)

L = inner(f, v) * dx



# Solve the variational problem
problem = LinearProblem(a, L, bcs)
u_p = problem.solve()

# Split the solution
u, p = u_p.split()

V_out = ufl.VectorElement("Lagrange", mesh.ufl_cell(), 1)
Q_out = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1)
W_out = FunctionSpace(mesh, V_out * Q_out)

u_p_out = Function(W_out)
u_out, p_out = u_p_out.split()
u_out.interpolate(u)
p_out.interpolate(p)

with XDMFFile(MPI.COMM_WORLD, path + filename + "_velocity1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(u_out)

with XDMFFile(MPI.COMM_WORLD, path + filename + "_pressure1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(p_out)

I suspect that the issue might be related to how I’m defining or applying these boundary conditions, or possibly something nuanced with the mesh itself that I’m overlooking. I’d deeply appreciate any insights or advice on how to ensure that these boundary conditions are correctly applied to achieve accurate simulation results.

Thank you once again for your help!

A part of the msh file is not sufficient. Please add the geo file or a link to the full msh file, as there is no way of running your code atm.

I have uploaded the complete .msh file to Dropbox. Here is the link to download the file:

Furthermore, based on the .msh file, I’ve made some adjustments to my code to better address the problem. Here’s the updated version of my code:

import dolfinx
import meshio
from dolfinx import fem
from dolfinx.io import gmshio
from mpi4py import MPI
from dolfinx.io.gmshio import model_to_mesh
import ufl
from dolfinx.cpp.mesh import cell_entity_type
from dolfinx.cpp.mesh import to_type
from dolfinx.fem import (Constant,  Function, FunctionSpace, assemble_scalar, 
                         dirichletbc, form, locate_dofs_topological,
                         locate_dofs_geometrical)
from dolfinx.io import XDMFFile
from petsc4py import PETSc
from ufl import (FacetNormal, FiniteElement, Identity, TestFunction, TrialFunction, VectorElement,
                 div, dot, ds, dx, inner, lhs, grad, rhs, sym)

from dolfinx.fem.petsc import LinearProblem
from dolfinx import default_scalar_type
from dolfinx.mesh import CellType, create_rectangle, locate_entities_boundary


import numpy as np

proc = MPI.COMM_WORLD.rank


path = '/path/'
filename = 'test'


mesh, cell_markers, facet_markers = gmshio.read_from_msh(path + filename + '.msh', MPI.COMM_WORLD, gdim=3)

# parameters
mu = 0.001003e-6 # [MPa.s]
rho = 1.0e-9 # [tonne/mm^3]
nu = mu/rho
flow_rate = 1.0e-8 # tonne/mm³ 

boundary_numbers = {'inlet': 27, 'outlet': 28, 'walls': 29}


print(boundary_numbers)

dx = ufl.Measure("dx", domain=mesh, subdomain_data=cell_markers)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_markers)

inlet1_area = fem.assemble_scalar(fem.form(1 * ds(boundary_numbers['inlet'])))
inl_velocity = flow_rate/(rho*inlet1_area)

tdim = mesh.topology.dim
fdim = tdim - 1

# Define function spaces for velocity and pressure
P2 = ufl.VectorElement('CG', mesh.ufl_cell(), 2)
P1 = ufl.FiniteElement('CG', mesh.ufl_cell(), 1)
element = ufl.MixedElement([P2, P1])
W = fem.FunctionSpace(mesh, element)

V, V_to_W = W.sub(0).collapse()
Q, Q_to_W = W.sub(1).collapse()


# Define the velocity part of the boundary condition
velocity_bc_value = fem.Constant(mesh, (0.0, 0.0, -1.0))  # Define the constant velocity value
noslip_bc_value = fem.Constant(mesh, (0.0, 0.0, 0.0))  # Define the constant velocity value
pressure_bc_value = fem.Constant(mesh, 0.0)  # Define the constant velocity value

# Locate degrees of freedom associated with the specified boundary
bcs = []  # List to hold boundary conditions
boundary_indices = fem.locate_dofs_topological(V, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['inlet']])
bcs.append(fem.dirichletbc(velocity_bc_value, boundary_indices, V))
boundary_indices = fem.locate_dofs_topological(V, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['walls']])
bcs.append(fem.dirichletbc(noslip_bc_value, boundary_indices, V))

boundary_indices = fem.locate_dofs_topological(Q, fdim, facet_markers.indices[facet_markers.values == boundary_numbers['outlet']])
bcs.append(fem.dirichletbc(pressure_bc_value, boundary_indices, Q))

# Define variational problem
(u, p) = ufl.TrialFunctions(W)
(v, q) = ufl.TestFunctions(W)

f = Constant(mesh, PETSc.ScalarType((0, 0, 0)))


a = (
    + nu * inner(grad(u), grad(v))*dx
    - 1/rho * inner(p, div(v))*dx
    - q * div(u) * dx
)

L = inner(f, v) * dx

# Solve the variational problem
problem = LinearProblem(a, L, bcs)
u_p = problem.solve()

# Split the solution
u, p = u_p.split()

V_out = ufl.VectorElement("Lagrange", mesh.ufl_cell(), 1)
Q_out = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1)
W_out = FunctionSpace(mesh, V_out * Q_out)

u_p_out = Function(W_out)
u_out, p_out = u_p_out.split()
u_out.interpolate(u)
p_out.interpolate(p)

with XDMFFile(MPI.COMM_WORLD, path + filename + "_velocity1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(u_out)

with XDMFFile(MPI.COMM_WORLD, path + filename + "_pressure1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(p_out)

Thank you once again for your support.

These are wrong, see: Mixed formulation of stokes equations - #6 by dokken

Thank you very much for pointing out the issue with my approach to defining boundary conditions.
However, my situation involves working with a mesh imported from a .msh file, where I’m utilizing facet_markers to identify different parts of the boundary based on tags defined in Gmsh. This differs from the approach mentioned in the linked thread, which utilizes dolfinx.mesh.locate_entities_boundary to identify boundary entities based on geometric criteria.
I’m currently unsure how to adapt the method recommended in the thread for my case. Would there be a recommended way to apply the same principle but using the tags I have from my .msh file to ensure my boundary conditions are correctly applied?

The whole point is that you are putting the wrong function space in DirichletBC. You should not use V and Q, but W.sub(0) and W.sub(1), just as in the aforementioned issue

Thank you once again for your guidance and patience. Based on your latest advice, I revisited the way I was applying the boundary conditions to ensure I was using the correct function spaces. As suggested, I attempted to use W.sub(0) and W.sub(1) directly in the DirichletBC instead of using V and Q . Here’s how I defined the boundary conditions after your recommendation:

bcs = []
boundary_indices_inlet = fem.locate_dofs_topological((W.sub(0),V), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['inlet']])
bcs.append(fem.dirichletbc(velocity_bc_func, boundary_indices_inlet, W.sub(0)))

boundary_indices_walls = fem.locate_dofs_topological((W.sub(0),V), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['walls']])
bcs.append(fem.dirichletbc(noslip_bc_func, boundary_indices_walls, W.sub(0)))

boundary_indices_outlet = fem.locate_dofs_topological((W.sub(1),Q), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['outlet']])
bcs.append(fem.dirichletbc(pressure_bc_func, boundary_indices_outlet, W.sub(1)))

I am still encountering the same issue with unexpected results from my simulation. Could there be another aspect of my implementation that I’m missing? Any further insights or suggestions would be greatly appreciated as I aim to resolve this persistent problem.

Many things are not defined in your code.
Here is an attempt from my side to help you:

import dolfinx
from dolfinx import fem
from dolfinx.io import gmshio
from mpi4py import MPI
import ufl
from dolfinx.fem import (Constant,  Function, FunctionSpace)
from dolfinx.io import XDMFFile
from petsc4py import PETSc
from ufl import (div, ds, dx, inner, grad)

from dolfinx.fem.petsc import LinearProblem
from dolfinx import default_scalar_type


import numpy as np

proc = MPI.COMM_WORLD.rank


filename = 'test'


mesh, cell_markers, facet_markers = gmshio.read_from_msh(
    filename + '.msh', MPI.COMM_WORLD, gdim=3)

# parameters
mu = 0.001003e-6  # [MPa.s]
rho = 1.0e-9  # [tonne/mm^3]
nu = mu/rho
flow_rate = 1.0e-8  # tonne/mm³

boundary_numbers = {'inlet': 27, 'outlet': 28, 'walls': 29}


print(boundary_numbers)

dx = ufl.Measure("dx", domain=mesh, subdomain_data=cell_markers)
ds = ufl.Measure("ds", domain=mesh, subdomain_data=facet_markers)

inlet1_area = fem.assemble_scalar(fem.form(1 * ds(boundary_numbers['inlet'])))
inl_velocity = flow_rate/(rho*inlet1_area)

tdim = mesh.topology.dim
fdim = tdim - 1

# Define function spaces for velocity and pressure
P2 = ufl.VectorElement('CG', mesh.ufl_cell(), 2)
P1 = ufl.FiniteElement('CG', mesh.ufl_cell(), 1)
element = ufl.MixedElement([P2, P1])
W = fem.FunctionSpace(mesh, element)

V, V_to_W = W.sub(0).collapse()
Q, Q_to_W = W.sub(1).collapse()


# Define the velocity part of the boundary condition

# Define the constant velocity value
def velocity(x):
    values = np.zeros((3, x.shape[1]), dtype=default_scalar_type)
    values[2] = -1*inl_velocity
    return values


velocity_bc_func = dolfinx.fem.Function(V)
velocity_bc_func.interpolate(velocity)

noslip_bc_func = dolfinx.fem.Function(V)
noslip_bc_func.x.array[:] = 0

pressure_bc_func = dolfinx.fem.Function(Q)
pressure_bc_func.x.array[:] = 0
# Locate degrees of freedom associated with the specified boundary
bcs = []  # List to hold boundary conditions

boundary_indices_inlet = fem.locate_dofs_topological((W.sub(
    0), V), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['inlet']])
bcs.append(fem.dirichletbc(velocity_bc_func, boundary_indices_inlet, W.sub(0)))

boundary_indices_walls = fem.locate_dofs_topological((W.sub(
    0), V), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['walls']])
bcs.append(fem.dirichletbc(noslip_bc_func, boundary_indices_walls, W.sub(0)))

boundary_indices_outlet = fem.locate_dofs_topological((W.sub(
    1), Q), fdim, facet_markers.indices[facet_markers.values == boundary_numbers['outlet']])
bcs.append(fem.dirichletbc(pressure_bc_func,
           boundary_indices_outlet, W.sub(1)))

# Define variational problem
(u, p) = ufl.TrialFunctions(W)
(v, q) = ufl.TestFunctions(W)

f = Constant(mesh, PETSc.ScalarType((0, 0, 0)))


a = (
    + nu * inner(grad(u), grad(v))*dx
    - inner(p, div(v))*dx
    - q * div(u) * dx
)

L = inner(f, v) * dx


# Solve the variational problem
problem = LinearProblem(a, L, bcs, petsc_options={
                        "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"})
u_p = problem.solve()
print(problem.solver.getConvergedReason())

# Split the solution
u, p = u_p.split()

V_out = ufl.VectorElement("Lagrange", mesh.ufl_cell(), 1)
Q_out = ufl.FiniteElement("Lagrange", mesh.ufl_cell(), 1)
W_out = FunctionSpace(mesh, V_out * Q_out)

u_p_out = Function(W_out)
u_out, p_out = u_p_out.split()
u_out.interpolate(u)
p_out.interpolate(p)

with XDMFFile(MPI.COMM_WORLD, filename + "_velocity1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(u_out)

with XDMFFile(MPI.COMM_WORLD, filename + "_pressure1.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_function(p_out)

Note that you have solver issues (i.e. the solver returns -11). However, if I change the petsc options to

# Solve the variational problem
problem = LinearProblem(a, L, bcs, petsc_options={
                        "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"})

the code converges, but the results are poor. I note that you only have two elements of width in the channel, which might cause issues. Note that I also change the sign in your variational form.

I also would interpolate into P-1 to visualize. I would use the VTXWriter, i.e.

with dolfinx.io.VTXWriter(u_p.function_space.mesh.comm, "test_vel.bp", [u_p.sub(0).collapse()], engine="BP4") as bp:
    bp.write(0.0)

as it gives you


Please put some effort into adjust your example with these suggestions, and ensure that you make the code as compact as possible. I would for instance start with a single channel, and work my way from there, as they only think it the code that will be changing is the input geometry.

1 Like

I’m really grateful for your help and expertise. Now, the boundary conditions are set up correctly. It turned out the way I was saving the results led to the discrepancies I noticed. By using dolfinx.io.VTKFile for saving, I’m now getting the results I expected. Thanks again.