REMI Lab
OPEN KNOWLEDGE / TOOLS

Core Concepts

이 페이지의 내용

rfx is a finite-difference time-domain (FDTD) solver. It divides space into small rectangular cells, steps Maxwell’s equations forward in time, and records what you ask for. This page explains the choices a model makes and what each one does to your result. Read it once before you build your own device.

The grid and the time step

rfx uses the standard Yee lattice. Each field component lives at its own position in the cell. E components sit on cell edges and H components sit on cell faces. You rarely need the exact positions, but they explain two things you will see:

  • Geometry snaps to the lattice. A face drawn between two grid planes lands on the nearest plane. sim.fidelity_report() prints the declared and realized position of every face.
  • A probe or port reads a field at a grid location, which may be up to half a cell away from the coordinate you gave.

Cell size. The cell must resolve the shortest wavelength inside the material, λ_eff = c / (f_max √ε_r). Aim for at least 15–20 cells per λ_eff for propagation, and more for anything phase-sensitive. Preflight warns when a dielectric has fewer than 15. The thinnest feature that matters also needs a few cells, such as a substrate or a gap. On a uniform grid, those two requirements together set dx. When they conflict, use a graded mesh; see Non-Uniform Mesh.

Time step. rfx picks the largest stable step from the cell size (the Courant limit). You normally leave dt alone. A run of N steps covers N * result.dt seconds.

Cost. Memory and time per step scale with the number of cells. Halving dx in 3-D gives 8× the cells and about 2× the steps, so roughly 16× the work. Start coarse, then refine.

Boundaries

The boundary decides what happens at the edge of the domain.

BoundaryUse it forNotes
"cpml"open problems: antennas, scattering, lines that leave the boxan absorber cpml_layers cells thick (default 16), added outside domain
"pec"closed problems: cavities, shielded boxesenergy cannot leave, so resonances ring indefinitely
"pmc" (via BoundarySpec)a symmetry plane, which halves the problemonly where the fields really are symmetric
"periodic" (via BoundarySpec)infinite arrays and unit cellsthe paired faces go together
"upml"legacy absorberreflects noticeably more than CPML; prefer CPML

A single string ("cpml", "pec" or "upml") applies to all six faces. Use BoundarySpec to set each face separately, and for "pmc" and "periodic":

from rfx import Simulation
from rfx.boundaries.spec import Boundary, BoundarySpec
# Open on all sides except an infinite PEC ground at z = 0.
spec = BoundarySpec(x="cpml", y="cpml", z=Boundary(lo="pec", hi="cpml"))
sim = Simulation(freq_max=10e9, domain=(0.02, 0.02, 0.01), dx=0.5e-3,
boundary=spec, cpml_layers=8)
print(spec.to_dict())

Don’t mix roles. A cavity with one absorbing wall is neither a cavity nor an antenna, and its Q means nothing. A PEC boundary is also infinite in its own plane. For a finite ground plane, draw a metal sheet inside an all-CPML domain instead, as described next.

Materials

A material has a relative permittivity eps_r, a conductivity sigma (S/m) and optionally a permeability mu_r. Frequency-dependent materials use Debye or Lorentz poles. rfx ships a small library that includes "fr4", "rogers4350b", "ptfe", "copper" and "pec". See Materials and Geometry.

Any material with sigma >= 1e6 S/m is treated as a perfect conductor. Its tangential E is forced to zero. The finite conductivity is not used, so library copper behaves as lossless PEC. For conductor loss on a metal sheet, use add_thin_conductor(..., surface_impedance_f0=f0).

Three kinds of conductor

Metal inside the domain is always exactly one of three things. Your declaration decides which, not the shape’s size on the mesh:

KindReal-world objectDeclare it withWhat the solver builds
sheetetched copper: ground plane, patch, tracesim.add_thin_conductor(Box(...)) with zero thickness on one axisone grid plane with in-plane E zeroed; occupies no cell
volumeplate, iris, post, machined wallsim.add(shape, material="pec")walls at both drawn faces, interior shorted
wirethin filamentPolylineWire thinner than half a cella path of zeroed E edges
from rfx import Box
from rfx.mesh_edges import edge_aware_profiles
substrate = Box((0.005, 0.005, 0.004), (0.015, 0.015, 0.005))
foil = Box((0.005, 0.005, 0.004), (0.015, 0.015, 0.004)) # zero thickness in z
block = Box((0.007, 0.007, 0.006), (0.009, 0.009, 0.007))
# x and y mesh lines placed so the foil's solved edges fall on its drawn edges.
profiles = edge_aware_profiles((0.02, 0.02, 0.01), 0.5e-3,
sheets=[foil], solids=[substrate, block])
sim = Simulation(freq_max=10e9, domain=(0.02, 0.02, 0.01), dx=0.5e-3,
boundary="cpml", cpml_layers=8, **profiles)
sim.add_material("substrate", eps_r=3.66)
sim.add(substrate, material="substrate")
# 35 um copper foil on the substrate floor: a sheet.
sim.add_thin_conductor(foil)
# A 2 x 2 x 1 mm metal block: a volume.
sim.add(block, material="pec")
sim.fidelity_report() # declared vs realized planes for every entity

The first add_thin_conductor call warns that the sheet is lossless PEC. That warning is expected: see the materials note above.

A sheet is solved as if it reached about 0.35 of a cell past its last mesh node, so on a plain uniform grid this 10 mm foil is solved wider than drawn. Preflight reports a sheet solved more than 1 % off its drawing as an error, so run() and forward() stop before the first step. Build the in-plane mesh with rfx.mesh_edges.edge_aware_profiles, as above, so the solved edges fall on the drawn ones; or pass Simulation(..., snap="declared") to accept the difference as a warning, recorded in sim.realized_geometry(). With the graded in-plane mesh, preflight also notes that the grading reaches the absorber runway.

A PEC shape that is thinner than one cell but not zero is neither a sheet nor a resolved volume. Preflight reports it as an error and run() refuses it, so you must choose one or the other. The full rule, with the index conventions, is in How conductors land on the lattice.

Sources and ports

  • A source (add_source, add_tfsf_source, add_polarized_source) injects a field. It has no impedance and does not load the structure. Use it for fields, resonances, far-field patterns and scattering.
  • A port injects a signal and terminates the line in a known impedance, so rfx can compute S-parameters. Each port family has its own calculator, and only that calculator gives S-parameters for it:
StructurePortS-parameter calculator
discrete feed, lumped component terminalsadd_port(...) (lumped; extent= makes a wire port)run(compute_s_params=True) or forward(port_s11_freqs=...)
microstrip lineadd_msl_port(...)compute_msl_s_matrix()
rectangular waveguideadd_waveguide_port(...)compute_waveguide_s_matrix()
coaxial lineadd_coaxial_port(...)compute_coaxial_line_reflection(), compute_coaxial_two_port()

Probes, flux monitors and DFT planes record fields. They do not define a reference impedance or a reference plane, so they cannot produce S-parameters. Sources and Ports covers every family.

Observables

WantUse
a field value over time at a pointadd_probe, add_vector_probe
a field on a plane at chosen frequenciesadd_dft_plane_probe
power through a plane (R, T, absorption)add_flux_monitor
radiation pattern, directivity, RCSadd_ntff_box, then compute_far_field / compute_rcs
resonant frequencies and Qa probe plus result.find_resonances() (Harminv)
S-parametersa port family and its calculator (table above)

Is the run finished?

A pulse must ring down before its spectrum means anything. Every spectral quantity is a Fourier transform of a finite record: S-parameters, flux, far fields, DFT planes and resonances. If the record stops while energy is still bouncing around, the result is wrong in a way that no warning can fully catch.

  • Check result.settling_witness["status"]. For every frequency a spectral result reads, rfx estimates from the poles still ringing after the source ends how much of the value lies past the record’s end; settling_db is the worst share in dB, and at or below −40 dB the status is pass. fail, undetermined and absent (nothing recorded to judge) are not passes.
  • The S-parameter calculators warn when a drive’s record ends too early.
  • High-Q structures need long records. A resonator with quality factor Q decays over roughly Q / (π f) seconds.

Preflight and fidelity

Two checks run without a solve:

  • sim.preflight() returns a report of errors and warnings about the setup. Examples are cell size against wavelength, structures touching the absorber, far-field boxes too close to the radiator, graded-mesh problems and lossless resonators. run() repeats it automatically.
  • sim.fidelity_report() prints where each shape actually landed on the grid.

Neither check is an accuracy guarantee. A clean preflight means rfx found none of the mistakes it knows about. Accuracy comes from a convergence study and a comparison with a reference; see Convergence Study and Validation.

run() versus forward()

  • sim.run(...) is the general entry point. It returns a Result with probe time series, S-parameters, DFT planes, flux and far-field data.
  • sim.forward(...) is the differentiable entry point. Wrap it in jax.grad to get gradients with respect to material overrides or design variables. It supports a narrower set of features. See Autodiff and Adjoint and Inverse Design.

Precision

The default is precision="float32", which suits most work and is what the GPU runs fastest. "float64" needs JAX 64-bit mode enabled before you build the simulation. "mixed" stores fields in float16. Both are limited to the uniform, single-device solver. Mixed precision raises the CPML’s reflection floor, so avoid it for low-reflection or S-parameter work.

Back to top ↑