Skip to main content

Finite element method for solving the electromagnetic Helmholtz equation.

Project description

Helmi_FEM

pypi shield

An open-source implementation of the Helmholtz equation for finite element analysis of electromagnetic wave propagation and scattering.

Based on scikit-fem for finite element assembly and on SciPy, NumPy, and matplotlib for solving, processing and plotting.

Installation

pip install Helmi-FEM

Usage

import helmi

See the example workflow below and additional examples in the subfolder.

Waveguide fields

Features

  • Supports complex-valued parameters
  • TE and TM polarization
  • Dirichlet and third-order boundary conditions (Neumann, freespace, absorbing)
  • Near-field to far-field transformation

Workflow

1. Create or load a mesh of the simulation domain

Use the mesh constructors of skfem.mesh for simple shapes such as rectangles, circles, or L-shapes. Use gmsh or other software for complex shapes.

https://scikit-fem.readthedocs.io/en/latest/api.html#module-skfem.mesh

http://gmsh.info/

import skfem
import numpy as np
from helmi import Helmholtz

# create a rectangular mesh with skfem:
x_pts = np.linspace(0, 100, 101)
y_pts = np.linspace(-5, 5, 21)
mesh = skfem.MeshTri.init_tensor(x_pts, y_pts)
mesh = mesh.with_subdomains({'air': lambda x: x[0] < 50,
                             'plastic': lambda x: x[0] >= 50})
mesh = mesh.with_boundaries({'bound_xmin': lambda x: np.isclose(x[0], x_pts[0]),
                             'bound_xmax': lambda x: np.isclose(x[0], x_pts[-1]),
                             'bound_ymin': lambda x: np.isclose(x[1], y_pts[0]),
                             'bound_ymax': lambda x: np.isclose(x[1], y_pts[-1])})

# alternatively, load a mesh from file:
mesh = skfem.Mesh.load('mymesh.msh')

2. Choose a finite element from skfem.element.

https://scikit-fem.readthedocs.io/en/latest/api.html#module-skfem.element

element = skfem.ElementTriP2()
fem = Helmholtz(mesh, element)

3. Assemble the domains and the boundaries

The Helmholtz equation is implemented as a general second-order partial differential equation:

α(2Φx2+2Φy2)+βΦ=f.

The electromagnetic wave propagation can be formulated in two different ways, depending on the polarization of interest:

  1. TM polarization: Φ=EZ, α=1/μr, β=k02ϵr, and f=jk0Z0JZ
  2. TE polarization: Φ=HZ, α=1/ϵr, β=k02μr, and f=1ϵr(JYxJXy)

with the normalized free-space vacuum wave number k0=2πλ0=2πfc0. Here, c0 is propagation velocity (speed of light) in vacuum, expressed in mesh units per second.

For f=10GHz and a mesh unit of a=1mm, one gets k02π10GHz3e8m/s/1mm0.209.

For a mesh with two different subdomains labeled 'air' and 'plastic', the domains are assembled with their respective parameters:

k0 = 0.5
eps_air = 1
mu_air = 1
eps_plastic = 2 - 0.1j
mu_plastic = 1
fem.assemble_subdomains(alpha={'air': 1 / mu_air, 
                               'plastic': 1 / mu_plastic}, 
                        beta={'air': -1 * k0 ** 2 * eps_air, 
                              'plastic': -1 * k0 ** 2 * eps_plastic}, 
                        f={'air': 0, 
                           'plastic': 0})

Similarly, the boundary conditions are defined. In this example, the upper and lower boundaries, labeled as 'bound_ymax' and 'bound_ymin', are supposed to be perfectly conducting (metallic) waveguide walls. The corresponding essential (Dirichlet) boundary condition is EZ=0:

fem.assemble_boundaries_dirichlet(value={'bound_ymin': 0, 
                                         'bound_ymax': 0})

The boundaries on the left and right, labeled 'bound_xmin' and 'bound_xmax', are supposed to be waveguide interfaces that are artificially extended to behave like infinite waveguides for the fundamental propagation mode. This can be formulated with the third-order boundary condition: α(Φxx^+Φyy^)n^+γΦ=q. Here, x^, y^, and n^ are unit vectors in x and y direction, and outward normal to the boundary, respectively.

The desired waveguide boundary condition is obtained with γ=α1jk0. For the excitation, an incident field EZ,inc=1 is added with q=α2jk0 on the left boundary. The natural (Neumann) boundary condition is a special case with γ=q=0, which does not have to be stated explicitly.

fem.assemble_boundaries_3rd(gamma={'bound_xmin': 1 / mu_plastic * 1j * k0, 
                                   'bound_xmax': 1 / mu_plastic * 1j * k0}, 
                            q={'bound_xmin': 1 / mu_plastic * 2j * k0, 
                               'bound_xmax': 0})

4. Solve the linear system for the field solution

fem.solve()

5. Process the solution

After solving, the field solution is stored in fem.phi as a complex vector. The individual real and imaginary parts are also stored seperately in fem.phi_re and fem.phi_im.

The corresponding locations of the N elements in the solution vector on the mesh are stored in fem.basis.doflocs, which has the shape (2, N).

scikit-fem offers helper functions to find certain elements, for example by means of labeled subdomains or boundaries:

x_bound_xmin, y_bound_xmin = fem.basis.doflocs[:, fem.basis.get_dofs('bound_xmin')]

Plotting is simple with the functions in skfem.visuals:

from skfem.visuals.matplotlib import plot
import matplotlib.pyplot as mplt

fig, ax = mplt.subplots(2, 1)
plot(fem.basis, fem.phi_re, ax=ax[0])
plot(fem.basis, fem.phi_im, ax=ax[1])
ax[0].set_aspect(1)
ax[1].set_aspect(1)
mplt.tight_layout()
mplt.show()

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page