Far-Field & Radar Cross Section
rfx computes far-field radiation patterns from a near-to-far-field transform (NTFF), and radar cross section (RCS) by combining TFSF plane-wave illumination with the same NTFF integration.
Radiation Pattern (NTFF)
Section titled “Radiation Pattern (NTFF)”An NTFF box records the tangential E and H fields on a closed Huygens surface during the time stepping. After the run, those surface fields are converted to equivalent currents and integrated to the far field — so the pattern comes from the recorded surface, not from probes sitting in the radiating near field.
from rfx import GaussianPulse, Simulation, compute_far_field, radiation_patternimport numpy as np
sim = Simulation( freq_max=5e9, domain=(0.20, 0.20, 0.20), boundary="cpml", dx=4e-3, cpml_layers=8,)sim.add_source( (0.10, 0.10, 0.10), "ez", waveform=GaussianPulse(f0=3e9, bandwidth=0.5),)
# NTFF box: encloses the radiator but stays inside the simulation region,# clear of the CPML absorber.sim.add_ntff_box( corner_lo=(0.045, 0.045, 0.045), corner_hi=(0.155, 0.155, 0.155), freqs=np.array([3e9]),)
# Inspect coded placement/support advisories and fail on blocking errors.preflight = sim.preflight()print(preflight.format())if (preflight.by_code("absorber_overlap") or preflight.by_code("ntff_near_field")): raise RuntimeError("move the NTFF box before running")preflight.raise_for_failure()
result = sim.run(n_steps=1000)
# Far field over a (theta, phi) grid. run() stores the raw NTFF data,# the box spec, and the grid on the result.theta = np.linspace(0, np.pi, 181)phi = np.linspace(0, 2 * np.pi, 360)ff = compute_far_field(result.ntff_data, result.ntff_box, result.grid, theta, phi)
# Normalized pattern in dB (peak = 0 dB), shape (n_freqs, n_theta, n_phi).pattern_dB = radiation_pattern(ff)compute_far_field returns a FarFieldResult (fields E_theta, E_phi,
theta, phi, freqs) that both radiation_pattern and directivity
consume.
Directivity
Section titled “Directivity”from rfx import directivity
D = directivity(ff) # (n_freqs,) in dBiprint(f"Directivity: {D[0]:.1f} dBi")directivity integrates radiated power over the whole sphere (D = 4π·U_max /
P_rad), so ff must be sampled over the full range — theta in [0, π] and
phi in [0, 2π], as in the block above. A single cut plane yields the wrong
P_rad.
For inverse-design objectives that change total radiated power, use
maximize_directivity(theta_target, phi_target, log_ratio=True) so the gradient
follows the full directivity ratio instead of a stopped-power proxy. See the
Inverse Design guide.
Radar Cross Section (RCS)
Section titled “Radar Cross Section (RCS)”compute_rcs illuminates a target with a TFSF plane wave, captures the
scattered field (the field outside the TFSF box) on an NTFF box, and reports
RCS(θ, φ) = 4π r² |E_scat|² / |E_inc|². It is a functional API: you pass a
Grid and a MaterialArrays holding the scatterer, not a Simulation.
import numpy as npimport jax.numpy as jnpfrom rfx import Grid, Box, compute_rcsfrom rfx.geometry.csg import rasterizefrom rfx.core.yee import MaterialArrays
f0 = 3e9 # illumination centre frequency (lambda = 0.1 m)dx = 0.01 # ~lambda/10 at 3 GHz; leaves room for scatterer + TFSF + NTFF + CPMLgrid = Grid( freq_max=f0 * 1.5, domain=(0.12, 0.12, 0.12), dx=dx, cpml_layers=8,)
# PEC plate: 4 cm square, one cell thick, centred and normal to x.c = 0.06plate = Box( corner_lo=(c - dx / 2, c - 0.02, c - 0.02), corner_hi=(c + dx / 2, c + 0.02, c + 0.02),)# rasterize takes (shape, eps_r, sigma) tuples; sigma = 1e7 S/m approximates PEC.eps_r, sigma = rasterize(grid, [(plate, 1.0, 1e7)])materials = MaterialArrays( eps_r=eps_r, sigma=sigma, mu_r=jnp.ones(grid.shape, dtype=jnp.float32),)
result = compute_rcs( grid, materials, n_steps=400, f0=f0, bandwidth=0.5, # fractional bandwidth of the Gaussian excitation polarization="ez", theta_obs=np.linspace(0.01, np.pi - 0.01, 91), phi_obs=np.array([0.0, np.pi / 2]), freqs=np.array([f0]), # Two-run complex subtraction is required for the validated bistatic path. subtract_incident_reference=True,)
# monostatic_rcs is evaluated exactly at the backscatter direction# (theta=pi/2, phi=pi for the +x incidence), independent of theta_obs/phi_obs.print(f"Monostatic RCS: {result.monostatic_rcs[0]:.1f} dBsm")print(f"Bistatic RCS range: {result.rcs_dbsm.min():.1f} to {result.rcs_dbsm.max():.1f} dBsm")RCSResult carries rcs_dbsm and rcs_linear (shape (n_freqs, n_theta, n_phi)), monostatic_rcs ((n_freqs,) dBsm, always populated — evaluated
exactly at the backscatter direction opposite the incident propagation
vector, so it does not depend on the observation grid), and the
freqs / theta / phi axes.
Validation scope: raw backscatter and reference-subtracted bistatic RCS
Section titled “Validation scope: raw backscatter and reference-subtracted bistatic RCS”monostatic_rcs is always evaluated from the unsubtracted run at the exact
backscatter direction. It agrees with the exact Mie series to about 0.06 dB for
the committed ka ≈ 1 PEC-sphere case.
With the default subtract_incident_reference=False, the full
rcs_dbsm / rcs_linear bistatic pattern is not validated. An empty-domain
run produces the same forward-oblique lobe as the target run, showing that the
dominant raw-pattern lobe is target-independent leakage from the discrete TFSF
boundary, not a scatterer staircase effect. Increasing ntff_offset alone does
not remove that leakage. Use the default path for the validated monostatic
quantity; treat its off-backscatter bins as qualitative.
For bistatic work, set subtract_incident_reference=True. rfx then performs a
second vacuum run with the same TFSF and NTFF setup and subtracts the complex
far fields before forming RCS. This doubles the solve cost. In the committed
ka ≈ 1 PEC-sphere H-plane comparison against exact Mie, subtraction reduces the
largest 15–90° forward-oblique difference from 10.5 dB to 1.2 dB, gives a
full-pattern dB correlation of 0.965 and a mean absolute difference of 0.42 dB,
and leaves the backscatter difference at about 0.06 dB. That evidence covers
the stated sphere, frequency, polarization, angle cut, and discretization; it
does not validate every target or setup. An independent Bempp cube fixture
confirms the raw forward-oblique discrepancy on a second geometry, but does not
validate a reference-subtracted cube pattern.
After subtraction, remaining error can come from curved-surface staircasing, deep-pattern-null sensitivity, NTFF placement, and CPML reflection. For a new target, repeat with finer cells and a longer run, increase the domain until the NTFF surface-to-target distance is adequate for the angles of interest, vary the NTFF placement and CPML thickness, and compare with an analytic or independent reference. The plate example above computes a corrected pattern; the sphere evidence does not by itself validate the plate values.
Plotting RCS
Section titled “Plotting RCS”from rfx import plot_rcs
plot_rcs(result, freq_idx=0, polar=True) # polar cutplot_rcs(result, freq_idx=0, polar=False) # dBsm vs angleplot_rcs selects one frequency (freq_idx) and one φ cut (phi_idx, default
0). For the other plotting helpers see
Visualization & Result Analysis.
- Keep all six NTFF faces outside sources, scatterers, radiators, and CPML.
sim.preflight()warns when a box enters the absorber. Itsntff_near_fieldcheck measures face-normal clearance to tangentially overlapping geometry boxes or point entries created byadd_source()and lumped/wireadd_port(). It warns below λ/2 atf_maxand more strongly below λ/4. This is a placement heuristic, not a nearest-3-D-distance or Huygens-validity test. TFSF boundaries and MSL source positions are not included and require manual clearance checks. Finite geometry such as PEC sheets is included only where its bounding box overlaps the NTFF face tangentially. - The
ntff_small_ground_planeadvisory fires when an NTFF box is present and the largest PEC sheet backed by anadd_source()/ lumped-wireadd_port()entry is under ~1λ across at the highest requested NTFF frequency. A sub-wavelength ground plane radiates a pattern shaped by ground-plane edge diffraction (broadside dip, off-axis side peaks) — that is expected physics, not a solver defect. Such a fixture stays valid for resonance and impedance work; for a clean broadside pattern use a ground plane of at least ~1.4λ. Deliberately small ground planes are legitimate: the advisory is a warning, never a blocker, so interpret the pattern accordingly and continue. TFSF-illuminated plates (RCS targets) do not trigger it. - The automatic
ring-down truncatedwarning examines a recorded probe time series, not the NTFF accumulators. It is emitted only for an absorbing-boundary run with aGaussianPulseentry fromadd_source(), lumped/wireadd_port(), oradd_msl_port(), automaticnum_periods, and a probe series. TFSF excitation, non-GaussianPulsesource entries, and the explicit-n_stepsexample above receive no automatic tail verdict. Repeat the calculation with a longer duration and compare the pattern, or useuntil_decayon the supported uniform or non-uniform CPML/UPML runners; neither method replaces observable-specific convergence testing. compute_rcsplaces the TFSF and NTFF boxes automatically fromcpml_layers,tfsf_margin, andntff_offset(the NTFF box sitsntff_offsetcells outside the TFSF boundary); you supply only the grid and the scatterer materials.- Report the incident direction, polarization, observation angles, and frequency band alongside any published RCS number.