Diffusion multi-material#
The first MMS problem has two materials (denoted, respectively, by left and right). In material left, the solubility is \(K_{S,\mathrm{left}} = 3\) and the diffusivity is \(D_\mathrm{left} = 2\). In material right, the solubility is \(K_{S,\mathrm{right}} = 6\) and the diffusivity is \(D_\mathrm{right} = 5\). Two exact solutions for mobile concentration of hydrogen are manufactured for both subdomains:
MMS sources are derived in each material:
These exact solutions can then determine the MMS fluxes and boundary conditions.
FESTIM code#
Comparison with exact solution#
The concentration fields can be visualised using pyvista.
import pyvista
from dolfinx.plot import vtk_mesh
pyvista.start_xvfb()
pyvista.set_jupyter_backend("html")
def get_ugrid(computed_solution: dolfinx.fem.Function, label):
u_topology, u_cell_types, u_geometry = vtk_mesh(computed_solution.function_space)
u_grid = pyvista.UnstructuredGrid(u_topology, u_cell_types, u_geometry)
u_grid.point_data[label] = computed_solution.x.array.real
u_grid.set_active_scalars(label)
return u_grid
u_plotter = pyvista.Plotter(shape=(1, 2))
u_grid_left = get_ugrid(H.subdomain_to_post_processing_solution[left_volume], "c")
u_grid_right = get_ugrid(H.subdomain_to_post_processing_solution[right_volume], "c")
u_plotter.subplot(0, 0)
u_plotter.add_mesh(u_grid_left, show_edges=True)
u_plotter.add_mesh(u_grid_right, show_edges=True)
u_plotter.view_xy()
u_plotter.subplot(0, 1)
exact_left = dolfinx.fem.Function(
H.subdomain_to_post_processing_solution[left_volume].function_space
)
exact_left.interpolate(exact_solution_left(np))
exact_right = dolfinx.fem.Function(
H.subdomain_to_post_processing_solution[right_volume].function_space
)
exact_right.interpolate(exact_solution_right(np))
u_grid_exact_left = get_ugrid(exact_left, "c_exact")
u_grid_exact_right = get_ugrid(exact_right, "c_exact")
u_plotter.add_mesh(u_grid_exact_left, show_edges=False)
u_plotter.add_mesh(u_grid_exact_right, show_edges=False)
u_plotter.view_xy()
if not pyvista.OFF_SCREEN:
u_plotter.show()
else:
figure = u_plotter.screenshot("discontinuity_concentration.png")
/home/docs/checkouts/readthedocs.org/user_builds/festim-vv-report/conda/festim-2/lib/python3.11/site-packages/pyvista/plotting/utilities/xvfb.py:48: PyVistaDeprecationWarning: This function is deprecated and will be removed in future version of PyVista. Use vtk-osmesa instead.
warnings.warn(
Computing errors#
First, we compute the \(L^2\)-norm of the error, defined by \(E=\sqrt{\int_\Omega (c-c_\mathrm{exact})^2\mathrm{d} x}\). Secondly, we compute the maximum error at any degree of freedom.
computed_left = H.subdomain_to_post_processing_solution[left_volume]
computed_right = H.subdomain_to_post_processing_solution[right_volume]
E_l2_left = error_L2(computed_left, exact_solution_left(np))
E_max_left = np.max(np.abs(exact_left.x.array - computed_left.x.array))
E_l2_right = error_L2(computed_right, exact_solution_right(np))
E_max_right = np.max(np.abs(exact_right.x.array - computed_right.x.array))
print("Left volume:")
print(f"L2 error: {E_l2_left:.2e}")
print(f"Max error: {E_max_left:.2e}")
print("\nRight volume:")
print(f"L2 error: {E_l2_right:.2e}")
print(f"Max error: {E_max_right:.2e}")
Left volume:
L2 error: 2.78e-02
Max error: 5.63e-02
Right volume:
L2 error: 5.26e-02
Max error: 7.25e-02
Compute convergence rates#
It is also possible to compute how the numerical error decreases as we increase the number of cells. By iteratively refining the mesh, we find that the error exhibits a second order convergence rate. This is expected for this particular problem as first order finite elements are used.
import matplotlib.pyplot as plt
errors_left, errors_right = [], []
ns = [8, 10, 20, 30, 50, 100, 150]
for n in ns:
nx = ny = n
fenics_mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, nx, ny)
new_model = F.HydrogenTransportProblemDiscontinuous()
new_model.mesh = F.Mesh(fenics_mesh)
new_model.species = my_model.species
new_model.subdomains = my_model.subdomains
new_model.surface_to_volume = my_model.surface_to_volume
new_model.interfaces = my_model.interfaces
new_model.sources = my_model.sources
new_model.boundary_conditions = my_model.boundary_conditions
new_model.temperature = my_model.temperature
new_model.settings = my_model.settings
new_model.initialise()
new_model.run()
computed_solution_left = H.subdomain_to_post_processing_solution[left_volume]
computed_solution_right = H.subdomain_to_post_processing_solution[right_volume]
errors_left.append(error_L2(computed_solution_left, exact_solution_left(np)))
errors_right.append(error_L2(computed_solution_right, exact_solution_right(np)))
h = 1 / np.array(ns)
plt.loglog(h, errors_left, marker="o", label="Left volume")
plt.loglog(h, errors_right, marker="o", label="Right volume")
plt.xlabel("Element size")
plt.ylabel("L2 error")
plt.loglog(h, 2 * h**2, linestyle="--", color="black")
plt.annotate(
"2nd order", (h[0], 2 * h[0] ** 2), textcoords="offset points", xytext=(10, 0)
)
plt.grid(alpha=0.3)
plt.gca().spines[["right", "top"]].set_visible(False)
plt.legend()
plt.show()