Skip to content

Visualization & Result Analysis

rfx ships matplotlib-based helpers for common RF post-processing: S-parameters, field slices, radiation patterns, RCS, and probe time series. Each helper takes plain arrays (result.s_params, result.freqs, result.time_series) or the result objects the analysis APIs return (FarFieldResult, RCSResult). For the most common plots, Result also exposes convenience methods — result.plot_s_params(), result.plot_smith(), and result.plot_time_series() — that forward to the functions below.

Use the S-matrix and frequency grid stored on the result:

from rfx import plot_s_params
fig = plot_s_params(result.s_params, result.freqs, db=True)

Pass the final field state together with the grid metadata:

from rfx import plot_field_slice
grid = result.grid
fig = plot_field_slice(
result.state,
grid,
component="ez",
axis="z",
index=grid.nz // 2,
title="Ez at the z-midplane",
)

First compute the far field, then plot it:

import numpy as np
from rfx import compute_far_field, plot_radiation_pattern
theta = np.linspace(0.0, np.pi, 181)
phi = np.array([0.0])
ff = compute_far_field(result.ntff_data, result.ntff_box, result.grid, theta, phi)
fig = plot_radiation_pattern(ff, freq_idx=0)

theta and phi are in radians. The example above uses a full elevation sweep with a single azimuth cut.

plot_rcs() takes the RCSResult returned by compute_rcs():

from rfx import plot_rcs
fig = plot_rcs(rcs_result, freq_idx=0, polar=True)

For how to produce rcs_result from a scattering run, see Far-Field & RCS.

Plot probe time series with the timestep used to record them:

from rfx import plot_time_series
fig = plot_time_series(result.time_series, result.dt, labels=["Probe 1"])

rfx results are NumPy or JAX arrays, so you can use standard Python analysis libraries directly.

import numpy as np
from scipy.signal import find_peaks
# Custom FFT analysis from time-domain data
ts = np.array(result.time_series[:, 0]) # first probe
spectrum = np.fft.rfft(ts)
freqs = np.fft.rfftfreq(len(ts), d=result.dt)
# Find resonant frequencies (peaks)
peaks, _ = find_peaks(np.abs(spectrum), height=np.max(np.abs(spectrum)) * 0.1)
print(f"Resonances at: {freqs[peaks] / 1e9} GHz")
import numpy as np
freqs = result.freqs
s11 = result.s_params[0, 0, :]
# Smith chart (impedance)
z_in = 50 * (1 + s11) / (1 - s11)
# Group delay
phase = np.unwrap(np.angle(result.s_params[1, 0, :]))
group_delay = -np.gradient(phase) / np.gradient(2 * np.pi * freqs)
# Return loss
return_loss_db = -20 * np.log10(np.abs(s11))
# VSWR
vswr = (1 + np.abs(s11)) / (1 - np.abs(s11))
import jax.numpy as jnp
EPS_0 = 8.8541878128e-12
MU_0 = 1.25663706212e-6
grid = result.grid
state = result.state
# Electric energy density
u_e = 0.5 * EPS_0 * (state.ex**2 + state.ey**2 + state.ez**2)
# Magnetic energy density
u_h = 0.5 * MU_0 * (state.hx**2 + state.hy**2 + state.hz**2)
# Approximate total stored energy on a uniform cubic grid
stored_energy = float(jnp.sum(u_e + u_h) * grid.dx**3)

This is an estimate: it uses the vacuum permittivity EPS_0 everywhere (no per-cell eps_r weighting, so it under-counts energy inside dielectrics) and treats the staggered Yee field components as if they were co-located. The final line also assumes a uniform cubic grid — for non-uniform spacing, integrate with the actual cell-volume weights instead of grid.dx**3.

from rfx import (
read_touchstone_full,
save_snapshots,
save_state,
write_touchstone,
)
# Legacy-compatible Touchstone for ADS/CST/HFSS. Shape is (n_ports, n_ports, n_freqs).
write_touchstone("device.s2p", result.s_params, result.freqs, z0=50.0)
# Metadata-rich Touchstone 2.0 export for a standard row-wise 4-port result
write_touchstone(
"device_v2.s4p",
four_port_result.s_params,
four_port_result.freqs,
version="2.0",
layout="standard",
port_z0=[50.0, 50.0, 50.0, 50.0],
information={"Project": "demo", "Tool": "rfx"},
)
network = read_touchstone_full("device_v2.s4p")
# HDF5 for the final field state
save_state("fields.h5", result.state, grid=result.grid)
# HDF5 for saved snapshots, when present
if result.snapshots is not None:
save_snapshots("snapshots.h5", result.snapshots, grid=result.grid, dt=result.dt)

Hand summaries to notebooks or reports, but retain the raw arrays and plots with the summary. A useful summary records the frequency grid, S-parameters, resonant peaks, bandwidth, return loss, and the exact pass/fail rule used to judge the result — state the metric rather than only that a design “looks good”.

Alongside exported plots or Touchstone files, keep a small machine-readable manifest with the command, git SHA, support status, and metric used to produce the figure, so the artifact can be reproduced.