Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ dependencies:
- scipy
- pip
- pip:
- festim==1.3
- festim~=1.3
6 changes: 5 additions & 1 deletion report/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ parts:
- file: verification/mms/simple
- file: verification/mms/simple_transient
- file: verification/mms/simple_temperature
- file: verification/mms/simple_cylindrical
- file: verification/mms/fluxes
- file: verification/mms/heat_transfer
- file: verification/mms/discontinuity
Expand All @@ -25,7 +26,10 @@ parts:
sections:
- file: verification/mes/radioactive_decay
- file: verification/mms/radioactive_decay
- file: verification/mms/soret
- file: verification/soret
sections:
- file: verification/mms/soret
- file: verification/mms/soret_cylindrical
- file: verification/tmap
sections:
- file: verification/mes/depleting_source
Expand Down
232 changes: 232 additions & 0 deletions report/verification/mms/simple_cylindrical.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
jupytext:
formats: ipynb,md:myst
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.16.4
kernelspec:
display_name: vv-festim-report-env
language: python
name: python3
---

# Simple cylindrical case

```{tags} 2D, MMS, steady state, cylindrical
```

This is a simple MMS example on a cylindrical mesh.
We will only consider diffusion of hydrogen in a unit square domain $\Omega$ s.t. $r \in [1, 2]$ and $z \in [0, 1]$ at steady state with a homogeneous diffusion coefficient $D$.
Moreover, a Dirichlet boundary condition will be assumed on the boundaries $\partial \Omega $.

The problem is therefore:

$$
\begin{align}
& \nabla \cdot (D \ \nabla{c}) = -S \quad \text{on } \Omega \\
& c = c_0 \quad \text{on } \partial \Omega
\end{align}
$$(problem_simple_cylindrical)

The exact solution for mobile concentration is:

$$
\begin{equation}
c_\mathrm{exact} = 1 + r^2
\end{equation}
$$(c_exact_simple_cylindrical)

Injecting {eq}`c_exact_simple_cylindrical` in {eq}`problem_simple_cylindrical`, we obtain the expressions of $S$ and $c_0$:

$$
\begin{align}
& S = -4D \\
& c_0 = c_\mathrm{exact}
\end{align}
$$

We can then run a FESTIM model with these values and compare the numerical solution with $c_\mathrm{exact}$.

+++

## FESTIM code

```{code-cell} ipython3
import festim as F
import sympy as sp
import fenics as f
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# Create and mark the mesh
nx = ny = 100
fenics_mesh = f.RectangleMesh(f.Point(1, 0), f.Point(2, 1), nx, ny)

volume_markers = f.MeshFunction("size_t", fenics_mesh, fenics_mesh.topology().dim())
volume_markers.set_all(1)

surface_markers = f.MeshFunction(
"size_t", fenics_mesh, fenics_mesh.topology().dim() - 1
)
surface_markers.set_all(0)

class Boundary(f.SubDomain):
def inside(self, x, on_boundary):
return on_boundary


boundary = Boundary()
boundary.mark(surface_markers, 1)

r = F.x
exact_solution = 1 + r**2
D = 2


def run_model(type: str):

# Create the FESTIM model
my_model = F.Simulation()

my_model.mesh = F.Mesh(
fenics_mesh,
volume_markers=volume_markers,
surface_markers=surface_markers,
type=type,
)

my_model.sources = [
F.Source(-4 * D, volume=1, field="solute"),
]

my_model.boundary_conditions = [
F.DirichletBC(surfaces=[1], value=exact_solution, field="solute"),
]

my_model.materials = F.Material(id=1, D_0=D, E_D=0)

my_model.T = F.Temperature(500) # ignored in this problem

my_model.settings = F.Settings(
absolute_tolerance=1e-10,
relative_tolerance=1e-10,
transient=False,
)

my_model.initialise()
my_model.run()

return my_model.h_transport_problem.mobile.post_processing_solution
```

## Comparison with exact solution

```{code-cell} ipython3
:tags: [hide-input]

c_exact = f.Expression(sp.printing.ccode(exact_solution), degree=4)
c_exact = f.project(c_exact, f.FunctionSpace(fenics_mesh, "CG", 1))

cylindrical_solution = run_model("cylindrical")

E_cyl = f.errornorm(cylindrical_solution, c_exact, "L2")
print(f"L2 error, cylindrical: {E_cyl:.2e}")

# plot exact solution and computed solution
fig, axs = plt.subplots(
1,
3,
figsize=(15, 5),
)

plt.sca(axs[0])
plt.title("Exact solution")
plt.xlabel("r")
plt.ylabel("z")
CS1 = f.plot(c_exact, cmap="inferno")
plt.sca(axs[1])
plt.xlabel("r")
plt.title("Cylindrical solution")
CS2 = f.plot(cylindrical_solution, cmap="inferno")

plt.colorbar(CS1, ax=[axs[0]], shrink=0.8)
plt.colorbar(CS2, ax=[axs[1]], shrink=0.8)

# axs[0].sharey(axs[1])
plt.setp(axs[1].get_yticklabels(), visible=False)
plt.setp(axs[2].get_yticklabels(), visible=False)

for CS in [CS1, CS2]:
CS.set_edgecolor("face")


def compute_arc_length(xs, ys):
"""Computes the arc length of x,y points based
on x and y arrays
"""
points = np.vstack((xs, ys)).T
distance = np.linalg.norm(points[1:] - points[:-1], axis=1)
arc_length = np.insert(np.cumsum(distance), 0, [0.0])
return arc_length


# define the profiles
profiles = [
{"start": (1.0, 0.0), "end": (2.0, 1.0)},
{"start": (1.2, 0.8), "end": (1.7, 0.2)},
{"start": (1.2, 0.6), "end": (1.8, 0.8)},
]

# plot the profiles on the right subplot
for i, profile in enumerate(profiles):
start_x, start_y = profile["start"]
end_x, end_y = profile["end"]
plt.sca(axs[1])
(l,) = plt.plot([start_x, end_x], [start_y, end_y])

plt.sca(axs[-1])

points_x_exact = np.linspace(start_x, end_x, num=30)
points_y_exact = np.linspace(start_y, end_y, num=30)
arc_length_exact = compute_arc_length(points_x_exact, points_y_exact)
u_values = [c_exact(x, y) for x, y in zip(points_x_exact, points_y_exact)]

points_x = np.linspace(start_x, end_x, num=100)
points_y = np.linspace(start_y, end_y, num=100)
arc_lengths = compute_arc_length(points_x, points_y)
computed_values = [cylindrical_solution(x, y) for x, y in zip(points_x, points_y)]

(exact_line,) = plt.plot(
arc_length_exact,
u_values,
color=l.get_color(),
marker="o",
linestyle="None",
alpha=0.3,
)
(computed_line,) = plt.plot(arc_lengths, computed_values, color=l.get_color())

plt.sca(axs[-1])
plt.xlabel("Arc length")
plt.ylabel("Solution")

legend_marker = mpl.lines.Line2D(
[],
[],
color="black",
marker=exact_line.get_marker(),
linestyle="None",
label="Exact",
)
legend_line = mpl.lines.Line2D([], [], color="black", label="Computed")
plt.legend(
[legend_marker, legend_line], [legend_marker.get_label(), legend_line.get_label()]
)

plt.grid(alpha=0.3)
plt.gca().spines[["right", "top"]].set_visible(False)
plt.show()
```
3 changes: 1 addition & 2 deletions report/verification/mms/soret.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ kernelspec:

# Soret Effect

```{tags} 2D, MMS, soret, steady state
```{tags} 2D, MMS, steady state, soret
```

This MMS case verifies the implementation of the Soret effect in FESTIM.
Expand Down Expand Up @@ -47,7 +47,6 @@ For this problem, we choose:
& D = 2
\end{align}


Injecting {eq}`c_exact_soret` in {eq}`problem_soret`, we obtain the expressions of $S$ and $c_0$:

$$
Expand Down
Loading