REMI Lab
OPEN KNOWLEDGE / TOOLS

Hollow rectangular waveguide (WR-90, TE10)

이 페이지의 내용

A rectangular waveguide is a hollow metal pipe that carries one electromagnetic mode at a time above a cutoff frequency. This case drives the dominant TE10 mode down a standard WR-90 guide (X-band) and reads the two-port S-parameters. An ideal empty guide should pass the mode with almost no reflection — and because the matched empty-guide answer is known in closed form, it is a clean absolute check on the waveguide port.

The structure

The hollow rectangular waveguide

A WR-90 guide: broad wall a = 22.86 mm, narrow wall b = 10.16 mm, metal (PEC) walls, hollow air inside. The mode is launched at the left port and collected at the right port. Both end faces are absorbing (CPML); the four side walls are PEC.

What this is

The dominant mode of a rectangular guide is TE10. It only propagates above the cutoff frequency set by the broad-wall width: f_c = c / (2·a). For WR-90, a = 22.86 mm gives f_c ≈ 6.557 GHz, so the X-band (8.2–12.4 GHz) sits comfortably above cutoff and the TE10 mode travels cleanly. Below f_c the mode is evanescent and the guide rejects it. Filling the guide with a dielectric εr lowers the cutoff to f_c,d = f_c / √εr — the lever the autodiff section below differentiates through.

What you’ll learn

  • Launching the TE10 dominant mode with a mode-matched waveguide port (a one-sided mode injection, not a soft point source).
  • The cutoff relation f_c = c/(2·a) and why X-band propagates in WR-90 while lower frequencies would not.
  • Reading two-port |S11| (reflection) and |S21| (transmission) against the exact matched empty-guide answer (|S11| = 0, |S21| = 1).
  • Taking a gradient through the FDTD solver with jax.grad: how |S21| responds to the guide’s dielectric fill, validated against finite differences.

Geometry & materials

import numpy as np
C0 = 299_792_458.0
a_wg = 22.86e-3 # WR-90 broad wall (sets the TE10 cutoff)
b_wg = 10.16e-3 # WR-90 narrow wall
f_c = C0 / (2 * a_wg) # TE10 cutoff: c / (2a) -> 6.557 GHz
dx = 1.0e-3 # 1 mm cells -> ~30 cells/wavelength at 10 GHz
freqs = np.linspace(8.2e9, 12.4e9, 21) # X-band sweep

The guide is hollow air inside PEC walls. The only material knob is the optional dielectric fill used in the gradient demonstration below.

Source, ports, boundaries, mesh

  • Two TE10 waveguide ports. The left port (direction="+x") launches the mode; the right port (direction="-x") acts as a matched load. Each port injects the discrete Yee-grid mode profile, so the launcher back-reflection is minimal.
  • CPML on the propagation axis only (20 cells). Twenty layers keep the absorber’s residual reflection of the guided mode to essentially zero (|S11| ≈ 0); a thinner absorber leaves a larger floor. Only the two x faces absorb.
  • PEC side walls. The four y/z walls are PEC — that is what defines a rectangular guide and its cutoff.
  • Uniform 1 mm mesh. Ten cells span the narrow wall, about 23 the broad wall, and roughly 30 cells per wavelength at the top of the band — enough to resolve the TE10 half-sine across a and keep numerical dispersion small.

Geometry check (before running)

The render above confirms the guide before committing compute: broad wall a, narrow wall b, the two port planes set back from the absorbers, and clearance to the CPML on both ends. An empty WR-90 run at 1 mm (about 46k cells, a few hundred steps) finishes in seconds on a CPU; peak memory is a few tens of megabytes.

Run

import numpy as np, jax.numpy as jnp
from rfx.api import Simulation
from rfx.boundaries.spec import Boundary, BoundarySpec
a_wg, b_wg, dx = 22.86e-3, 10.16e-3, 1.0e-3
freqs = np.linspace(8.2e9, 12.4e9, 21)
f0 = float(freqs.mean()); bandwidth = 0.5
domain_x = 0.200
sim = Simulation(
freq_max=float(freqs[-1]) * 1.1,
domain=(domain_x, a_wg, b_wg), dx=dx,
boundary=BoundarySpec(x=Boundary(lo="cpml", hi="cpml"),
y=Boundary(lo="pec", hi="pec"),
z=Boundary(lo="pec", hi="pec")),
cpml_layers=20,
)
port_freqs = jnp.asarray(freqs)
sim.add_waveguide_port(0.040, direction="+x", mode=(1, 0), mode_type="TE",
freqs=port_freqs, f0=f0, bandwidth=bandwidth,
waveform="modulated_gaussian", reference_plane=0.050, name="left")
sim.add_waveguide_port(domain_x - 0.040, direction="-x", mode=(1, 0), mode_type="TE",
freqs=port_freqs, f0=f0, bandwidth=bandwidth,
waveform="modulated_gaussian", reference_plane=domain_x - 0.050, name="right")
result = sim.compute_waveguide_s_matrix(num_periods=200, normalize=True)

Run it from the command line:

Terminal window
python scripts/precompute_gallery_artifacts.py --case waveguide_wr90

Fields

E_z on the broad-wall mid-plane of a source-fed empty guide, captured once the pulse has propagated to mid-guide. The field is a half-sine across the broad wall — peaking at the centre and vanishing at the PEC side walls — travelling in the +x direction, with the next half-cycle of opposite sign following behind. That is the TE10 mode moving down the guide.

TE10 mode E_z on the broad-wall mid-plane of the WR-90 guide

The same mode in motion: the TE10 wave packet propagates in the +x direction down the guide and into the matched absorber, holding its half-sine profile across the broad wall as it travels.

Animation: the TE10 mode travelling down the WR-90 guide

Result

Across the X-band |S21| sits at about 0 dB and max|S11| = 0.0000 with the 20-layer CPML. An empty matched guide above cutoff should pass the TE10 mode with no reflection, and it does.

Reflection and transmission vs frequency for the empty WR-90 guide

Validation & limits

The empty matched guide has an exact answer: |S11| = 0 and |S21| = 1 at every frequency above cutoff. The figure overlays the rfx values on that reference.

ObservablerfxReferenceTolerancePass
max|S11|0.00000 (analytic matched-load)< 0.02yes
min|S21|1.00001 (analytic matched-load)> 0.97yes

|S11| and |S21| vs the exact matched empty-guide answer

Reference: analytic matched-load (|S11| = 0, |S21| = 1 above cutoff), cross-checked against MEEP/OpenEMS/Palace.

Limit — absorber floor. The residual |S11| is CPML back-reflection, not a solver error. The 20-layer absorber holds it at the ≈0 published here; a thinner absorber raises the floor. That floor tracks the absorber thickness, not the guide’s reflection. These assets predate the 2.0 absorber correction, which lowered the CPML’s reflection at every layer count.

Autodiff

Fill part of the guide with a dielectric and |S21| responds, and that response is differentiable. The design variable is the fill permittivity εr, entered through eps_override, and the scalar is |S21|². A single jax.value_and_grad call returns ∂|S21|²/∂εr straight through compute_waveguide_s_matrix( normalize="flux", ...) — the power-flux extraction is on the AD tape, so the gradient flows through the full modal S-matrix assembly.

There is no closed form for the filled-guide |S21|, so the AD gradient is compared against central finite differences (h = 0.05), asking for agreement within 5% and a matching sign.

CheckAD valueCompared againstrel. errorSign agrees
∂|S21|²/∂εr−0.2201central FD −0.2197 (h = 0.05)0.0017 (0.17%)yes

The gradient agrees to 0.17% with matching sign, inside the 5% this check asks for. It checks the differentiation of this one computation, not the whole waveguide family. The waveguide S-matrix is limited in the support matrix: its gradient checks were closed with 64-bit JAX on the flux lane, and this 0.17% is a float32 gradient, which sits outside that closure.

Transmission sensitivity to the dielectric fill — AD gradient vs central finite differences

The sign is physical: a dielectric fill lowers the cutoff toward the band through f_c,d = f_c / √εr (so ∂f_c,d/∂εr = −f_c / (2·εr^{3/2}), about −1.8 GHz), which moves the band edge and reduces transmission. The closed-form cutoff relation points the same way the solver gradient does.

Limits. The broad-wall width a is not a jax.grad input: a/dx is a discrete cell count and the analytic mode profile is rebuilt for each width, so its sensitivity comes from re-meshing and finite differences, alongside the analytic cutoff relation f_c = c/(2·a) — not from automatic differentiation.

The differentiable lanes are normalize=False and normalize="flux". normalize=True is the host-assembled two-run lane and is outside the differentiable chain: under jax.grad/jax.jit, a traced eps_override/sigma_override makes compute_waveguide_s_matrix raise NotImplementedError before any FDTD run. The Run block above passes a concrete normalize=True and runs forward; the same call under jax.grad raises.

Next

Computed with rfx 1.6.5 · 1eb551b · generated 2026-06-30T03:22:31Z

Back to top ↑