Skip to content

Migration Guide

This guide helps users coming from Meep or OpenEMS translate their workflows into rfx. rfx follows the same Yee-cell FDTD physics, so the core concepts are familiar — the API surface is different.


ConceptMeepOpenEMSrfx
Grid setupSimulation(resolution=N)InitCSX() + InitFDTD()Simulation(freq_max=...) or Simulation.auto(...)
Cell sizeresolution (cells/unit)SetDeltaUnit(1e-3)dx= in metres (auto-calculated from freq_max)
SourceEigenModeSource, SourceAddExcitationadd_port(), add_source()
S-parametersadd_flux() + post-processingCalcPortcompute_s_params=True in run()
Resonance findingharminv(...)Manual FFTresult.find_resonances()
Auto-stopstop_when_fields_decayedEndCriteriarun(until_decay=1e-3)
MaterialsMedium(epsilon=...)AddMaterialsim.add(shape, material="fr4") or sim.add_material(...)
PECPerfectElectricalConductorAddMetalmaterial="pec"
PML / ABCPML(thickness)AddPMLboundary="cpml"
Dispersive mediaLorentzianSusceptibilityAddLorentzMaterialDebyePole, LorentzPole, drude_pole()
DifferentiableNot availableNot availablejax.grad(loss_fn)(params)
Inverse designNot native (adjoint plugin)Not nativerfx.optimize(sim, design_region, objective)
SubgriddingNot availableNot availableexperimental / specialized
Non-uniform meshNot nativeSmoothMeshLinesdz_profile or auto_configure()

# Meep
import meep as mp
sim = mp.Simulation(
cell_size=mp.Vector3(0.1, 0.1, 0.05),
resolution=50,
boundary_layers=[],
)
sim.sources = [mp.Source(
mp.GaussianSource(frequency=2.0, fwidth=0.5),
component=mp.Ez,
center=mp.Vector3(0.03, 0.03, 0.02),
)]
sim.run(mp.at_beginning(mp.output_epsilon),
until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, mp.Vector3(), 1e-6))
# rfx equivalent
from rfx import Simulation
sim = Simulation(freq_max=5e9, domain=(0.1, 0.1, 0.05), boundary="pec")
sim.add_source(position=(0.03, 0.03, 0.02), component="ez")
sim.add_probe(position=(0.06, 0.06, 0.02), component="ez")
result = sim.run(until_decay=1e-3)
freqs = result.find_resonances()
% OpenEMS (MATLAB)
CSX = InitCSX();
FDTD = InitFDTD('EndCriteria', 1e-5);
CSX = AddMetal(CSX, 'PEC');
CSX = AddBox(CSX, 'PEC', 10, [0 0 0], [40 20 10]);
[CSX, port{1}] = AddRectWaveGuidePort(CSX, 0, 1, ...);
RunOpenEMS(Sim_Path, Sim_CSX, '--engine=multithreaded');
port = calcPort(port, Sim_Path, freq);
s11 = port{1}.uf.ref ./ port{1}.uf.inc;
# rfx equivalent
from rfx import Simulation, Box
sim = Simulation(freq_max=15e9, domain=(0.04, 0.02, 0.01), boundary="cpml")
sim.add(Box((0, 0, 0), (0.04, 0.02, 0.01)), material="pec")
sim.add_port(
position=(0.005, 0.01, 0.005),
component="ez",
waveform=GaussianPulse(f0=10e9),
)
result = sim.run(until_decay=1e-5, compute_s_params=True)
s11 = result.s_params[0, 0, :]
# Meep (requires meep.adjoint plugin)
opt = mpa.OptimizationProblem(...)
opt.update_design([design_params])
f, g = opt() # forward + adjoint
# rfx (native JAX autodiff)
import jax
def loss(eps_r):
sim = Simulation(freq_max=8e9, domain=(0.04, 0.01, 0.01), boundary="cpml")
sim.add_port(position=(0.008, 0.005, 0.005), component="ez")
result = sim.run(n_steps=150, compute_s_params=True, eps_r_override=eps_r)
return -jnp.abs(result.s_params[0, 0, 50])
grad_fn = jax.grad(loss)
gradient = grad_fn(initial_eps_r)

rfx runs on GPU out of the box via JAX. No separate engine binary, no file-based I/O between the Python front-end and a C++ solver. The full time-stepping loop is JIT-compiled and executes on GPU (~3,000 Mcells/s on RTX 4090).

Every rfx simulation is differentiable with jax.grad. Gradients propagate through the entire FDTD time-stepping, sources, probes, and post-processing. This enables gradient-based inverse design without adjoint plugins or finite-difference approximations.

rfx uses a single Simulation object that accumulates geometry, materials, sources, and probes. Call run() once — S-parameters, resonances, and field snapshots are computed automatically based on what was requested.

Eleven common RF materials (fr4, rogers4003c, alumina, silicon, copper, pec, etc.) are available by name. No need to look up permittivity tables for standard substrates and conductors.

Simulation(freq_max=N) automatically sets cell size, time step, CPML thickness, and source waveform. For most antenna and waveguide problems you only need to specify freq_max and domain.

For PCB and patch-style problems, the most practical recent rfx workflow is graded z meshing via dz_profile / auto_configure(), rather than assuming one globally fine uniform mesh.


IssueSolution
Coordinates are in metres, not mm or cell unitsAll positions use SI metres
freq_max is in Hz, not normalized frequencyUse freq_max=5e9 for 5 GHz
PML is outside the domain you specifydomain= is the physical region; CPML pads are added automatically
run() returns a Result objectAccess fields via result.s_params, result.time_series, result.find_resonances()
No mesh file exportrfx solves in-process; use sim.grid to inspect the mesh