#!/usr/bin/env python # coding: utf-8 # This tutorial forms part of the documentation for Bempp. Find out more at bempp.com # # The multitrace operator and Calderón projector # # This tutorial shows how to create and use a Calderón projectors and multitrace operators for Maxwell problems, as described in section 4 of Scroggs et al (2017). # # ## Background # For Maxwell problems, we define the multitrace operator $\mathsf{A}$ by # # $$\mathsf{A}=\begin{bmatrix}\mathsf{H}&\mathsf{E}\\-\mathsf{E}&\mathsf{H}\end{bmatrix},$$ # # where $\mathsf{H}$ and $\mathsf{E}$ are the magnetic and electric field boundary operators. # # The interior Calderón projector $\mathcal{C}^\text{--}$ is defined by # # $$\mathcal{C}^\text{--}=\tfrac12\mathsf{Id}+\mathsf{A},$$ # # and the exterior Calderón projector $\mathcal{C}^\text{+}$ is defined by # # $$\mathcal{C}^\text{+}=\tfrac12\mathsf{Id}-\mathsf{A}.$$ # # If $\mathbf{E}$ and $\mathbf{H}$ are valid Cauchy data for a exterior Maxwell problem, then # # $$\mathcal{C}^\text{+}\begin{bmatrix}\mathbf{E}\\\mathbf{H}\end{bmatrix}=\begin{bmatrix}\mathbf{E}\\\mathbf{H}\end{bmatrix}.$$ # # It follows from the property $\left[\mathcal{C}^\text{+}\right]^2=\mathcal{C}^\text{+}$ that for any tangential functions $\mathbf{A}$ and $\mathbf{B}$, the functions $\mathbf{E}$ and $\mathbf{H}$ defined by # # $$\begin{bmatrix}\mathbf{E}\\\mathbf{H}\end{bmatrix}=\mathcal{C}^\text{+}\begin{bmatrix}\mathbf{A}\\\mathbf{B}\end{bmatrix}$$ # # are valid Cauchy data. # # ### Discretisation # Let $\mathsf{B}$ and $\mathsf{C}$ be two boundary operators. If the product $\mathsf{BC}$ were discretised, the result would $B_hM^{-1}C_h$, where $M$ is the identity matrix between the ``range`` and ``dual_to_range`` spaces of $\mathsf{C}$. # # If standard Raviart–Thomas (RT) and Nédélec (NC) basic functions are used to discretise $\mathsf{C}$, the matrix $M$ will be numerically singular. To avoid this problem, a stable pairing of RT and Buffa–Christiansen (BC) functions should be used. # # In Bempp, the function ``bempp.api.operators.boundary.maxwell.multitrace_operator`` will automatically use the correct spaces so that all terms in the product $\mathsf{A}^2$ are stable. This is discussed in more detail in section 4 of Scroggs et al (2017). # # ## Implementation # First, we import everything we need and set the wavenumber. # In[1]: import bempp.api from bempp.api.operators.boundary import maxwell from bempp.api.operators.boundary import sparse import numpy as np k = 2 # We make the meshed cube on which we will define our operators. # In[2]: grid = bempp.api.shapes.cube(h=0.1) # Next, we make the multitrace operator and identity on the grid. Here, we must rell the multitrace identity to use vector ``"maxwell"`` spaces instead of the default scalar spaces used for Laplace and Helmholtz. # In[3]: multitrace = maxwell.multitrace_operator(grid, k) identity = sparse.multitrace_identity(grid, spaces='maxwell') # We define the exterior Calderón projector, $\mathcal{C}^\text{+} = \tfrac12\mathsf{Id}-\mathsf{A}$. # In[4]: calderon = 0.5 * identity - multitrace # We define grid functions using a Python function. These functions will not be valid Cauchy data. # In[5]: def tangential_trace(x, n, domain_index, result): result[:] = np.cross(np.array([1,0,0]),n) electric_trace = bempp.api.GridFunction( space=calderon.domain_spaces[0], fun=tangential_trace, dual_space=calderon.dual_to_range_spaces[0]) magnetic_trace = bempp.api.GridFunction( space=calderon.domain_spaces[1], fun=tangential_trace, dual_space=calderon.dual_to_range_spaces[1]) # We now apply the Calderón projector to the functions twice. # In[ ]: traces_1 = calderon * [electric_trace, magnetic_trace] traces_2 = calderon * traces_1 # As explained above, the functions in ``traces_1`` should be valid Cauchy data. Additionally, ``traces_2`` should be equal to ``traces_1`` because $\left[\mathcal{C}^\text{+}\right]^2=\mathcal{C}^\text{+}$. To verify this, we plot the relative difference between the functions. # In[ ]: electric_error = (traces_2[0] - traces_1[0]).l2_norm() / traces_1[0].l2_norm() magnetic_error = (traces_2[1] - traces_1[1]).l2_norm() / traces_1[1].l2_norm() print("Electric error is ", electric_error) print("Magnetic error is ", magnetic_error) # As expected, the errors are small. # In[ ]: traces_1[0].plot() # In[ ]: