No-slip boundary condition not being applied

Hi all. I’m an undergrad student modeling Antarctic ice flow around ice rumples as 2D Stokes flow around a circular obstacle. I read a few of the previous posts and tutorials on this, but unfortunately I’m still having trouble with defining my no-slip boundary conditions around the obstacle.

My mesh looks like this:

I want to establish no-slip boundary conditions around the obstacles. I did this using the residual method found here. The resulting velocity field looks like this:

However, I want to make my material non-Newtonian, so I decided to go back and use the normal linear variational solver that is used in most of the FEniCS tutorials.

First, I create a mesh, define subdomains, and export the mesh and subdomains as xml files:

mymesh = generate_mesh(Rectangle(Point(0.0,0.0), Point(L,H)) - Circle(Point(60,0.25*H),25) - Circle(Point(130, 0.75*H), 25), resolution)
File("meshes/mesh.xml.gz") << mymesh
mesh = Mesh("meshes/mesh.xml.gz")

# Sub domain for no-slip (mark whole boundary, inflow and outflow will overwrite)
class Noslip(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary 

# Sub domain for inflow (right)
class Inflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] > 1.0 - DOLFIN_EPS and on_boundary

# Sub domain for outflow (left)
class Outflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] < DOLFIN_EPS and on_boundary

# Create mesh functions over the cell facets
sub_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)

# Mark all facets as sub domain 3
sub_domains.set_all(3)

# Mark no-slip facets as sub domain 0, 0.0
noslip = Noslip()
noslip.mark(sub_domains, 0)

# Mark inflow as sub domain 1, 01
inflow = Inflow()
inflow.mark(sub_domains, 1)

# Mark outflow as sub domain 2, 0.2, True
outflow = Outflow()
outflow.mark(sub_domains, 2)

# Save sub domains to file
file = File("meshes/subdomains.xml.gz")
file << sub_domains

Then, I import the xml mesh files, define my variational problem with Taylor-Hood elements, define my Dirichlet boundary conditions, and then solve the problem.

mesh = Mesh("meshes/mesh.xml.gz")
sub_domains = MeshFunction("size_t", mesh, "meshes/subdomains.xml.gz")

# Define Taylor-Hood function space
VE = VectorElement("CG", mesh.ufl_cell(), 2)
QE = FiniteElement("CG", mesh.ufl_cell(), 1)
WE = VE * QE

W = FunctionSpace(mesh, WE)
V = FunctionSpace(mesh, VE)
Q = FunctionSpace(mesh, QE)

noslip = Constant((0, 0))
bc0 = DirichletBC(W.sub(0), noslip, sub_domains, 0) # subdomain marked 0 (noslip)

# Inflow boundary condition for velocity
# x0 = 1
inflow = Expression(("4*(x[1]*(YMAX-x[1]))/(YMAX*YMAX)", "0."), YMAX=YMAX, element = V.ufl_element())
bc1 = DirichletBC(W.sub(0), inflow, sub_domains, 1) # subdomain marked 1 (inflow)

# Boundary condition for pressure at outflow
# x0 = 0
zero = Constant(0)
bc2 = DirichletBC(W.sub(1), zero, sub_domains, 2) # subdomain marked 2 (outflow)

# Collect boundary conditions
bcs = [bc0, bc1, bc2]
eta = 1
I = Identity(2)
def epsilon(u): # strain rate tensor
  return 0.5*(grad(u) + grad(u).T)

def sigma(u,p): # Cauchy stress tensor
  return 2*eta*epsilon(u) - p*I # from https://fenicsproject.org/pub/tutorial/html/._ftut1008.html 

# Define trial and test functions
u, p = TrialFunctions(W)
v, q = TestFunctions(W)
f = Constant((0, 0)) # set f, body force, to 0
a = inner(sigma(u,p), grad(v))*dx + q*div(u)*dx
L = inner(f, v)*dx

# Compute solution
w = Function(W)
solve(a == L, w, bcs)

The resulting velocity field looks like this:


As you can see, the velocity at the no-slip boundary on the obstacles is not 0, as it is in the previous velocity plot. My code runs without error, so I think it is able to find the appropriate facets to apply the boundary conditions to. Am I defining my no-slip boundary incorrectly? Any suggestions would be greatly appreciated!

First, note that there is no reason for saving the mesh as xml_gz, as it can directly used with dolfin.
Secondly, your issue is that you have been sloppy with your definition of boundaries.

This should be:

# Sub domain for inflow (right)
class Inflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] > L - 1e3*DOLFIN_EPS and on_boundary

# Sub domain for outflow (left)
class Outflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] < DOLFIN_EPS and on_boundary

Note that you can visualize the mesh function in paraview by saving it as pvd:

# Save sub domains to file
file = File("meshes/subdomains.pvd")
file << sub_domains

Thank you, now it works!!! I am very new to FEniCS and finite element modeling, so I had just been using the boundary definitions as found in the demos. Just so I can understand, why did you multiply 1e3*DOLFIN_EPS in the Inflow boundary?

1 Like

Just to make certain that round off error doesn’t remove the right boundary (especially as L is of order 1e2). If you remove it, you will see that the boundary is not included in the sub_domains.

I see. Yes, I did get that error when I was playing around with it before. Thanks for your help and have a great day!

Hello @dokken

I am trying to impose boundary conditions on the velocity field. It is a problem based on the navier stokes equations, and I have the doubt if I am imposing correctly the conditions. I am using gmsh to create the mesh and mark the boundaries, but I don’t know if I am doing it correctly in the code. Could you give me some guidance?

xml_file = "mesh_v.xml"
mesh = fe.Mesh(xml_file)
boundaries = fe.MeshFunction('size_t', mesh, "mesh_v_facet_region.xml")
markers = fe.MeshFunction('size_t', mesh, "mesh_v_physical_region.xml")
velocity_function_space = fe.VectorFunctionSpace(mesh, "Lagrange", 2)
pressure_function_space = fe.FunctionSpace(mesh, "Lagrange", 1)
u_0 = fe.Expression(('0.0', '0.0'), degree=2)
fd = fe.MeshFunction('size_t', mesh, "mesh_v_facet_region.xml")
# Define the Boundary Condition for velocity
in_exp = 0.16
u_ref = 0.25
z_ref = 2.5
u_inflow = fe.Expression(('u_ref*pow(x[1] / z_ref, in_exp)', '0.0'), degree=2, z_ref=z_ref, u_ref=u_ref,
                         in_exp=in_exp)
u_in_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    u_inflow,
    boundaries,
    17
)
u_bot_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    boundaries,
    18
)
u_top_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    boundaries,
    19
)
u_out_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    boundaries,
    20
)

p0 = 101325
p_inflow = fe.Expression('p0 - rho*g*x[1]', degree=1, p0=p0, rho=rho, g=g)
# Define the Boundary Condition for pressure

p_in_boundary_condition = fe.DirichletBC(
    pressure_function_space,
    p_inflow,
    boundaries,
    17
)

p_out_boundary_condition = fe.DirichletBC(
    pressure_function_space,
    fe.Constant(0.0),
    boundaries,
    20
)

velocity_boundary_conditions = [u_in_boundary_condition, u_bot_boundary_condition, u_out_boundary_condition]

pressure_boundary_conditions = [p_in_boundary_condition, p_out_boundary_condition]

This is my solution

Could you start by doing this on a built in rectangle mesh? Then it is reproducible for others.

Of course,

import fenics as fe
from tqdm import tqdm # optional
import matplotlib.pyplot as plt
from dolfin import *
import numpy as np

TIME_STEP_LENGTH = 0.01
SIMULATION_TIME = 10
N_TIME_STEPS = int(SIMULATION_TIME/TIME_STEP_LENGTH)
t = np.linspace(0, SIMULATION_TIME, N_TIME_STEPS)
L = 10
mesh = UnitSquareMesh(L, L)

class Noslip(SubDomain):
    def inside(self, x, on_boundary):

        return on_boundary
class Inflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] > L - 1e3*DOLFIN_EPS and on_boundary

class Outflow(SubDomain):
    def inside(self, x, on_boundary):
        return x[0] < DOLFIN_EPS and on_boundary

sub_domains = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)
sub_domains.set_all(3)
noslip = Noslip()
noslip.mark(sub_domains, 0)
inflow = Inflow()
inflow.mark(sub_domains, 1)
outflow = Outflow()
outflow.mark(sub_domains, 2)
file = File("meshes/subdomains.pvd")
file << sub_domains

velocity_function_space = fe.VectorFunctionSpace(mesh, "Lagrange", 2)
pressure_function_space = fe.FunctionSpace(mesh, "Lagrange", 1)
u_0 = fe.Expression(('0.0', '0.0'), degree=2)
fd = fe.MeshFunction('size_t', mesh, "mesh_v_facet_region.xml")
u_trial = fe.TrialFunction(velocity_function_space)
p_trial = fe.TrialFunction(pressure_function_space)
v_test = fe.TestFunction(velocity_function_space)
q_test = fe.TestFunction(pressure_function_space)

#Define the Initial Value for velocity
u_0 = fe.Expression(('0.0', '0.0'), degree=2)

#Define the Boundary Condition for velocity
in_exp = 0.16
u_ref = 0.25
z_ref = 2.5
mu = 1.87*10**(-5)
mu_t = 1.87*10**(-4)
h = 1.0
rho = 1.1614
phi = 0.6
K = h * phi ** 3 / 150 * (1 - phi) ** 2
C_F = 1.75 / fe.sqrt(150 * phi ** 3)
mu = 1.87*10**(-5)
mu_t = 1.87*10**(-4)
g = 9.81

u_inflow = fe.Expression(('u_ref*pow(x[1] / z_ref, in_exp)', '0.0'), degree=2, z_ref=z_ref, u_ref=u_ref,
                     in_exp=in_exp)
u_in_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    u_inflow,
    sub_domains,
    1
)
u_bot_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    sub_domains,
    0
)
u_top_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    sub_domains,
    0
)
u_out_boundary_condition = fe.DirichletBC(
    velocity_function_space,
    (0.0, 0.0),
    sub_domains,
    2
)

p0 = 101325
p_inflow = fe.Expression('p0 - rho*g*x[1]', degree=1, p0=p0, rho=rho, g=g)
p_in_boundary_condition = fe.DirichletBC(
    pressure_function_space,
    p_inflow,
    sub_domains,
    1
)
p_out_boundary_condition = fe.DirichletBC(
    pressure_function_space,
    fe.Constant(0.0),
    sub_domains,
    2
)

velocity_boundary_conditions = [u_in_boundary_condition, u_bot_boundary_condition, u_out_boundary_condition]
pressure_boundary_conditions = [p_in_boundary_condition, p_out_boundary_condition]

u_tent = fe.Function(velocity_function_space)
u_next = fe.Function(velocity_function_space)
p_next = fe.Function(pressure_function_space)
u_prev = fe.interpolate(u_0, velocity_function_space)

momentum_weak_form_residuum = (
    rho * 1.0 / TIME_STEP_LENGTH * fe.inner(u_trial - u_prev, v_test) * fe.dx
    +
    rho * fe.inner(fe.dot(u_prev, fe.grad(u_prev)), v_test) * fe.dx
    +
    (mu + mu_t) * fe.inner(fe.grad(u_trial), fe.grad(v_test)) * fe.dx
    +
    mu_t * fe.inner(fe.grad(u_trial).T, fe.grad(v_test)) * fe.dx
    -
    mu / K * fe.inner(phi * u_prev, v_test) * fe.dx
    +
    rho * C_F / fe.sqrt(K) * fe.inner(phi * fe.sqrt(fe.inner(u_prev, u_prev)) * u_prev, v_test) * fe.dx
    )

momentum_weak_form_lhs = fe.lhs(momentum_weak_form_residuum)
momentum_weak_form_rhs = fe.rhs(momentum_weak_form_residuum)

#Weak form of the pressure poisson problem
pressure_poisson_weak_form_lhs = fe.inner(fe.grad(p_trial), fe.grad(q_test)) * fe.dx
pressure_poisson_weak_form_rhs = - 1.0 / TIME_STEP_LENGTH * fe.div(u_tent) * q_test * fe.dx

#Weak form of the velocity update equation
velocity_update_weak_form_lhs = fe.inner(u_trial, v_test) * fe.dx
velocity_update_weak_form_rhs = (
    fe.inner(u_tent, v_test) * fe.dx
    -
    TIME_STEP_LENGTH * fe.inner(fe.grad(p_next), v_test) * fe.dx
    )

#Pre-Compute the system matrices (because they do not greatly change)
momentum_assembled_system_matrix = fe.assemble(momentum_weak_form_lhs)
pressure_poisson_assembled_system_matrix = fe.assemble(pressure_poisson_weak_form_lhs)
velocity_update_assembled_system_matrix = fe.assemble(velocity_update_weak_form_lhs)

ufile = fe.File("results/velocity.pvd")
ufile << (u_prev, 0)

for n in tqdm(range(N_TIME_STEPS)):

# (1) Solve for tentative velocity
momentum_assembled_rhs = fe.assemble(momentum_weak_form_rhs)
[bc.apply(momentum_assembled_system_matrix, momentum_assembled_rhs) for bc in velocity_boundary_conditions]
fe.solve(
    momentum_assembled_system_matrix,
    u_tent.vector(),
    momentum_assembled_rhs,
    "gmres",
    "ilu",
)

# (2) Solve for the pressure
pressure_poisson_assembled_rhs = fe.assemble(pressure_poisson_weak_form_rhs)
[bc.apply(pressure_poisson_assembled_system_matrix, pressure_poisson_assembled_rhs) for bc in
    pressure_boundary_conditions]
fe.solve(
    pressure_poisson_assembled_system_matrix,
    p_next.vector(),
    pressure_poisson_assembled_rhs,
    "gmres",
    "amg",
)

# (3) Correct the velocities to be incompressible
velocity_update_assembled_rhs = fe.assemble(velocity_update_weak_form_rhs)
[bc.apply(momentum_assembled_system_matrix, momentum_assembled_rhs) for bc in velocity_boundary_conditions]
fe.solve(
    velocity_update_assembled_system_matrix,
    u_next.vector(),
    velocity_update_assembled_rhs,
    "gmres",
    "ilu",
)

# Advance in time
u_prev.assign(u_next)
ufile << (u_next, t[n])

How can I view the boundary tags in Paraview after having created the subdomains?

Save the sub_domains variable to say a .pvd-file.

How can I know if I tagged correctly in the pvd file?

Use for instance the Threshold filter to view a subset of facets.

Hello @dokken

What is the difference between these two ways of imposing boundary conditions?

[bc.apply(momentum_assembled_system_matrix, momentum_assembled_rhs) for bc in 
velocity_boundary_conditions]
fe.solve(
     momentum_assembled_system_matrix,
     u_tent.vector(),
    momentum_assembled_rhs,
    "gmres",
    "ilu",
)

and

noslip = Constant((0, 0))
bc0 = DirichletBC(W.sub(0), noslip, sub_domains, 0)
bcs = [bc0, bc1, bc2]
solve(a == L, w, bcs)

This seems rather unrelated to the first question, and does not just have to do with boundary conditions.

The first API uses assembled matrices and vectors directly, it is simply an interface to a linear algebra backend.
The second API gets symbolical forms, and has to create matrices, vectors, analyze the forms, compile code or look up in cache, then apply boundary conditions, making it much more complex and time consuming

An apology and thanks for your help