Skip to content

rfx for AI Agents

rfx is a JAX-based differentiable 3D FDTD electromagnetic simulator designed with agent-friendliness as a first-class concern. This guide explains how LLM agents can use rfx to design and simulate RF structures.

Agents should treat the uniform Cartesian Yee RF workflow as the current claims-bearing reference lane. Non-uniform graded-z, distributed, and Floquet/Bloch workflows should be treated as shadow or experimental lanes unless the task explicitly requires them and the current support matrix allows the requested combination.


Every operation uses named arguments and string identifiers — no grid indices, no NamedTuples, no JAX internals required:

sim = Simulation(freq_max=6e9, domain=(0.06, 0.06, 0.025))
sim.add_material("substrate", eps_r=4.4, sigma=0.025)
sim.add(Box((0.005, 0.005, 0), (0.055, 0.055, 0.0016)), material="substrate")
sim.add_port((0.03, 0.03, 0.0016), "ez", impedance=50)
result = sim.run()

An agent can generate this code directly from a natural-language description of the structure.

rfx ships a MATERIAL_LIBRARY with curated RF materials. An agent only needs to pass the name string — no manual lookup of dielectric constants:

Nameeps_rsigma (S/m)Use case
"fr4"4.40.025PCB substrates
"rogers4003c"3.550.0027High-freq laminates
"alumina"9.80.0Ceramic substrates
"silicon"11.90.01MMIC substrates
"ptfe"2.10.0Low-loss laminates
"copper"1.05.8e7Conductors
"pec"1.01e10Ideal conductors
"vacuum"1.00.0Free space

The auto_configure() function derives all simulation parameters — cell size, domain margins, CPML layers, timestep count — from geometry and frequency range alone. An agent never needs to compute:

  • dx = lambda_min / 20 / sqrt(eps_r)
  • n_steps = ceil((T_source + T_ringdown) / dt)
  • CPML layer counts
  • Domain padding distances

All add_* methods return self, enabling compact pipelines:

result = (
Simulation(freq_max=5e9, domain=(0.04, 0.04, 0.02))
.add_material("sub", eps_r=3.55, sigma=1e-4)
.add(Box((0, 0, 0), (0.04, 0.04, 0.002)), material="sub")
.add(Box((0.01, 0.01, 0.002), (0.03, 0.03, 0.002)), material="pec")
.add_port((0.02, 0.02, 0.002), "ez", impedance=50)
.run(n_steps=4000)
)

Taskrfx SupportKey API
Patch antenna resonanceFulladd_source(), result.find_resonances()
S-parameter extractionFulladd_port(), result.s_params
Filter bandwidth optimizationFullDesignRegion, optimize()
Far-field radiation patternFulladd_ntff_box(), compute_far_field()
RCS calculationFullcompute_rcs()
Waveguide S-matrixFulladd_waveguide_port()
Convergence study (dx sweep)Fullauto_configure(accuracy=...)
Cross-validation vs MeepFullread_touchstone(), write_touchstone()
Dispersive materials (Debye)FullDebyePole, add_material(debye_poles=...)
Thin conductors (subcell)Fulladd_thin_conductor()
Inverse design (gradient)FullDesignRegion, JAX grad
Oblique incidence scatteringPartialadd_tfsf_source(angle_deg=...)
Eigenmode expansionOptionalsolve_waveguide_modes() (requires scipy)

For thin-substrate graded-z workflows, prefer to treat them as a shadow-qualification path rather than the default first choice.


This is the canonical pattern an agent should generate for a first simulation of any planar structure:

import rfx
from rfx import Simulation, Box, auto_configure
# 1. Describe geometry
geometry = [
(Box((0, 0, 0), (0.06, 0.06, 0.0016)), "fr4"),
(Box((0.01, 0.01, 0.0016), (0.05, 0.05, 0.0016)), "pec"),
]
# 2. Auto-configure all simulation parameters
config = auto_configure(geometry, freq_range=(1e9, 4e9), accuracy="standard")
print(config.summary()) # shows dx, domain, n_steps, warnings
# 3. Build simulation
sim = Simulation(**config.to_sim_kwargs())
for shape, mat in geometry:
sim.add(shape, material=mat)
sim.add_source((0.03, 0.03, 0.0016), "ez")
sim.add_probe((0.03, 0.03, 0.0016), "ez")
# 4. Run and extract resonances
result = sim.run(n_steps=config.n_steps)
modes = result.find_resonances(freq_range=(1e9, 4e9))
for m in modes:
print(f" f = {m.freq/1e9:.3f} GHz, Q = {m.Q:.1f}")

Pattern 1: Function Calling (Structured Output)

Section titled “Pattern 1: Function Calling (Structured Output)”

Use rfx as a tool backend. The agent receives structured parameters and generates runnable Python:

{
"tool": "rfx_simulate",
"parameters": {
"substrate": "fr4",
"patch_length_mm": 38.0,
"patch_width_mm": 29.0,
"substrate_thickness_mm": 1.6,
"freq_range_ghz": [1.0, 4.0],
"accuracy": "standard"
}
}

The tool wrapper translates this directly to auto_configure() + Simulation.

The agent generates complete Python scripts. rfx’s API is designed to be readable from code output alone — no hidden state, no implicit configuration. A typical generated script:

  1. Import rfx and geometry primitives
  2. Define geometry as a list of (Box(...), "material_name") tuples
  3. Call auto_configure() to get SimConfig
  4. Build Simulation(**config.to_sim_kwargs())
  5. Register geometry, sources, probes
  6. Call result = sim.run(n_steps=config.n_steps)
  7. Extract and report results
Agent: generate initial design
-> simulate (accuracy="draft")
-> if resonance found: refine design
-> simulate (accuracy="standard")
-> if error > threshold: escalate accuracy="high"
-> report final result

Use accuracy="draft" (10 cells/lambda) for rapid exploration and accuracy="high" (40 cells/lambda) only for final verification. This reduces compute by 8-64x during search.

rfx simulations are pure functions. An agent can dispatch multiple simulations in parallel by varying one parameter at a time:

import concurrent.futures
def simulate_patch(length_mm):
geometry = [(Box(...), "fr4"), ...] # vary length_mm
config = auto_configure(geometry, freq_range=(1e9, 4e9))
sim = Simulation(**config.to_sim_kwargs())
# ... add geometry, source, probe
result = sim.run(n_steps=config.n_steps)
modes = result.find_resonances(freq_range=(1e9, 4e9))
return length_mm, modes
lengths = [30, 32, 34, 36, 38, 40] # mm
with concurrent.futures.ProcessPoolExecutor() as pool:
results = list(pool.map(simulate_patch, lengths))

!!! warning “Common Agent Mistakes”

  • Do not set dx manually unless you understand the geometry. Use auto_configure() and let it select dx from wavelength and feature analysis.
  • Do not use add_port() for resonance characterization. The 50 ohm load damps high-Q cavities. Use add_source() + result.find_resonances() instead.
  • Do not set n_steps arbitrarily. Use config.n_steps from auto_configure() or until_decay=1e-4 for unknown Q structures.
  • Do not use "copper" material for thick conductor volumes. Use "pec" for ideal conductors or add_thin_conductor() for thin metal sheets with subcell correction.
  • Do not skip config.warnings. Warnings about thin features or high step counts indicate the geometry needs attention before running.

result = sim.run(...)
result.time_series # (n_steps, n_probes) -- raw field recordings
result.s_params # (n_ports, n_ports, n_freqs) complex -- S-matrix
result.freqs # (n_freqs,) Hz -- S-param frequency axis
result.state # final FDTD field state (for visualization)
result.ntff_data # raw NTFF DFT data (use compute_far_field())
result.snapshots # dict of field snapshots by component name
result.waveguide_sparams # dict[port_name, WaveguideSParamResult]
# Resonance extraction (Harminv)
modes = result.find_resonances(freq_range=(f_min, f_max))
modes[0].freq # resonant frequency (Hz)
modes[0].Q # quality factor
modes[0].decay_rate # field decay rate (s^-1)
modes[0].amplitude # complex amplitude