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)
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)
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)