--- jupyter: description: 'Waveguide cutoff frequencies with planex2d and NGSolve: the modes of a WR-90 rectangular waveguide and a round one against the closed forms, and a ridge waveguide, whose ridge lowers the cutoff where no formula is left.' --- # Waveguide cutoff Below which frequency does a waveguide stop carrying a wave? Each mode of a hollow metal guide has a cutoff frequency, below which it does not propagate. For a mode whose field across the guide is $\psi$, the cutoff follows from the eigenvalue problem $\nabla^2 \psi + k_c^2 \psi = 0$ over the guide's section, $f_c = c\,k_c / (2\pi)$. The TE modes take $\partial\psi/\partial n = 0$ on the wall and the TM modes $\psi = 0$. For a rectangle $a \times b$, $k_c = \pi\sqrt{(m/a)^2 + (n/b)^2}$, so the standard WR-90 guide, 22.86 × 10.16 mm, has its lowest mode TE10 at $c/(2a) = 6.557$ GHz. A round guide of radius $r$ has TE11 at $1.8412\,c/(2\pi r)$ and TM01 at $2.4048\,c/(2\pi r)$, from the zeros of Bessel functions. Here, NGSolve finds the cutoffs of both and checks them against the closed forms, then those of a ridge waveguide, whose ridge pulls the lowest cutoff down where no formula is left. ![The electric field of the lowest mode in a rectangular, a ridge and a round waveguide, gathered in the ridge's gap](../_static/gallery/waveguide-cutoff.png){.result} ```python import math import ngsolve as ngs import numpy as np import planex as px import scipy.sparse as sp from scipy.sparse.linalg import eigsh ``` ```python nbsphinx="hidden" from docs_figures import MAP, Row def sampled(mode, mesh, middle=(0, 0)): """the strength of the mode's electric field across the guide, |grad ψ| of a TE mode, 1 at middle, for Row""" def field(face, plot=True, **_): (u0, v0), (u1, v1) = face.bounding_box gu, gv = np.meshgrid(np.linspace(u0, u1, 161), np.linspace(v0, v1, 121)) uv = np.c_[gu.ravel(), gv.ravel()] uv = uv[face.Distance(uv, signed=True) < -1e-3] strength = ngs.Norm(ngs.grad(mode)) values = np.asarray(strength(mesh(uv[:, 0], uv[:, 1]))).ravel() / strength(mesh(*middle)) return {"points": [tuple(p) for p in uv], "values": values, "label": "|E|, 1 in the middle"} return field ``` ## The cutoffs of a section `cutoffs` solves the eigenvalue problem over a section in millimeters, for the TE modes with a free wall or the TM modes with $\psi = 0$ on it, and returns the lowest cutoff frequencies in GHz with the modes. A TE section has one eigenvalue zero, the constant, which is no mode and is left out: ```python c = 299_792_458e3 # mm/s def cutoffs(section, kind, count=4): mesh = ngs.Mesh(px.OCCGeometry(section).GenerateMesh(maxh=1.0)) mesh.Curve(3) V = ngs.H1(mesh, order=4, dirichlet=".*" if kind == "TM" else "") u, v = V.TnT() stiffness = ngs.BilinearForm(ngs.grad(u) * ngs.grad(v) * ngs.dx).Assemble() mass = ngs.BilinearForm(u * v * ngs.dx).Assemble() free = np.array(list(V.FreeDofs()), dtype=bool) K = sp.csr_matrix(stiffness.mat.CSR(), shape=(V.ndof, V.ndof))[free][:, free] M = sp.csr_matrix(mass.mat.CSR(), shape=(V.ndof, V.ndof))[free][:, free] values, vectors = eigsh(K, k=count + 1, M=M, sigma=-1e-3, which="LM") order = np.argsort(values)[1:] if kind == "TE" else np.argsort(values)[:count] modes = [] for k in order: mode = ngs.GridFunction(V) mode.vec.FV().NumPy()[free] = vectors[:, k] modes.append(mode) return [c * math.sqrt(values[k]) / (2 * math.pi) / 1e9 for k in order], modes, mesh ``` ## A rectangular and a round guide The WR-90 guide is 22.86 × 10.16 mm. Its TE modes TE10, TE20 and TE01 follow the closed form, and so do the round guide's TE11 and TM01, for a radius of 10 mm: ```python wr90 = px.QuickSketch().RectangleC(22.86, 10.16).Face() te, wr90_modes, wr90_mesh = cutoffs(wr90, "TE") for (m, n), f in zip(((1, 0), (2, 0), (0, 1)), te): print(f"TE{m}{n}", round(f, 4), "GHz | closed form", round(c / 2 * math.hypot(m / 22.86, n / 10.16) / 1e9, 4)) round_guide = px.Circle((0, 0), 10).Face() te11, round_modes, round_mesh = cutoffs(round_guide, "TE") tm01, _, _ = cutoffs(round_guide, "TM") print("TE11", round(te11[0], 4), "GHz | closed form", round(1.841184 * c / (2 * math.pi * 10) / 1e9, 4)) print("TM01", round(tm01[0], 4), "GHz | closed form", round(2.404826 * c / (2 * math.pi * 10) / 1e9, 4)) ``` ## A ridge waveguide A ridge down the middle of the broad wall leaves a narrow gap, which carries the lowest mode's field like a capacitor and lowers its cutoff, while the next mode stays nearly where it was. The guide carries a single mode over a wider band. The example is the WR-90 section with a ridge 6 mm wide reaching down to a gap of 3 mm. Across the guide, a TE mode's electric field has the strength $|\nabla\psi|$, here 1 in the middle of each guide and of the ridge's gap. In the plain guide it is strongest in the middle and vanishes at the narrow walls. In the ridged guide it gathers in the gap and grows without bound at the ridge's sharp corners (Figure 1): ```python ridge = px.QuickSketch((0, 10.16 / 2 - (10.16 - 3) / 2)).RectangleC(6, 10.16 - 3).Face() ridged = wr90 - ridge te_ridge, ridge_modes, ridge_mesh = cutoffs(ridged, "TE") print("lowest cutoff", round(te_ridge[0], 4), "GHz against", round(te[0], 4), "| next", round(te_ridge[1], 4), "GHz against", round(te[1], 4)) print("single-mode band", round(te_ridge[1] / te_ridge[0], 3), "against", round(te[1] / te[0], 3)) ``` ```python nbsphinx="hide-input" Row((wr90, "WR-90, TE10", {"field": sampled(wr90_modes[0], wr90_mesh), "cmap": MAP.reversed(), "clim": (0, 1.2)}), (round_guide, "Round, TE11", {"field": sampled(round_modes[0], round_mesh), "cmap": MAP.reversed(), "clim": (0, 1.2)}), (ridged, "Ridged, the lowest mode", {"field": sampled(ridge_modes[0], ridge_mesh, (0, -3.58)), "cmap": MAP.reversed(), "clim": (0, 1.2)}), caption="The electric field of the lowest mode in the three guides") ``` ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") import matplotlib.pyplot as plt from gallery import save_figure places = [(wr90, wr90_modes[0], wr90_mesh, (0, 6.5), (0, 0)), (ridged, ridge_modes[0], ridge_mesh, (0, -6.5), (0, -3.58)), # the middle of the gap (round_guide, round_modes[0], round_mesh, (24, 0), (0, 0))] def all_guides(region, plot=True, **_): """the electric field of each guide's lowest mode, 1 in its middle, for PlotField on the guides side by side""" points, values = [], [] for face, mode, mesh, (h, v), middle in places: (u0, v0), (u1, v1) = face.bounding_box gu, gv = np.meshgrid(np.arange(u0, u1 + 0.12, 0.12), np.arange(v0, v1 + 0.12, 0.12)) uv = np.c_[gu.ravel(), gv.ravel()] uv = uv[face.Distance(uv, signed=True) < -1e-3] strength = ngs.Norm(ngs.grad(mode)) points += [(a + h, b + v) for a, b in uv] values += list(np.asarray(strength(mesh(uv[:, 0], uv[:, 1]))).ravel() / strength(mesh(*middle))) return {"points": points, "values": np.array(values), "label": "|E|, 1 in the middle"} fig, ax = plt.subplots(figsize=(6.4, 3.4)) region = px.Glue([face.Translated(place) for face, mode, mesh, place, middle in places]) px.PlotField(all_guides, region, ax=ax, colormap=MAP.reversed(), min=0, max=1.2) save_figure(fig, "waveguide-cutoff", bare=True, bars={1: "|E|, 1 in the middle"}) ```