Skip to content

Commit

Permalink
Refactor LBB stability script + add 2D figure
Browse files Browse the repository at this point in the history
  • Loading branch information
danshapero committed Jun 29, 2023
1 parent 1ba23f3 commit 2fc0fed
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 83 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Checkpoint files
## Output files
*.h5
*.json


## Meshes
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

tensor-product-stokes.pdf: tensor-product-stokes.tex tensor-product-stokes.bib
cd demo/flow-around-cylinder && make cylinder_flow.pdf && cd ../../
cd demo/lbb-stability && make results-2d.pdf && cd ../../
pdflatex tensor-product-stokes.tex
bibtex tensor-product-stokes
pdflatex tensor-product-stokes.tex
Expand Down
14 changes: 6 additions & 8 deletions demo/lbb-stability/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
all: taylor-hood-2d.png taylor-hood-3d.png taylor-hood-3d-2layer.png

taylor-hood-2d.png: lbb-stability.py
python3 lbb-stability.py --dimension 2 --output $@
results-2d.pdf: results-2d.json make_plots.py
python make_plots.py --input $< --output $@

taylor-hood-3d.png: lbb-stability.py
python3 lbb-stability.py --dimension 3 --output $@

taylor-hood-3d-2layer.png: lbb-stability.py
python3 lbb-stability.py --dimension 3 --num-layers 2 --output $@
results-2d.json: lbb-stability.py
python lbb-stability.py --dimension 2 --element mini --output $@
python lbb-stability.py --dimension 2 --element taylor-hood --output $@
python lbb-stability.py --dimension 2 --element crouzeix-raviart --output $@
165 changes: 93 additions & 72 deletions demo/lbb-stability/lbb-stability.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json
import argparse
import pathlib
import firedrake
from firedrake import assemble, inner, sym, grad, div, dx
import matplotlib.pyplot as plt
import numpy as np
from petsc4py import PETSc
from slepc4py import SLEPc
import tqdm


def compute_inf_sup_constant(spaces, b, inner_products):
Expand All @@ -16,18 +17,18 @@ def compute_inf_sup_constant(spaces, b, inner_products):
u, p = firedrake.TrialFunctions(Z)
v, q = firedrake.TestFunctions(Z)

A = assemble(-(m(u, v) + b(v, p) + b(u, q)), mat_type='aij').M.handle
M = assemble(n(p, q), mat_type='aij').M.handle
A = assemble(-(m(u, v) + b(v, p) + b(u, q)), mat_type="aij").M.handle
M = assemble(n(p, q), mat_type="aij").M.handle

opts = PETSc.Options()
opts.setValue('eps_gen_hermitian', None)
opts.setValue('eps_target_real', None)
opts.setValue('eps_smallest_magnitude', None)
opts.setValue('st_type', 'sinvert')
opts.setValue('st_ksp_type', 'preonly')
opts.setValue('st_pc_type', 'lu')
opts.setValue('st_pc_factor_mat_solver_type', 'mumps')
opts.setValue('eps_tol', 1e-8)
opts.setValue("eps_gen_hermitian", None)
opts.setValue("eps_target_real", None)
opts.setValue("eps_smallest_magnitude", None)
opts.setValue("st_type", "sinvert")
opts.setValue("st_ksp_type", "preonly")
opts.setValue("st_pc_type", "lu")
opts.setValue("st_pc_factor_mat_solver_type", "mumps")
opts.setValue("eps_tol", 1e-8)

num_values = 1
eigensolver = SLEPc.EPS().create(comm=firedrake.COMM_WORLD)
Expand All @@ -41,67 +42,87 @@ def compute_inf_sup_constant(spaces, b, inner_products):
return λ


def run(*, dimension, num_cells, element, num_layers=None, vdegree=None):
mesh = firedrake.UnitSquareMesh(num_cells, num_cells, diagonal="crossed")
h = mesh.cell_sizes.dat.data_ro[:].min()

cg_1 = firedrake.FiniteElement("CG", "triangle", 1)
cg_2 = firedrake.FiniteElement("CG", "triangle", 2)
b_3 = firedrake.FiniteElement("Bubble", "triangle", 3)
if element == "taylor-hood":
velocity_element = cg_2
pressure_element = cg_1
elif element == "mini":
velocity_element = cg_1 + b_3
pressure_element = cg_1
elif element == "crouzeix-raviart":
velocity_element = cg_2 + b_3
pressure_element = firedrake.FiniteElement("DG", "triangle", 1)

elif dimension == 3:
cg_z = firedrake.FiniteElement("CG", "interval", vdegree)
dg_z = firedrake.FiniteElement("DG", "interval", vdegree - 1)
velocity_element = firedrake.TensorProductElement(velocity_element, cg_z)
pressure_element = firedrake.TensorProductElement(pressure_element, dg_z)
mesh = firedrake.ExtrudedMesh(mesh, num_layers)

V = firedrake.VectorFunctionSpace(mesh, velocity_element)
Q = firedrake.FunctionSpace(mesh, pressure_element)

# This is always just going to be equal to 1 in our case, but in general
# you should compute a characteristic length scale for the domain when
# defining the H1 norm.
d = mesh.topological_dimension()
volume = assemble(firedrake.Constant(1) * dx(mesh))
l = firedrake.Constant(volume ** (1 / d))

b = lambda v, q: -q * div(v) * dx
m = lambda u, v: (inner(u, v) + l**2 * inner(grad(u), grad(v))) * dx
n = lambda p, q: p * q * dx
try:
λ = compute_inf_sup_constant((V, Q), b, (m, n)).real
except PETSc.Error:
λ = np.nan
return h, λ


parser = argparse.ArgumentParser()
parser.add_argument('--dimension', type=int, choices=[2, 3])
parser.add_argument('--log-dx-min', type=int, default=2)
parser.add_argument('--log-dx-max', type=int, default=5)
parser.add_argument('--num-layers', type=int, default=1)
parser.add_argument('--zdegree-min', type=int, default=1)
parser.add_argument('--zdegree-max', type=int, default=6)
parser.add_argument('--output')
parser.add_argument("--dimension", type=int, choices=[2, 3])
elements = ["taylor-hood", "crouzeix-raviart", "mini"]
parser.add_argument("--element", choices=elements, default="taylor-hood")
parser.add_argument("--num-cells-min", type=int, default=4)
parser.add_argument("--num-cells-max", type=int, default=32)
parser.add_argument("--num-cells-step", type=int, default=1)
parser.add_argument("--num-layers", type=int, default=1)
parser.add_argument("--vdegree", type=int, default=1)
parser.add_argument("--output")
args = parser.parse_args()


if args.dimension == 2:
stability_constants = []
for N in tqdm.trange(2**args.log_dx_min, 2**args.log_dx_max + 1):
mesh = firedrake.UnitSquareMesh(N, N)
d = mesh.topological_dimension()
volume = assemble(firedrake.Constant(1) * dx(mesh))
l = firedrake.Constant(volume ** (1 / d))

V = firedrake.VectorFunctionSpace(mesh, 'CG', 2)
Q = firedrake.FunctionSpace(mesh, 'CG', 1)
try:
b = lambda v, q: -q * div(v) * dx
m = lambda u, v: (inner(u, v) + l**2 * inner(grad(u), grad(v))) * dx
n = lambda p, q: p * q * dx
λ = compute_inf_sup_constant((V, Q), b, (m, n))
except PETSc.Error:
λ = np.nan
stability_constants.append(λ)

fig, axes = plt.subplots()
axes.plot(abs(np.array(stability_constants)))
fig.savefig(args.output)

elif args.dimension == 3:
kmin, kmax = args.zdegree_min, args.zdegree_max + 1
fig, axes = plt.subplots()
for k in tqdm.trange(kmin, kmax):
stability_constants = []
for N in tqdm.trange(2**args.log_dx_min, 2**args.log_dx_max + 1):
mesh2d = firedrake.UnitSquareMesh(N, N, diagonal='crossed')
mesh = firedrake.ExtrudedMesh(mesh2d, args.num_layers)
d = mesh.topological_dimension()
volume = assemble(firedrake.Constant(1) * dx(mesh))
l = firedrake.Constant(volume ** (1 / d))

V = firedrake.VectorFunctionSpace(mesh, 'CG', 2, vfamily='GLL', vdegree=k + 1)
Q = firedrake.FunctionSpace(mesh, 'CG', 1, vfamily='GL', vdegree=k)
try:
b = lambda v, q: -q * div(v) * dx
m = lambda u, v: (inner(u, v) + l**2 * inner(grad(u), grad(v))) * dx
n = lambda p, q: p * q * dx
λ = compute_inf_sup_constant((V, Q), b, (m, n))
except PETSc.Error:
λ = np.nan
stability_constants.append(λ)

axes.plot(abs(np.array(stability_constants)), label=str(k))

axes.legend()
fig.savefig(args.output)

else:
raise ValueError("Dimension must be 2 or 3!")
stability_constants = []
nmin, nmax, nstep = args.num_cells_min, args.num_cells_max, args.num_cells_step
for num_cells in range(nmin, nmax + 1, nstep):
h, λ = run(
dimension=args.dimension,
num_cells=num_cells,
element=args.element,
num_layers=args.num_layers,
vdegree=args.vdegree
)
stability_constants.append((h, λ))
print(".", flush=True, end="")

data = []
if pathlib.Path(args.output).is_file():
with open(args.output, "r") as output_file:
data = json.load(output_file)

result = {
"dimension": args.dimension,
"element": args.element,
"results": stability_constants,
}
if args.dimension == 3:
result.update({"num_layers": args.num_layers, "vdegree": args.vdegree})
data.append(result)
with open(args.output, "w") as output_file:
json.dump(data, output_file)
25 changes: 25 additions & 0 deletions demo/lbb-stability/make_plots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import argparse
import numpy as np
import matplotlib.pyplot as plt

parser = argparse.ArgumentParser()
parser.add_argument("--input")
parser.add_argument("--output")
args = parser.parse_args()

with open(args.input, "r") as input_file:
data = json.load(input_file)

fig, ax = plt.subplots()
ax.set_title("Empirical inf-sup constants")
ax.set_xscale("log", base=2)
ax.set_ylim((0.0, 1.0))
ax.set_xlabel("Mesh spacing")
ax.set_ylabel("inf-sup constant")
for entry in data:
element = entry["element"]
hs, λs = zip(*entry["results"])
ax.scatter(hs, λs, label=element)
ax.legend()
fig.savefig(args.output, bbox_inches="tight")
17 changes: 15 additions & 2 deletions tensor-product-stokes.tex
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,16 @@ \section{Demonstrations}

\subsection{Empirical confirmation of inf-sup stability}

Given a mesh of the spatial domain and a pair of discrete spaces $V^h$, $Q^h$, we can check empirically whether they satisfy the inf-sup condition by solving a generalized eigenvalue problem \citep{rognes2012automated}.
This empirical check is not a substitute for a formal proof of inf-sup stability, but rather serves as a ``smoke test'' that our numerical implementation and our proofs are not obviously wrong.
\begin{figure}
\begin{center}
\includegraphics[width=0.6\linewidth]{demo/lbb-stability/results-2d.pdf}
\end{center}
\caption{Empirical inf-sup constants for the three 2D element families on the unit square as the mesh is refined.
These elements are known to stable for the 2D Stokes problem.}
\label{fig:empirical-lbb-2d}
\end{figure}

Given a mesh of the spatial domain and a pair of discrete spaces $V^h$, $Q^h$, we can check empirically whether they satisfy the inf-sup condition by solving a generalized eigenvalue problem \citep{rognes2012automated}.
Let $M$ and $N$ be the Riesz representers for the canonical mapping from $V \to V^*$ and $Q \to Q^*$ respectively.
Consider the following eigenproblem: find velocity-pressure pairs $u$, $p$ and scalars $\lambda$ such that
\begin{align}
Expand All @@ -283,6 +290,12 @@ \subsection{Empirical confirmation of inf-sup stability}
\end{align}
The eigenvalues of this problem are all real and positive, and the square root of the smallest eigenvalue is equal to the inf-sup constant for $V^h$, $Q^h$ \citep{malkus1981eigenproblems}.
We can form this eigenproblem in Firedrake and then call out to the SLEPc package to solve it \citep{hernandez2005slepc}.
This empirical check is not a substitute for a formal proof of inf-sup stability, but rather serves as a ``smoke test'' that our numerical implementation and our proofs are not obviously wrong.

We performed the empirical inf-sup stability check using the unit square as our footprint domain, with a mesh spacing starting at 1/4 and going down to 1/32.
Figure \ref{fig:empirical-lbb-2d} shows the results for 2D elements which we know to be inf-sup stable.
\textcolor{red}{Do the 3D elements.}


\subsection{Lid-driven cavity flow}

Expand Down

0 comments on commit 2fc0fed

Please sign in to comment.