Hollow Rectangular Waveguide Modes#

This notebook solves a PEC rectangular waveguide with WaveguideModeSolver, compares against analytic TE/TM modes, and plots the first fields.

import palacetoolkit as ptk

import gmsh
import numpy as np
import matplotlib.pyplot as plt

from palacetoolkit.mode_solver import WaveguideModeSolver
from palacetoolkit.utils import write_and_finalize_gmsh
from palacetoolkit.viz import view_mesh
def _set_transfinite_rect(surf_tag, nx, ny):
    bnd = gmsh.model.getBoundary([(2, surf_tag)], oriented=True)
    lines = [abs(t) for _, t in bnd]
    for line in lines:
        pts = gmsh.model.getBoundary([(1, line)], oriented=False)
        c0 = gmsh.model.getValue(0, pts[0][1], [])
        c1 = gmsh.model.getValue(0, pts[1][1], [])
        dx = abs(c1[0] - c0[0])
        dy = abs(c1[1] - c0[1])
        n = (nx + 1) if dx > dy else (ny + 1)
        gmsh.model.mesh.setTransfiniteCurve(line, n)
    gmsh.model.mesh.setTransfiniteSurface(surf_tag)


def make_rectangular_mesh(a, b, nx, ny, structured=True, lc=None, filename=None):
    gmsh.initialize()
    gmsh.option.setNumber("General.Verbosity", 0)
    gmsh.model.add("hollow_waveguide")

    gmsh.model.occ.addRectangle(0, 0, 0, a, b, tag=1)
    gmsh.model.occ.synchronize()

    gmsh.model.addPhysicalGroup(2, [1], tag=1, name="domain")
    bnd = gmsh.model.getBoundary([(2, 1)], oriented=False)
    bnd_tags = [abs(t) for _, t in bnd]
    gmsh.model.addPhysicalGroup(1, bnd_tags, tag=1, name="PEC")

    if structured:
        _set_transfinite_rect(1, nx, ny)
        gmsh.model.mesh.setRecombine(2, 1)
    else:
        _lc = lc if lc is not None else max(a / nx, b / ny)
        gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.8 * _lc)
        gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 1.2 * _lc)

    gmsh.model.mesh.generate(2)
    return write_and_finalize_gmsh(filename, prefix="wg_rect_")
def analytic_kn(a, b, omega, m_max=5, n_max=5, mu=1.0, eps=1.0):
    modes = []
    k0_sq = omega**2 * mu * eps

    for m in range(0, m_max + 1):
        for n in range(0, n_max + 1):
            if m == 0 and n == 0:
                continue
            kc_sq = (m * np.pi / a) ** 2 + (n * np.pi / b) ** 2
            kn = np.sqrt((k0_sq - kc_sq) + 0j)
            modes.append((f"TE{m}{n}", np.sqrt(kc_sq), kn))

    for m in range(1, m_max + 1):
        for n in range(1, n_max + 1):
            kc_sq = (m * np.pi / a) ** 2 + (n * np.pi / b) ** 2
            kn = np.sqrt((k0_sq - kc_sq) + 0j)
            modes.append((f"TM{m}{n}", np.sqrt(kc_sq), kn))

    modes.sort(key=lambda x: -x[2].real)
    return modes
a, b = 2.0, 1.0
mu, eps = 1.0, 1.0
c0 = 1.0 / np.sqrt(mu * eps)

fc_te10 = c0 * np.pi / a / (2 * np.pi)
f_op = 3.5 * fc_te10
omega = 2 * np.pi * f_op

analytic = analytic_kn(a, b, omega, m_max=4, n_max=4, mu=mu, eps=eps)
print("Top 8 analytic modes:")
for name, kc, kn in analytic[:8]:
    print(f"  {name:6s}: kc={kc:8.4f}, kn={kn.real:+10.6f}{kn.imag:+10.6f}j")
Top 8 analytic modes:
  TE10  : kc=  1.5708, kn= +5.268611 +0.000000j
  TE01  : kc=  3.1416, kn= +4.511769 +0.000000j
  TE20  : kc=  3.1416, kn= +4.511769 +0.000000j
  TE11  : kc=  3.5124, kn= +4.229499 +0.000000j
  TM11  : kc=  3.5124, kn= +4.229499 +0.000000j
  TE21  : kc=  4.4429, kn= +3.238280 +0.000000j
  TM21  : kc=  4.4429, kn= +3.238280 +0.000000j
  TE30  : kc=  4.7124, kn= +2.831793 +0.000000j
mesh_file = make_rectangular_mesh(
    a,
    b,
    nx=32,
    ny=16,
    structured=True,
)
view_mesh(mesh_file)
solver = WaveguideModeSolver(mesh_file, order=2, mu_inv=1.0 / mu, eps=eps, pec_bdr="all")
results = solver.solve(omega, num_modes=8, mode_idx=1)

print("\nNumerical modes:")
for i, kn in enumerate(results["kn"], start=1):
    print(f"  Mode {i}: kn={kn.real:+10.6f}{kn.imag:+10.6f}j")
Loading mesh file: /tmp/wg_rect_mhlsva3w.msh
Groups to render transparent: ['air_none', 'air_plastic_enclosure']

Mesh loaded successfully with 2 cell blocks
Found 1024 triangles total
Physical group tags in mesh: {1: 'domain'}
  FE spaces: ND dofs = 4192, H1 dofs = 2145, total = 6337
  Essential DOFs: ND = 192, H1 = 192, total = 384
  Solving eigenvalue problem (omega = 5.49779, sigma = -33.2482, size = 6337)...
  Found 8 modes:
    Mode 1: kn = +5.26861102e+00 -0.00000000e+00j <-- selected
    Mode 2: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 3: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 4: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 5: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 6: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 7: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 8: kn = +2.83175256e+00 -0.00000000e+00j

Numerical modes:
  Mode 1: kn= +5.268611 -0.000000j
  Mode 2: kn= +4.511767 -0.000000j
  Mode 3: kn= +4.511767 -0.000000j
  Mode 4: kn= +4.229496 -0.000000j
  Mode 5: kn= +4.229496 -0.000000j
  Mode 6: kn= +3.238273 -0.000000j
  Mode 7: kn= +3.238273 -0.000000j
  Mode 8: kn= +2.831753 -0.000000j
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
fig.suptitle("Hollow Waveguide First Modes", fontsize=14)

for mode_i in range(3):
    r = solver.solve(omega, num_modes=8, mode_idx=mode_i + 1)
    X, Y, Ex, Ey, Ez = solver.get_field_on_grid(r["Et_vec"], r["En_vec"], nx=60, ny=30)

    Et_mag = np.sqrt(np.abs(Ex) ** 2 + np.abs(Ey) ** 2)
    En_mag = np.abs(Ez)

    ax = axes[0, mode_i]
    im = ax.pcolormesh(X, Y, Et_mag, cmap="hot", shading="auto")
    ax.set_title(f"|Et| mode {mode_i+1}")
    ax.set_aspect("equal")
    fig.colorbar(im, ax=ax, fraction=0.046)

    ax = axes[1, mode_i]
    im = ax.pcolormesh(X, Y, En_mag, cmap="hot", shading="auto")
    ax.set_title(f"|En| mode {mode_i+1}")
    ax.set_aspect("equal")
    fig.colorbar(im, ax=ax, fraction=0.046)

plt.tight_layout()
plt.show()
  Solving eigenvalue problem (omega = 5.49779, sigma = -33.2482, size = 6337)...
  Found 8 modes:
    Mode 1: kn = +5.26861102e+00 -0.00000000e+00j <-- selected
    Mode 2: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 3: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 4: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 5: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 6: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 7: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 8: kn = +2.83175256e+00 -0.00000000e+00j
  Solving eigenvalue problem (omega = 5.49779, sigma = -33.2482, size = 6337)...
  Found 8 modes:
    Mode 1: kn = +5.26861102e+00 -0.00000000e+00j
    Mode 2: kn = +4.51176670e+00 -0.00000000e+00j <-- selected
    Mode 3: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 4: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 5: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 6: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 7: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 8: kn = +2.83175256e+00 -0.00000000e+00j
  Solving eigenvalue problem (omega = 5.49779, sigma = -33.2482, size = 6337)...
  Found 8 modes:
    Mode 1: kn = +5.26861102e+00 -0.00000000e+00j
    Mode 2: kn = +4.51176670e+00 -0.00000000e+00j
    Mode 3: kn = +4.51176670e+00 -0.00000000e+00j <-- selected
    Mode 4: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 5: kn = +4.22949611e+00 -0.00000000e+00j
    Mode 6: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 7: kn = +3.23827331e+00 -0.00000000e+00j
    Mode 8: kn = +2.83175256e+00 -0.00000000e+00j
../_images/bdb8e46d004aff607c64ec8cc3916d992cc391a18762c19390cfa05c183f0f20.png