Cavity (2D)

This notebook implements a steady incompressible Navier-Stokes solver for the cavity problem.

The problem is strong form reads: \begin{equation} \left\{ \begin{array}{ll} \nabla \cdot \mathbf{u} =0& in\;\Omega\\ \displaystyle \left(\mathbf{u}\cdot \nabla\right)\mathbf{u}= \nu\Delta \mathbf{u}-\nabla p & in\;\Omega\\ & \\ \mathbf{u} = \mathbf{i} & on\;\Gamma_{lid}\\ \mathbf{u} = \mathbf{0} & on\;\partial\Omega\setminus\Gamma_{lid} \end{array} \right. \end{equation}

[1]:
import numpy as np
import pandas as pd

# Mesh generation
import gmsh
import dolfinx
from dolfinx.io import gmshio
from mpi4py import MPI
from dolfinx.mesh import (CellType, GhostMode, create_rectangle, locate_entities_boundary)
from dolfinx.fem import Function, locate_dofs_geometrical, dirichletbc

import matplotlib.pyplot as plt
from matplotlib import cm

import sys

mesh_path      = '../../../mesh/'
benchmark_path = '../../../BenchmarkData/'

sys.path.append('../../../models/fenicsx')

Mesh generation from gmsh

[2]:
gdim = 2
model_rank = 0
mesh_comm = MPI.COMM_WORLD

mesh_factor = .01

# Initialize the gmsh module
gmsh.initialize()

# Load the .geo file
gmsh.merge(mesh_path+'cavity.geo')
gmsh.model.geo.synchronize()

gmsh.option.setNumber("Mesh.MeshSizeFactor", mesh_factor)

gmsh.model.mesh.generate(gdim)
gmsh.model.mesh.optimize("Netgen")

# Domain
domain, ct, ft = gmshio.model_to_mesh(gmsh.model, comm = mesh_comm, rank = model_rank, gdim = gdim )
gmsh.finalize()

bound_markers = {'lid':   10,
                 'walls': 20}

domain_marker = 100
Info    : Reading '../../../mesh/cavity.geo'...
Info    : Done reading '../../../mesh/cavity.geo'
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 1 (Line)
Info    : [ 30%] Meshing curve 2 (Line)
Info    : [ 50%] Meshing curve 3 (Line)
Info    : [ 80%] Meshing curve 4 (Line)
Info    : Done meshing 1D (Wall 0.00200965s, CPU 0.000675s)
Info    : Meshing 2D...
Info    : Meshing surface 1 (Transfinite)
Info    : Done meshing 2D (Wall 0.00728691s, CPU 0.003989s)
Info    : 10201 nodes 20404 elements
Info    : Optimizing mesh (Netgen)...
Info    : Done optimizing mesh (Wall 1.98001e-06s, CPU 2e-06s)

The problem we want to face is non-linear, whose weak formulation reads: \begin{equation} \int_\Omega \left(\mathbf{u}\cdot \nabla\right)\mathbf{u}\cdot \mathbf{v}\,d\Omega + \nu \int_\Omega\nabla \mathbf{u}\cdot \nabla \mathbf{v}\,d\Omega -\int_\Omega p(\nabla\cdot\mathbf{v})\,d\Omega -\int_\Omega q(\nabla\cdot\mathbf{u})\,d\Omega=0 \end{equation}

[5]:
from fluid_dynamics.steady_ns import ns_steady_nl

cavity = ns_steady_nl(domain, ct = ct, ft = ft, bound_markers=bound_markers)

# ReVec = np.array([100, 400, 1000, 2500, 5000, 7500])
ReVec = np.array([100, 400, 1000])

u_list = []
boundary_type = {'lid': 0,
                 'walls': 1}
boundary_value = {'lid': np.array([1, 0, 0])}

for ii in range(len(ReVec)):

    cavity.parameters(1/ReVec[ii])
    cavity.set_bc(boundary_type=boundary_type, boundary_value=boundary_value)

    dofs_p = locate_dofs_geometrical((cavity.W.sub(1), cavity.W.sub(1).collapse()[0]),
                                  lambda x: np.isclose(x.T, [0, 0, 0]).all(axis=1))
    cavity.bcs.append( dirichletbc(Function(cavity.W.sub(1).collapse()[0]),
                                    dofs_p, cavity.W.sub(1)) )
#     cavity.set_cavity_bc()
    cavity.assemble(maxIter=20, verbose = False)

    up_sol = cavity.solve()
    (u_sol, p_sol) = (up_sol.sub(0).collapse(), up_sol.sub(1).collapse())
    u_list.append(u_sol)

Below a function to extract data from a function is implemented

[6]:
def extract2D(problem, N, u_sol):
    grid = np.linspace(0, 1, N)
    ux = np.zeros((N,N))
    uy = np.zeros((N,N))

    for ii in range(N):
        points = np.zeros((3, N))
        points[0, :] = grid[ii]
        points[1, :] = grid

        bb_tree = dolfinx.geometry.BoundingBoxTree(problem.domain, problem.domain.topology.dim)
        cells = []
        points_on_proc = []
        cell_candidates = dolfinx.geometry.compute_collisions(bb_tree, points.T)
        colliding_cells = dolfinx.geometry.compute_colliding_cells(problem.domain, cell_candidates, points.T)
        for i, point in enumerate(points.T):
            if len(colliding_cells.links(i))>0:
                points_on_proc.append(point)
                cells.append(colliding_cells.links(i)[0])
        xPlot = np.array(points_on_proc, dtype=np.float64)

        ux[ii, :] = u_sol.sub(0).eval(xPlot, cells).flatten()
        uy[ii, :] = u_sol.sub(1).eval(xPlot, cells).flatten()
    return ux.T, uy.T, grid

Let’s make some 2D streamline plots

[7]:
nrows = 1
ncols = 3

fontsize  = 30
labelsize = 20

fig, axs = plt.subplots(nrows = nrows, ncols = ncols, sharex=True, sharey=True,
                        figsize = (6 * ncols, 6 * nrows))
axs = axs.flatten()

N = 100

for ii in range(nrows * ncols):
    ux, uy, grid = extract2D(cavity, N, u_list[ii])
    X, Y = np.meshgrid(grid, grid)
    color = np.sqrt(((uy+2)/2)*2 + ((ux+2)/2)*2)

    axs[ii].streamplot(X, Y, ux, uy, color=np.sqrt(ux**2+uy**2), linewidth=1, cmap='jet')
    axs[ii].set_title(r'$Re = {:.0f}'.format(ReVec[ii])+'$', fontsize = fontsize)
    axs[ii].set_xlim(0,1)
    axs[ii].set_ylim(0,1)
    axs[ii].set_xticks(np.arange(0.25, 1.01, 0.25))
    axs[ii].set_yticks(np.arange(0., 1.01, 0.25))
    axs[ii].tick_params(axis='both', labelsize=labelsize)

for ii in range(3, len(ReVec)):
    axs[ii].set_xlabel(r'$x$', fontsize = fontsize)

axs[0].set_ylabel(r'$y$', fontsize = fontsize)
# axs[3].set_ylabel(r'$y$', fontsize = fontsize)

fig.subplots_adjust(hspace=0.1, wspace = 0)
../../../_images/Tutorials_fenicsx_fluid_dynamics_01_Cavity2D_9_0.png

Comparison with benchmark data

Benchmark data are taken from https://www.acenumerics.com/the-benchmarks.html

[8]:
def extract1D_y(problem, N, u_sol, y_lines: list, component = 0):
    grid = np.linspace(0, 1, N)
    u_extracted = np.zeros((len(y_lines), N))

    for idx_y in range(len(y_lines)):
        points = np.zeros((3, N))
        points[0, :] = grid
        points[1, :] = y_lines[idx_y]

        bb_tree = dolfinx.geometry.BoundingBoxTree(problem.domain, problem.domain.topology.dim)
        cells = []
        points_on_proc = []
        cell_candidates = dolfinx.geometry.compute_collisions(bb_tree, points.T)
        colliding_cells = dolfinx.geometry.compute_colliding_cells(problem.domain, cell_candidates, points.T)
        for i, point in enumerate(points.T):
            if len(colliding_cells.links(i))>0:
                points_on_proc.append(point)
                cells.append(colliding_cells.links(i)[0])
        xPlot = np.array(points_on_proc, dtype=np.float64)

        if np.isclose(component, 0):
            u_extracted[idx_y, :] = u_sol.sub(0).eval(xPlot, cells).flatten()
        else:
            u_extracted[idx_y, :] = u_sol.sub(1).eval(xPlot, cells).flatten()

    return u_extracted, xPlot[:,0]

def extract1D_x(problem, N, u_sol, x_lines: list, component = 0):
    grid = np.linspace(0, 1, N)
    u_extracted = np.zeros((len(x_lines), N))

    for idx_x in range(len(x_lines)):
        points = np.zeros((3, N))
        points[0, :] = x_lines[idx_x]
        points[1, :] = grid

        bb_tree = dolfinx.geometry.BoundingBoxTree(problem.domain, problem.domain.topology.dim)
        cells = []
        points_on_proc = []
        cell_candidates = dolfinx.geometry.compute_collisions(bb_tree, points.T)
        colliding_cells = dolfinx.geometry.compute_colliding_cells(problem.domain, cell_candidates, points.T)
        for i, point in enumerate(points.T):
            if len(colliding_cells.links(i))>0:
                points_on_proc.append(point)
                cells.append(colliding_cells.links(i)[0])
        xPlot = np.array(points_on_proc, dtype=np.float64)

        if np.isclose(component, 0):
            u_extracted[idx_x, :] = u_sol.sub(0).eval(xPlot, cells).flatten()
        else:
            u_extracted[idx_x, :] = u_sol.sub(1).eval(xPlot, cells).flatten()

    return u_extracted, xPlot[:,1]

Vertical velocity across various horizontal planes

[11]:
idx_Re = 2
df = pd.read_excel(benchmark_path+'fluid_dynamics/cavity/re_'+str(ReVec[idx_Re])+'_driven_cavity_benchmark_results.xlsx',
                   sheet_name='Table 6')

y_lines = [0.05, 0.1, 0.5, 0.9, 0.99]

df = np.asarray(df.to_numpy())

x_bench = df[:,0].flatten()
uy_bench = df[:,1:]

del df

uy, x_plot = extract1D_y(cavity, N, u_list[idx_Re], y_lines, component = 1)

###############################################################################################

fontsize  = 30
labelsize = 20

nrows, ncols = 2, 3
fig, axs = plt.subplots(nrows = nrows, ncols = ncols, sharex=True,
                        figsize = (6 * ncols, 5 * nrows))
axs = axs.flatten()

for ii in range(len(y_lines)):
    axs[ii+1].plot(x_plot, uy[ii], '-', color=cm.jet(0.1), label='FEniCSx')
    axs[ii+1].plot(x_bench, uy_bench[:, ii], 'o', color=cm.jet(0.9), label='Benchmark')
    axs[ii+1].grid()
    axs[ii+1].set_xlim(0,1)
    axs[ii+1].set_ylabel(r'$u_y$ at $y={:.2f}'.format(y_lines[ii])+'$', fontsize=fontsize)
    axs[ii+1].tick_params(axis='both', labelsize = labelsize)
    axs[ii+1].set_xticks(np.arange(0, 1.01, 0.25))

axs[0].axis('off')
axs[3].set_xlabel(r'$x$', fontsize=fontsize)
axs[4].set_xlabel(r'$x$', fontsize=fontsize)
axs[5].set_xlabel(r'$x$', fontsize=fontsize)

fig.text(0.16, 0.8, s=r'$Re='+str(ReVec[idx_Re])+'$', fontsize=fontsize)

Line, Label = axs[1].get_legend_handles_labels()
fig.legend(Line, Label, framealpha = 1, fontsize=labelsize, loc=(0.1, 0.75), ncols=1)

fig.subplots_adjust(hspace=0.075, wspace=0.4)
../../../_images/Tutorials_fenicsx_fluid_dynamics_01_Cavity2D_13_0.png

Horizontal velocity across various vertical planes

[12]:
idx_Re = 2
df = pd.read_excel(benchmark_path+'fluid_dynamics/cavity/re_'+str(ReVec[idx_Re])+'_driven_cavity_benchmark_results.xlsx',
                   sheet_name='Table 9')

x_lines = [0.05, 0.1, 0.5, 0.9, 0.95]

df = np.asarray(df.to_numpy())

y_bench = df[:,0].flatten()
ux_bench = df[:,1:]

del df

ux, y_plot = extract1D_x(cavity, N, u_list[idx_Re], x_lines, component = 0)

###############################################################################################

fontsize  = 30
labelsize = 20

nrows, ncols = 2, 3
fig, axs = plt.subplots(nrows = nrows, ncols = ncols, sharex=True,
                        figsize = (6 * ncols, 5 * nrows))
axs = axs.flatten()

for ii in range(len(x_lines)):
    axs[ii+1].plot(y_plot, ux[ii], '-', color=cm.jet(0.1), label='FEniCSx')
    axs[ii+1].plot(y_bench, ux_bench[:, ii], 'o', color=cm.jet(0.9), label='Benchmark')
    axs[ii+1].grid()
    axs[ii+1].set_xlim(0,1)
    axs[ii+1].set_ylabel(r'$u_x$ at $x={:.2f}'.format(x_lines[ii])+'$', fontsize=fontsize)
    axs[ii+1].tick_params(axis='both', labelsize = labelsize)

axs[0].axis('off')
axs[3].set_xlabel(r'$y$', fontsize=fontsize)
axs[4].set_xlabel(r'$y$', fontsize=fontsize)

fig.text(0.175, 0.8, s=r'$Re='+str(ReVec[idx_Re])+'$', fontsize=fontsize)

Line, Label = axs[1].get_legend_handles_labels()
fig.legend(Line, Label, framealpha = 1, fontsize=labelsize, loc=(0.1, 0.75), ncols=1)

fig.subplots_adjust(hspace=0.0, wspace=0.35)
../../../_images/Tutorials_fenicsx_fluid_dynamics_01_Cavity2D_15_0.png