Defining a simple metric field and adapting the mesh to itΒΆ

Animate provides the RiemannianMetric class which is simply a firedrake.Function on a firedrake.TensorFunctionSpace with additional functionality. For metric-based mesh adaptivity the metric is usually assumed to be vertex-based, so we need to define the metric in a Continuous Galerkin (CG) P1 tensor function space

from firedrake import *
from firedrake.pyplot import triplot

from animate import *

mesh = UnitSquareMesh(10, 10)
P1_ten = TensorFunctionSpace(mesh, "CG", 1)
metric = RiemannianMetric(P1_ten)

We start by choosing a uniform, and isotropic metric, which is to say that the metric tensor is just a constant diagonal matrix that is a scalar multiple of the identity matrix:

alpha = 100
metric.interpolate(as_matrix([[alpha, 0], [0, alpha]]))

In metric based adaptivity the metric is used to measure the length of edges: for any edge \(\mathbf e={\mathbf v_2}-{\mathbf v_1}\) written as a vector between vertices \(\mathbf v_1\) and \(\mathbf v_2\), its length in metric space is given by: .. math:

\ell_{\mathcal M}(\vec{\mathbf{e}})
:=\sqrt{\mathbf{e}^T{\mathcal M}\mathbf{e}}

which in this case evaluates to a simple scalar multiple of its Euclidean length: .. math:

\ell_{\mathcal M}(\vec{\mathbf{e}})
:=\sqrt{\mathbf{e}^T \begin{pmatrix} \alpha & 0 \\ 0 & \alpha \end{pmatrix} \mathbf{e}}
 =\sqrt{\alpha}~\sqrt{\mathbf{e}^T\mathbf{e}}.

The metric is used by the mesh adaptivity library to determine the optimal length of edges, where an edge is considered optimal if its length in metric space is 1. For our isotropic metric this means that an edge of (Euclidean) length \(h=\sqrt{\mathbf{e}^T\mathbf{e}}\) is considered optimal if \(\sqrt{\alpha} h=1\), or \(h=1/\sqrt{\alpha}\). Thus using \(\alpha=100\) we expect a mesh with edges of length \(h=0.1\)

new_mesh = adapt(mesh, metric)
import matplotlib.pyplot as plt

fig, axes = plt.subplots()
triplot(new_mesh, axes=axes)
axes.set_aspect("equal")
fig.show()
fig.savefig("simple_metric-mesh1.jpg")
../_images/simple_metric-mesh1.jpg

To create a anisotropic mesh with edge lengths \(h_x=0.1\) in the x-direction, and \(h_y=0.25\) we simply create a diagonal Riemannian metric with, respectively, the values \(1/0.1^2\) and \(1/0.25^2\) along the diagonal:

hx = 0.1
hy = 0.25
metric.interpolate(as_matrix([[1 / hx**2, 0], [0, 1 / hy**2]]))
new_mesh = adapt(mesh, metric)
fig, axes = plt.subplots()
triplot(new_mesh, axes=axes)
axes.set_aspect("equal")
fig.show()
fig.savefig("simple_metric-mesh2.jpg")
../_images/simple_metric-mesh2.jpg

An example of a non-uniform mesh, in which we ask for higher resolution inside a circle of radius \(r\) is given below:

x, y = SpatialCoordinate(mesh)
r = 0.4
h1 = 0.02
h2 = 0.05
high = as_matrix([[1 / h1**2, 0], [0, 1 / h1**2]])
low = as_matrix([[1 / h2**2, 0], [0, 1 / h2**2]])
metric.interpolate(conditional(sqrt((x - 0.5) ** 2 + (y - 0.5) ** 2) < r, high, low))
new_mesh = adapt(mesh, metric)
fig, axes = plt.subplots(figsize=(16, 8), ncols=2)
triplot(mesh, axes=axes[0])
axes[0].set_aspect("equal")
axes[0].set_title("Input mesh: 10 x 10")
triplot(new_mesh, axes=axes[1])
axes[1].set_aspect("equal")
axes[1].set_title("Adapted mesh")
fig.show()
fig.savefig("simple_metric-mesh3.jpg")
../_images/simple_metric-mesh3.jpg

As we can observe in the figure on the right, the circular region of high resolution is not very accurately approximated. This is a consequence of the low resolution of the initial mesh (left figure) on which we have defined our metric. For a more accurate result we therefore increase the resolution of this initial mesh

mesh = UnitSquareMesh(50, 50)
P1_ten = TensorFunctionSpace(mesh, "CG", 1)
metric = RiemannianMetric(P1_ten)
x, y = SpatialCoordinate(mesh)
r = 0.4
h1 = 0.02
h2 = 0.05
high = as_matrix([[1 / h1**2, 0], [0, 1 / h1**2]])
low = as_matrix([[1 / h2**2, 0], [0, 1 / h2**2]])
metric.interpolate(conditional(sqrt((x - 0.5) ** 2 + (y - 0.5) ** 2) < r, high, low))
new_mesh = adapt(mesh, metric)
fig, axes = plt.subplots(figsize=(16, 8), ncols=2)
triplot(mesh, axes=axes[0])
axes[0].set_aspect("equal")
axes[0].set_title("Input mesh: 50 x 50")
triplot(new_mesh, axes=axes[1])
axes[1].set_aspect("equal")
axes[1].set_title("Adapted mesh")
fig.show()
fig.savefig("simple_metric-mesh4.jpg")
../_images/simple_metric-mesh4.jpg

This demo can also be accessed as a Python script.