Exploding velocities with simple boundary conditions - Navier-Stokes

Dear community,

I tried to adapt this Navier-Stokes example to a similar scenario with an L-shaped domain. The velocities are very turbulent and many velocities have an extreme value compared to their neighbours (see Fig. 1). Later on in the simulation all velocities are NaN (see Fig. 2). The boundary conditions are trivial, which means it can’t be the Gibbs phenomenon mentioned in this thread.

I don’t know what causes that bug and thus clueless how to fix it.
Many thanks in advance for your response!

Code to reproduce:

import pyvista
import os
import numpy as np
from petsc4py import PETSc
from dolfinx.fem import (Constant, Function, FunctionSpace, dirichletbc, form, locate_dofs_topological)
from dolfinx.fem.petsc import (apply_lifting, assemble_matrix, assemble_vector, create_vector, create_matrix, set_bc)
from dolfinx.mesh import locate_entities_boundary
from dolfinx.plot import create_vtk_mesh
from ufl import (FiniteElement, TestFunction, TrialFunction, VectorElement, div, dot, dx, inner, lhs, grad, nabla_grad, rhs)
from dolfinx import io
from mpi4py import MPI
import pygmsh
import meshio

with pygmsh.geo.Geometry() as geom:
    geom.add_polygon(
        [
            [0.0, 0.0],
            [0.0, 1.0],
            [0.5, 1.0],
            [0.5, 0.5],
            [1.0, 0.5],
            [1.0, 0.0],
        ],
        mesh_size=0.1,
    )
    mesh = geom.generate_mesh()

cells = [("triangle", mesh.cells_dict['triangle'])]
mesh = meshio.Mesh(mesh.points[:, :2], cells)
meshio.write("lshape.xdmf", mesh)

with io.XDMFFile(MPI.COMM_WORLD, "lshape.xdmf", 'r') as infile:
    mesh = infile.read_mesh(name="Grid")

m_grid = pyvista.UnstructuredGrid(*create_vtk_mesh(mesh))
plotter = pyvista.Plotter()
plotter.add_mesh(m_grid, show_edges=True, opacity=0.5)
plotter.show_bounds(
    grid='front',
    location='outer',
    all_edges=True)
plotter.view_xy()
plotter.show()

# Problem parameters
t = 0
T = 3  # Final time
dt = 0.1  # Time step size
num_steps = int(T / dt)
k = Constant(mesh, PETSc.ScalarType(dt))
mu = Constant(mesh, PETSc.ScalarType(0.001))  # Dynamic viscosity
rho = Constant(mesh, PETSc.ScalarType(1))  # Density

v_cg2 = VectorElement("CG", mesh.ufl_cell(), 2)
s_cg1 = FiniteElement("CG", mesh.ufl_cell(), 1)
V = FunctionSpace(mesh, v_cg2)
Q = FunctionSpace(mesh, s_cg1)
fdim = mesh.topology.dim - 1
gdim = 2


# Define boundary conditions
class InletPressure():
    def __init__(self, t):
        self.t = t

    def __call__(self, x):
        values = np.zeros((1, x.shape[1]), dtype=PETSc.ScalarType)
        values[0] = np.sin(3.0 * self.t)
        return values


DOLFIN_EPS = 0.000001


def inflow(x):
    return x[1] > 1 - DOLFIN_EPS


def outflow(x):
    return x[0] > 1 - DOLFIN_EPS


def boundary(x):
    return np.logical_or(x[0] < DOLFIN_EPS,
                         x[1] < DOLFIN_EPS,
                         np.logical_and(x[0] > 0.5 - DOLFIN_EPS, x[1] > 0.5 - DOLFIN_EPS))


# Inlet
p_inlet = Function(Q)
inlet_pressure = InletPressure(t)
p_inlet.interpolate(inlet_pressure)

inflow_facets = locate_entities_boundary(mesh, fdim, inflow)
bcu_inflow = dirichletbc(p_inlet, locate_dofs_topological(Q, fdim, inflow_facets))
# Walls
u_nonslip = np.array((0,) * mesh.geometry.dim, dtype=PETSc.ScalarType)
bndry_facets = locate_entities_boundary(mesh, fdim, boundary)
bcu_walls = dirichletbc(u_nonslip, locate_dofs_topological(V, fdim, bndry_facets), V)
bcu = [bcu_inflow, bcu_walls]
# Outlet
outflow_facets = locate_entities_boundary(mesh, fdim, outflow)
bcp_outlet = dirichletbc(PETSc.ScalarType(0), locate_dofs_topological(Q, fdim, outflow_facets), Q)
bcp = [bcp_outlet]

# Define variables
u = TrialFunction(V)
v = TestFunction(V)
u_ = Function(V)
u_.name = "u"
u_s = Function(V)
u_n = Function(V)
u_n1 = Function(V)
p = TrialFunction(Q)
q = TestFunction(Q)
p_ = Function(Q)
p_.name = "p"
phi = Function(Q)

f = Constant(mesh, PETSc.ScalarType((0, 0)))
F1 = rho / k * dot(u - u_n, v) * dx
F1 += inner(dot(1.5 * u_n - 0.5 * u_n1, 0.5 * nabla_grad(u + u_n)), v) * dx
F1 += 0.5 * mu * inner(grad(u + u_n), grad(v)) * dx - dot(p_, div(v)) * dx
F1 += dot(f, v) * dx
a1 = form(lhs(F1))
L1 = form(rhs(F1))
A1 = create_matrix(a1)
b1 = create_vector(L1)

a2 = form(dot(grad(p), grad(q)) * dx)
L2 = form(-1 / k * dot(div(u_s), q) * dx)
A2 = assemble_matrix(a2, bcs=bcp)
A2.assemble()
b2 = create_vector(L2)

a3 = form(dot(u, v) * dx)
L3 = form(dot(u_s, v) * dx - k * dot(nabla_grad(phi), v) * dx)
A3 = assemble_matrix(a3)
A3.assemble()
b3 = create_vector(L3)

# Solver for step 1
solver1 = PETSc.KSP().create(mesh.comm)
solver1.setOperators(A1)
solver1.setType(PETSc.KSP.Type.BCGS)
pc1 = solver1.getPC()
pc1.setType(PETSc.PC.Type.JACOBI)

# Solver for step 2
solver2 = PETSc.KSP().create(mesh.comm)
solver2.setOperators(A2)
solver2.setType(PETSc.KSP.Type.MINRES)
pc2 = solver2.getPC()
pc2.setType(PETSc.PC.Type.HYPRE)
pc2.setHYPREType("boomeramg")

# Solver for step 3
solver3 = PETSc.KSP().create(mesh.comm)
solver3.setOperators(A3)
solver3.setType(PETSc.KSP.Type.CG)
pc3 = solver3.getPC()
pc3.setType(PETSc.PC.Type.SOR)

a3 = form(dot(u, v) * dx)
L3 = form(dot(u_s, v) * dx - k * dot(nabla_grad(phi), v) * dx)
A3 = assemble_matrix(a3)
A3.assemble()
b3 = create_vector(L3)

u_out = './u-shapeL'
p_out = './p-shapeL'
if not os.path.exists(u_out):
    os.makedirs(u_out)
if not os.path.exists(p_out):
    os.makedirs(p_out)
topology, cell_types, geometry = create_vtk_mesh(V)
u_grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
p_grid = pyvista.UnstructuredGrid(*create_vtk_mesh(Q))

for i in range(num_steps):
    # Update current time step
    t += dt
    # Update inlet velocity
    inlet_pressure.t = t
    p_inlet.interpolate(inlet_pressure)

    # Step 1: Tentative velocity step
    A1.zeroEntries()
    assemble_matrix(A1, a1, bcs=bcu)
    A1.assemble()
    with b1.localForm() as loc:
        loc.set(0)
    assemble_vector(b1, L1)
    apply_lifting(b1, [a1], [bcu])
    b1.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b1, bcu)
    solver1.solve(b1, u_s.vector)
    u_s.x.scatter_forward()

    # Step 2: Pressure corrrection step
    with b2.localForm() as loc:
        loc.set(0)
    assemble_vector(b2, L2)
    apply_lifting(b2, [a2], [bcp])
    b2.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b2, bcp)
    solver2.solve(b2, phi.vector)
    phi.x.scatter_forward()

    p_.vector.axpy(1, phi.vector)
    p_.x.scatter_forward()

    # Step 3: Velocity correction step
    with b3.localForm() as loc:
        loc.set(0)
    assemble_vector(b3, L3)
    b3.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    solver3.solve(b3, u_.vector)
    u_.x.scatter_forward()

    # Create plotter
    u_plotter = pyvista.Plotter(off_screen=True)
    p_plotter = pyvista.Plotter(off_screen=True)
    p_plotter.add_title("Frame {}".format(i))
    p_plotter.add_mesh(p_grid, style="wireframe", color="k")
    p_grid["p"] = p_.x.array.real
    p_plotter.add_mesh(p_grid, cmap='viridis')
    p_plotter.show_bounds(
        grid='front',
        location='outer',
        all_edges=True)

    values = np.zeros((geometry.shape[0], 3), dtype=np.float64)
    values[:, :len(u_)] = u_.x.array.real.reshape((geometry.shape[0], len(u_)))
    u_grid["u"] = values
    glyphs = u_grid.glyph(orient="u", scale=False, factor=0.1)

    u_plotter.add_title("Frame {}".format(i))
    u_plotter.add_mesh(glyphs, cmap='jet')
    u_plotter.show_bounds(
        grid='front',
        location='outer',
        all_edges=True)
    u_plotter.view_xy()
    u_plotter.screenshot("{}/{}.png".format(u_out, i))
    p_plotter.view_xy()
    p_plotter.screenshot("{}/{}.png".format(p_out, i))

    # Update variable with solution form this time step
    with u_.vector.localForm() as loc_, u_n.vector.localForm() as loc_n, u_n1.vector.localForm() as loc_n1:
        loc_n.copy(loc_n1)
        loc_.copy(loc_n)

Figure 1:


Figure 2:

I do not have time to read all the lines, but do you get the same error even if you use dt=0.01 as in the original turotial?

1 Like

Supplying the xdmf without the h5 file doesn’t make your example reproducible. I would suggest writing the xdmf in ascii format, so that you can supply it here.

@dokken I have adapted the code (in the post above) to make it reproducible.

You have set your boundary conditions wrong:

should be

bcu = [bcu_walls]
bcp = [bcu_inflow, bcp_outlet]

You are also not tagging the correct facets, see:

facets = np.hstack([inflow_facets, bndry_facets, outflow_facets])
values = np.hstack([np.full_like(inflow_facets, 1, dtype=np.int32),
                    np.full_like(bndry_facets, 2, dtype=np.int32),
                    np.full_like(outflow_facets, 3, dtype=np.int32)])
sort = np.argsort(facets)
ct = meshtags(mesh, mesh.topology.dim-1, facets[sort], values[sort])
with io.XDMFFile(mesh.comm, "ct.xdmf", "w") as xdmf:
    xdmf.write_mesh(mesh)
    xdmf.write_meshtags(ct)

@dokken Thanks for the quick response. I adapted the boundary conditions (bcu & bcp) as well as the functions for locating the different boundaries (see below code and figure). I also tried @Kei_Yamamoto 's suggestion. Overall, the problem still persists which means:
-high velocities compared to neighbours
-system does not converge to an equilibrium
-NaN velocities later on in the simulation

def inflow(x):
    return np.isclose(x[1], 1)

def outflow(x):
    return np.isclose(x[0], 1)

def boundary(x):
    return np.logical_not(np.logical_or(inflow(x), outflow(x)))

Please present the complete code at its current stage.

@dokken Sure!

import pyvista
import os
import numpy as np
from petsc4py import PETSc
from dolfinx.fem import (Constant, Function, FunctionSpace, dirichletbc, form, locate_dofs_topological)
from dolfinx.fem.petsc import (apply_lifting, assemble_matrix, assemble_vector, create_vector, create_matrix, set_bc)
from dolfinx.mesh import locate_entities_boundary
from dolfinx.cpp.mesh import entities_to_geometry
from dolfinx.plot import create_vtk_mesh
from ufl import (FiniteElement, TestFunction, TrialFunction, VectorElement, div, dot, dx, inner, lhs, grad, nabla_grad,
                 rhs)
from dolfinx import io
from mpi4py import MPI
import pygmsh
import meshio

with pygmsh.geo.Geometry() as geom:
    geom.add_polygon(
        [
            [0.0, 0.0],
            [0.0, 1.0],
            [0.5, 1.0],
            [0.5, 0.5],
            [1.0, 0.5],
            [1.0, 0.0],
        ],
        mesh_size=0.1,
    )
    mesh = geom.generate_mesh()

cells = [("triangle", mesh.cells_dict['triangle'])]
mesh = meshio.Mesh(mesh.points[:, :2], cells)
meshio.write("lshape.xdmf", mesh)

with io.XDMFFile(MPI.COMM_WORLD, "lshape.xdmf", 'r') as infile:
    mesh = infile.read_mesh(name="Grid")

m_grid = pyvista.UnstructuredGrid(*create_vtk_mesh(mesh))
plotter = pyvista.Plotter()
plotter.add_mesh(m_grid, show_edges=True, opacity=0.5)
plotter.show_bounds(
    grid='front',
    location='outer',
    all_edges=True)
plotter.view_xy()
plotter.show()

# Problem parameters
t = 0
T = 3  # Final time
dt = 0.01  # Time step size
num_steps = int(T / dt)
k = Constant(mesh, PETSc.ScalarType(dt))
mu = Constant(mesh, PETSc.ScalarType(0.001))  # Dynamic viscosity
rho = Constant(mesh, PETSc.ScalarType(1))  # Density

v_cg2 = VectorElement("CG", mesh.ufl_cell(), 2)
s_cg1 = FiniteElement("CG", mesh.ufl_cell(), 1)
V = FunctionSpace(mesh, v_cg2)
Q = FunctionSpace(mesh, s_cg1)
fdim = mesh.topology.dim - 1
gdim = 2


# Define boundary conditions
class InletPressure():
    def __init__(self, t):
        self.t = t

    def __call__(self, x):
        values = np.zeros((1, x.shape[1]), dtype=PETSc.ScalarType)
        values[0] = np.sin(3.0 * self.t)
        return values


def inflow(x):
    return np.isclose(x[1], 1)


def outflow(x):
    return np.isclose(x[0], 1)


def boundary(x):
    return np.logical_not(np.logical_or(inflow(x), outflow(x)))


# Inlet
p_inlet = Function(Q)
inlet_pressure = InletPressure(t)
p_inlet.interpolate(inlet_pressure)

inflow_facets = locate_entities_boundary(mesh, fdim, inflow)
bcu_inflow = dirichletbc(p_inlet, locate_dofs_topological(Q, fdim, inflow_facets))
# Walls
u_nonslip = np.array((0,) * mesh.geometry.dim, dtype=PETSc.ScalarType)
bndry_facets = locate_entities_boundary(mesh, fdim, boundary)
bcu_walls = dirichletbc(u_nonslip, locate_dofs_topological(V, fdim, bndry_facets), V)
bcu = [bcu_walls]
# Outlet
outflow_facets = locate_entities_boundary(mesh, fdim, outflow)
bcp_outlet = dirichletbc(PETSc.ScalarType(0), locate_dofs_topological(Q, fdim, outflow_facets), Q)
bcp = [bcu_inflow, bcp_outlet]

# Define variables
u = TrialFunction(V)
v = TestFunction(V)
u_ = Function(V)
u_.name = "u"
u_s = Function(V)
u_n = Function(V)
u_n1 = Function(V)
p = TrialFunction(Q)
q = TestFunction(Q)
p_ = Function(Q)
p_.name = "p"
phi = Function(Q)

f = Constant(mesh, PETSc.ScalarType((0, 0)))
F1 = rho / k * dot(u - u_n, v) * dx
F1 += inner(dot(1.5 * u_n - 0.5 * u_n1, 0.5 * nabla_grad(u + u_n)), v) * dx
F1 += 0.5 * mu * inner(grad(u + u_n), grad(v)) * dx - dot(p_, div(v)) * dx
F1 += dot(f, v) * dx
a1 = form(lhs(F1))
L1 = form(rhs(F1))
A1 = create_matrix(a1)
b1 = create_vector(L1)

a2 = form(dot(grad(p), grad(q)) * dx)
L2 = form(-1 / k * dot(div(u_s), q) * dx)
A2 = assemble_matrix(a2, bcs=bcp)
A2.assemble()
b2 = create_vector(L2)

a3 = form(dot(u, v) * dx)
L3 = form(dot(u_s, v) * dx - k * dot(nabla_grad(phi), v) * dx)
A3 = assemble_matrix(a3)
A3.assemble()
b3 = create_vector(L3)

# Solver for step 1
solver1 = PETSc.KSP().create(mesh.comm)
solver1.setOperators(A1)
solver1.setType(PETSc.KSP.Type.BCGS)
pc1 = solver1.getPC()
pc1.setType(PETSc.PC.Type.JACOBI)

# Solver for step 2
solver2 = PETSc.KSP().create(mesh.comm)
solver2.setOperators(A2)
solver2.setType(PETSc.KSP.Type.MINRES)
pc2 = solver2.getPC()
pc2.setType(PETSc.PC.Type.HYPRE)
pc2.setHYPREType("boomeramg")

# Solver for step 3
solver3 = PETSc.KSP().create(mesh.comm)
solver3.setOperators(A3)
solver3.setType(PETSc.KSP.Type.CG)
pc3 = solver3.getPC()
pc3.setType(PETSc.PC.Type.SOR)

a3 = form(dot(u, v) * dx)
L3 = form(dot(u_s, v) * dx - k * dot(nabla_grad(phi), v) * dx)
A3 = assemble_matrix(a3)
A3.assemble()
b3 = create_vector(L3)

u_out = './u-shapeL'
p_out = './p-shapeL'
if not os.path.exists(u_out):
    os.makedirs(u_out)
if not os.path.exists(p_out):
    os.makedirs(p_out)
topology, cell_types, geometry = create_vtk_mesh(V)
u_grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
p_topology, p_cell_types, p_geometry = create_vtk_mesh(Q)
p_grid = pyvista.UnstructuredGrid(p_topology, p_cell_types, p_geometry)

num_facets_owned_by_proc = mesh.topology.index_map(fdim).size_local
geometry_entitites = entities_to_geometry(mesh, fdim, np.arange(num_facets_owned_by_proc, dtype=np.int32), False)
inflow_points = []
bndry_points = []
outflow_points = []
for e, entity in enumerate(geometry_entitites):
    if e in inflow_facets:
        inflow_points = np.append(inflow_points, entity)
    elif e in outflow_facets:
        outflow_points = np.append(outflow_points, entity)
    elif e in bndry_facets:
        bndry_points = np.append(bndry_points, entity)

# Create plotter
u_plotter = pyvista.Plotter(off_screen=True)
p_plotter = pyvista.Plotter(off_screen=True)

bc_plotter = pyvista.Plotter()
values = np.zeros(p_geometry.shape[0], dtype=np.float64)
values[inflow_points.astype(int)] = 100
values[bndry_points.astype(int)] = 50
values[outflow_points.astype(int)] = -100
p_grid["bc"] = values
bc_plotter.add_mesh(p_grid, scalars='bc', cmap='jet')
bc_plotter.show()

for i in range(num_steps):
    # Update current time step
    t += dt
    # Update inlet velocity
    inlet_pressure.t = t
    p_inlet.interpolate(inlet_pressure)

    # Step 1: Tentative velocity step
    A1.zeroEntries()
    assemble_matrix(A1, a1, bcs=bcu)
    A1.assemble()
    with b1.localForm() as loc:
        loc.set(0)
    assemble_vector(b1, L1)
    apply_lifting(b1, [a1], [bcu])
    b1.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b1, bcu)
    solver1.solve(b1, u_s.vector)
    u_s.x.scatter_forward()

    # Step 2: Pressure corrrection step
    with b2.localForm() as loc:
        loc.set(0)
    assemble_vector(b2, L2)
    apply_lifting(b2, [a2], [bcp])
    b2.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b2, bcp)
    solver2.solve(b2, phi.vector)
    phi.x.scatter_forward()

    p_.vector.axpy(1, phi.vector)
    p_.x.scatter_forward()

    # Step 3: Velocity correction step
    with b3.localForm() as loc:
        loc.set(0)
    assemble_vector(b3, L3)
    b3.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    solver3.solve(b3, u_.vector)
    u_.x.scatter_forward()

    p_plotter.add_title("Frame {}".format(i))
    p_plotter.add_mesh(p_grid, style="wireframe", color="k")
    p_grid["p"] = p_.x.array.real
    p_plotter.add_mesh(p_grid, scalars='p', cmap='viridis')
    p_plotter.show_bounds(
        grid='front',
        location='outer',
        all_edges=True)

    values = np.zeros((geometry.shape[0], 3), dtype=np.float64)
    values[:, :len(u_)] = u_.x.array.real.reshape((geometry.shape[0], len(u_)))
    u_grid["u"] = values
    glyphs = u_grid.glyph(orient="u", scale=False, factor=0.1)

    u_plotter.add_title("Frame {}".format(i))
    u_plotter.add_mesh(glyphs, cmap='jet')
    u_plotter.show_bounds(
        grid='front',
        location='outer',
        all_edges=True)
    u_plotter.view_xy()
    u_plotter.screenshot("{}/{}.png".format(u_out, i))
    p_plotter.view_xy()
    p_plotter.screenshot("{}/{}.png".format(p_out, i))
    p_plotter.clear()
    u_plotter.clear()

    # Update variable with solution form this time step
    with u_.vector.localForm() as loc_, u_n.vector.localForm() as loc_n, u_n1.vector.localForm() as loc_n1:
        loc_n.copy(loc_n1)
        loc_.copy(loc_n)