A bundle is one JSON file that describes one simulation: the model and its parameter values, the solver setup, the exact geometry, the mesh, the run statistics and the post-processed results. It is written by Simulation.to_bundle() (python/fairbeam/simulation.py) and read by the viewer (typed in src/types.ts) and the VBA macro exporter (src/export/cst.ts).
Files live in public/projects/<slug>.json. Each write also rebuilds public/projects/index.json (see Index file).
Conventions
| Quantity | Unit |
|---|---|
| Lengths and coordinates | drawing units, given by units.length / units.length_m. The default is mm (length_m = 0.001) |
| Frequencies | Hz everywhere in the bundle. The index file uses GHz |
Angles (theta, phi) |
degrees. θ is measured from +z, φ from +x toward +y |
| Directivity and gain | dBi |
| Power | W |
| Impedance and resistance | Ω |
| Time | time_ns in ns, dt_s in s |
Conductivity (kappa) |
S/m |
- Coordinates are
[x, y, z]triples (Vec3). Axis indices are0 = x,1 = y,2 = z. - Bounding boxes are
[[xmin, ymin, zmin], [xmax, ymax, zmax]]. - The JSON is written compactly with
allow_nan=False, so it never containsNaNorInfinity. - Files are written atomically: the writer creates
<slug>.json.tmpand then renames it. - Rounding: mesh lines have 4 decimals, geometry 6, S11 5, Zin 3, far-field angles and directivity 2.
Top level
| Field | Type | Description |
|---|---|---|
schema |
string | Always "fairbeam.project/1". Readers should check the fairbeam.project/ prefix |
generator |
object | See generator |
created |
string | Local time as %Y-%m-%dT%H:%M:%S%z, for example 2026-09-24T22:24:15+0300 (the offset has no colon) |
name |
string | Display name: the model name plus overrides, for example Sierpinski gasket monopole · iterations=3. fairbeam geometry appends (geometry only) |
model |
object | See model |
units |
object | See units |
solver |
object | See solver |
parts |
Part[] | Physical structure. See parts |
ports |
Port[] | See ports |
lumped_elements |
LumpedElement[]? | Lumped circuit elements that are not ports (e.g. an isolation resistor). Absent when there are none. See lumped_elements |
half_space |
object | null | Infinite PEC ground from a boundary. See Half space and mirror planes |
mesh |
object | See mesh |
domain |
{min: Vec3, max: Vec3} |
Simulation domain: the first and last mesh line on each axis |
nf2ff_box |
{min, max, faces?} | null |
Bounding box of the DumpBox helper primitives that are the NF2FF recording surfaces (Fairbeam's own field dumps, surface currents fairbeam_J_* and field planes fairbeam_F_*, are excluded). null if there are none. faces (6 booleans, x-, x+, y-, y+, z-, z+) is present when the model skipped faces with add_nf2ff_box(directions=…), e.g. the face a feed waveguide crosses; false = not recorded |
nf2ff_center |
Vec3 | null | Phase center used for the NF2FF transform. It is the value passed to add_nf2ff_box(center=…), else the center of focus, else the origin. null when the model has no NF2FF box |
focus |
{min, max} | null |
Region the viewer frames by default (Simulation.set_focus) |
run |
object | null | Solver statistics. null for geometry-only bundles. See run |
results |
object | null | Post-processed results. null for geometry-only bundles. See results |
fields |
object? | Optional surface-current maps (fairbeam run --fields). See fields |
field_planes |
FieldPlaneMap[]? | Optional E/H field maps on cut planes (fairbeam run --field-plane, a design's monitors.field_planes). See field_planes |
generator
| Field | Type | Description |
|---|---|---|
name |
string | "fairbeam" |
version |
string | Fairbeam package version |
openems |
string | null | openEMS.__version__ |
csxcad |
string | null | CSXCAD.__version__, if the module exposes it |
python |
string | Python version |
model
This is the model file's MODEL dict (id, name, description, and optionally reference) spread into the object, plus:
| Field | Type | Description |
|---|---|---|
params |
Param[] | One entry per PARAMS item, in declaration order |
Each Param is dataclasses.asdict(Param) plus the resolved value:
| Field | Type | Description |
|---|---|---|
key |
string | Parameter key used by --set |
default |
number | string | Default value |
value |
number | string | Value used for this run |
label |
string | Human-readable label |
unit |
string | Display unit, for example "mm" or "GHz", or "". This is the model's own unit, not the bundle's |
description |
string | Longer description (may be empty) |
minimum, maximum |
number | null | Allowed range |
units
| Field | Type | Description |
|---|---|---|
length |
string | "mm" when the drawing unit is 1e-3 m, otherwise "<unit> m" (for example "0.0001 m") |
length_m |
number | Drawing unit in meters |
frequency |
string | Always "Hz" |
solver
| Field | Type | Description |
|---|---|---|
engine |
string | "openEMS" |
method |
string | "FDTD (Yee, staircase)" |
excitation |
object | See below |
boundaries |
object | Keys x-, x+, y-, y+, z-, z+. Values are openEMS boundary names: "MUR", "PEC", "PMC", "PML_8", … |
end_criteria_db |
number | Energy end criterion in dB, for example -40 |
max_timesteps |
number | Timestep limit |
excitation has one of two shapes:
- Default
{"type": "gaussian-derivative", "f_min", "f_max", "expression", "dc_free": true}.expressionis the fparser formula int(seconds) passed to openEMS' custom excitation. The pulse has unit peak and is -20 dB atf_max. - openEMS Gaussian
{"type": "gaussian", "f0", "fc", "f_min", "f_max", "dc_free": false}, wheref0 = (f_min + f_max) / 2andfc = (f_max - f_min) / 2.
parts
Physical CSXCAD properties with their primitives, in order of first appearance. Helper properties (DumpBox, ProbeBox, Excitation) and the LumpedElement properties created by ports are excluded. Ports are listed in ports instead.
| Field | Type | Description |
|---|---|---|
name |
string | CSXCAD property name (unique) |
label |
string? | Display label from sim.metal(..., label=) or sim.dielectric(..., label=) |
type |
string | CSXCAD property type: "Metal", "Material", "ConductingSheet", … |
color |
string? | Optional color recorded by the model |
material |
object? | Present only for type == "Material". See below |
conductor |
object? | A metal of finite conductivity (a design material's conductivity, sim.metal(..., conductivity=)): conductivity in S/m and thickness, the modeled sheet thickness in mm of a "ConductingSheet" (openEMS AddConductingSheet), null for a volume, which is a "Material" of that conductivity and is still a metal. Absent for perfect conductors |
void |
boolean? | true for a vacuum carver (a Boolean Subtract's cut-out): it erases lower-priority material inside it; viewers should draw it as a ghost or hide it; absent otherwise; older readers show it as an ordinary eps_r 1 part |
primitives |
Primitive[] | Geometry, see below |
bbox |
[Vec3, Vec3] | Union of the primitives' bounding boxes |
material:
| Field | Type | Description |
|---|---|---|
eps_r |
number | Relative permittivity (x component if anisotropic) |
mu_r |
number | Relative permeability |
kappa |
number | Electric conductivity in S/m. openEMS models dielectric loss as a constant conductivity |
tan_d |
number | null | Loss tangent requested by the model (null if the property was created directly through CSXCAD) |
tan_d_freq |
number | null | Frequency in Hz at which kappa reproduces tan_d exactly (kappa = tan_d · 2π f · ε0 · eps_r). Defaults to the band center |
isotropic |
boolean | false if epsilon or kappa differ between axes |
dispersion |
object? | Optional. A frequency-dependent dielectric (Simulation.dispersive, fairbeam.dispersion). The CSXCAD property is then a LorentzMaterial, exported with type: "Material" so that viewers and exporters treat it as a dielectric. eps_r, mu_r and tan_d are its values at tan_d_freq, the band center, and kappa is the conductivity that gives its whole loss there (kappa = tan_d · 2π f · ε0 · eps_r, as for a constant material), so that a consumer that takes the loss as a conductivity keeps the band-center loss; the static conductivity is dispersion.kappa. Absent for constant materials, so existing bundles are unchanged |
material.dispersion is the model as simulated, with frequencies in Hz and times in s, in openEMS'
conventions (e^{+jωt}):
| Field | Type | Description |
|---|---|---|
model |
string | "lorentz": ε∞·[1 − Σ ωp²/(ω² − ωL² − jω/τ)], where Drude is ωL = 0. openEMS 0.37.0rc3's DebyeMaterial diverges above ΣΔε/ε∞ ≈ 0.3 in 3D (0.6 in 1D), so Debye poles and laminates are simulated as fitted Lorentz poles, and the bundle records those. "debye" (ε∞ + Σ Δε/(1 + jωτ)) appears only inside source, or for a DebyeMaterial a model created directly through CSXCAD |
eps_inf |
number | ε∞ |
kappa |
number | Static conductivity in S/m (adds −jκ/(ωε0)) |
eps_poles |
object[] | {"type": "lorentz", "f_plasma", "f_pole", "tau"} (in source also {"type": "debye", "delta", "tau"}) |
mu_inf, mu_poles |
number, object[] | Magnetic Lorentz/Drude dispersion. Present only when μ∞ ≠ 1 or there are poles |
source |
object? | The model the poles were fitted to, with the fit. For a Djordjevic-Sarkar laminate: {"model": "djordjevic-sarkar", eps_inf, delta, m1, m2, fit: {f_min, f_max, span}, report: {...}}. For Debye poles: the requested Dispersion ("model": "debye", its poles), with fit and report. The report holds the fit errors in the band and the timestep dt the poles were bounded by |
Primitives
Every primitive has these fields:
| Field | Type | Description |
|---|---|---|
kind |
string | "box", "polygon", "linpoly", "cylinder", "cylindricalshell", "sphere", "rotpoly", "curve", "wire", "polyhedron", "transformed" or "bbox" |
priority |
number | CSXCAD priority. Where primitives overlap, the higher priority wins |
bbox |
[Vec3, Vec3] | Bounding box |
exact |
boolean | true if the shape is reproduced exactly, false for the bounding-box fallback |
The remaining fields depend on kind:
box:start,stop(Vec3), which are opposite corners. A box with zero extent on one axis is a sheet, for example a patch or a ground plane.polygon: a planar polygon (zero thickness).normalis the normal axis indexn(0, 1 or 2).elevationis the plane's coordinate along axisn.pointsis a list of[a, b]pairs in the plane.
linpoly: the same fields aspolygon, pluslength. The polygon is extruded along the normal fromelevationtoelevation + length(lengthmay be negative).cylinder:start,stop(Vec3) are the axis end points, andradiusis the radius.cylindricalshell: a tube (CSXCADCylindricalShell).start,stopas forcylinder;radiusis the radius of the middle of the wall andshell_widththe wall thickness, so the tube spansradius - shell_width / 2toradius + shell_width / 2.sphere:center(Vec3) andradius(CSXCADSphere).rotpoly: a solid of revolution (CSXCADRotPolyturned a full circle; the designer's cone and torus).pointsare[radial, axial]pairs of a closed outline, turned about the line throughorigin(Vec3) along axis indexaxis(0, 1 or 2): a point is inside when its distance from that line and its coordinate along it, minusorigin, lie in the outline. Radial values are never negative. The exporter computesbboxitself (CSXCAD's ignores the rotation) and writes full turns in the plane through the axis; an additional native affine transform is represented by atransformedwrapper.curve: a thin wire along a polyline (CSXCADCurve).pointsis a list of Vec3. It has no radius: openEMS puts it on the nearest mesh edges, so its effective radius is a fraction of a cell. Viewers draw it as a tube of a nominal radius.wire: the same with aradius(CSXCADWire), rasterised as a volume.polyhedron: a closed solid (CSXCADPolyhedron).verticesis a list of Vec3 andfacesa list of vertex-index lists (triangles; CSXCAD only rasterises triangular faces correctly). Faces are oriented outward in Fairbeam's models, but readers should not rely on it.transformed: an exact affine instance of a supported primitive.primitivecontains that exact local shape,matrixis a row-major 4×4 homogeneous matrix mapping column vectors from local to world coordinates, andbboxis the world-space axis-aligned bound. It is a separate kind so older readers cannot mistake local coordinates for world geometry. Readers that do not support the wrapper should usebboxas an explicitly approximate fallback.bbox: fallback for CSXCAD primitive types Fairbeam does not export exactly.source_kindholds the CSXCAD type name, and onlybboxdescribes the shape. When the native type is understood but its exact local geometry is not, the bound is transformed into world coordinates andtransformed: trueis set.
Added later (additive): curve, wire and polyhedron. Older readers treat the new kinds as
unknown and should fall back to bbox. The drawing shows polyhedra by their hull and feature edges
and wires as polylines.
Added with exact arbitrary-angle designer transforms (additive): transformed. Older readers treat
the wrapper as unknown and should fall back to its world-space bbox. Native simulation keeps the
local CSXCAD primitive and applies its affine matrix. Zero-thickness metal sheets must remain
parallel to the solver coordinate planes; lossy sheets must also retain their local normal because
openEMS selects tangential loss from the local CSXCAD bounds. The VBA macro exporter skips transformed
geometry with a warning.
Added with the designer's cylinder-shell and sphere shapes (additive): cylindricalshell and sphere. The viewer,
the drawing and the VBA macro exporter (a Cylinder with an inner radius, a Sphere) rebuild them exactly;
the fabrication export treats a vertical tube through the substrate as a plated via of its outer
diameter and skips spheres with a warning.
Added with the designer's cones and tori (additive): rotpoly. The viewer turns the outline
exactly (a lathe), the drawing shows a circle along the axis and the silhouette from the side, the
VBA macro exporter writes a Cone (or a Cylinder for equal radii) or a Torus when the outline is one, and
skips other outlines with a warning; the fabrication export skips it with a warning.
In-plane coordinate convention (polygon and linpoly). This follows CSXCAD. For normal axis n:
- the first coordinate of each point lies along axis
(n + 1) % 3 - the second coordinate lies along axis
(n + 2) % 3
| normal | points[i][0] |
points[i][1] |
|---|---|---|
| 0 (x) | y | z |
| 1 (y) | z | x |
| 2 (z) | x | y |
Note the y-normal case: points are (z, x), not (x, z). The 3D position of point [a, b] is:
p[n] = elevation; p[(n+1)%3] = a; p[(n+2)%3] = bports
| Field | Type | Description |
|---|---|---|
number |
number | Port number. It is also the key into results.ports (as a string) |
type |
string | "lumped" (openEMS LumpedPort) or "waveguide" (openEMS RectWGPort) |
R |
number | Port (reference) resistance in Ω. Waveguide ports: the TE wave impedance at the band center (see below) |
direction |
string | "x", "y" or "z": the direction of the feed current and voltage (waveguide: of propagation into the structure) |
start, stop |
Vec3 | Port box corners. Waveguide ports: opposite corners of the guide cross-section; the excitation plane is at start, the mode probes at stop along direction |
mode, a, b, f_cutoff |
string, number, number, number | Waveguide ports only: the mode ("TE10"), broad wall a along axis (n+1)%3 and narrow wall b along (n+2)%3 in drawing units, and the mode cutoff in Hz |
eps_r, mu_r |
number? | Waveguide ports with a filled reference only (a Python model's waveguide_port(..., eps_r=, mu_r=), see below); absent for the air reference |
excite |
boolean | Whether this port is driven, as declared by the model. Multi-port runs rebuild the model once per excited port and override this (see Multi-port runs); the bundle records the first run's setup |
group |
object, optional | Lumped ports only: connection ("parallel" or "series"), members (1–15 additional {start, stop, direction, polarity?} feeds), optional nonnegative integer priority (default 5). Member polarity is +1 (default) or −1; the main port is the first positive feed |
Grouped ports. This additive field preserves one logical port with N physical resistive feeds.
R and the result key refer to the logical mode. For signed member signals sᵢUᵢ, sᵢIᵢ,
Parallel reports U = ΣsᵢUᵢ/N and I = ΣsᵢIᵢ, with member resistance N·R; Series reports
U = ΣsᵢUᵢ and I = ΣsᵢIᵢ/N, with member resistance R/N. Sources excite all members of the
selected port, with signs and gap-length normalization. The existing incident/reflected-wave
and S-matrix fields use these modal signals. Orthogonal modes are excluded from modal power;
the physical power sum can differ when members are imbalanced. Absence of group keeps the
ordinary port behavior. Malformed groups are refused on reopen instead of losing members.
Waveguide ports. The port excites one TE_mn mode with its analytic field profile and measures
the mode voltage and current by projecting E and H in the probe plane onto the mode functions. The
reference impedance is the frequency-dependent TE wave impedance Z_TE = η0 k / β = η0 / √(1 − (f_c/f)²),
so u_inc, u_ref split the probe signals into forward and backward waves and S11 = u_ref / u_inc is
the ratio of reflected to incident mode amplitude.
results.ports[k].z_ref is Z_TE at the band center and z_ref_f holds it per frequency; there are
no time signals (they need a scalar reference). Terminate the guide behind the port in a PML (run
it through the boundary, auto_mesh(pad=[…, 0, …])) so the backward wave is absorbed.
python/examples/waveguide_thru.py checks the implementation (docs/VALIDATION.md section 13).
A Python model of a guide homogeneously filled with a real, nondispersive ε_r, μ_r can pass them to
Simulation.waveguide_port(..., eps_r=, mu_r=). The port then uses β = √(k0² ε_r μ_r − k_c²) and
Z_TE = η0 μ_r k0 / β, R, f_cutoff, z_ref and z_ref_f are those of the filled guide, and the
port entry records eps_r and mu_r (both absent for the air reference). Designs always use the
air reference.
Waveguide port power. openEMS' mode-matching probes read the power of the mode low, by an
amount set by the size of the mesh cells next to the guide walls (first order in that size: 4.3 %
for the horn's WR-90 feed at 20 cells/λ, 8.6 % at 30, the same at every frequency): the template is
normalized over the full node areas, but a node on a PEC wall carries half the air field.
Simulation.evaluate computes the deficit from the mesh lines (fairbeam.wgport, the probe
projection applied to the ideal mode on the Yee lattice) and multiplies the port's U and I by the
square root of the factor, so pacc_w, the incident and reflected power and the efficiency are
the mode's power. S11, Z_in and the S-parameters (ratios) do not change, except that in a
multi-port run the amplitudes of ports with different wall cells now carry their own factors.
The factor is stored as results.ports[k].probe_power_factor; sim.wg_probe_correction = False
switches it off. Verified for TE10 against the exact flux through the guide; it is not applied
when the probe box does not describe a closed guide (factor outside 0.9-1.3).
Guides sharing a wall. Where two guides are separated only by a zero-thickness PEC sheet
(e.g. a branch-guide coupler), the probe nodes on that sheet interpolate the fields of both guides,
so the neighbouring guide leaks into the port's U and I. fairbeam.wgport.inset_mode_probes(port, cells=1) (opt-in, rectangular TE10 ports on a Cartesian mesh) moves only the E/H probe boxes
cells mesh intervals in from both broad walls; the excitation, mode origin, port corners and
measurement plane stay where they are, and the power factor above is computed for the inset
probes (TE10 is uniform along b). Call it on every port of the S-matrix after the mesh is final.
Without it the probes span the full guide, as before.
lumped_elements
Written by Simulation.lumped_element(...) and its shortcuts lumped_resistor(...),
lumped_inductor(...) and lumped_capacitor(...) (a Design's resistors/RLC loads use the same
call). The element is an openEMS LumpedElement spread over the box start..stop, with current
along direction. It is not a part (it is excluded from parts) and not a port.
| Field | Type | Description |
|---|---|---|
name |
string | CSXCAD property name |
label |
string | Display label |
type |
string | "resistor" (R alone) or "rlc" (any element with L or C, including a single L or C) |
R, L, C |
number? | Resistance in Ω, inductance in H, capacitance in F; only the given branches are present |
topology |
string | "parallel" (native LEtype 0) or "series" (LEtype 1); lumped_resistor, lumped_inductor and lumped_capacitor write "parallel" |
direction |
string | "x", "y" or "z" |
start, stop |
Vec3 | Box corners (usually a sheet bridging a gap) |
The combined native series element has not passed its ideal-circuit check
(rf-rlc-workflows (opens in a new tab)); a series LC built from a separate
lumped_inductor and lumped_capacitor connected geometrically in series is the alternative.
Half space and mirror planes
half_space. When the z- boundary is PEC, the bundle carries:
{"axis": "z", "side": "min", "position": <first z mesh line>, "kind": "PEC"}This means the physical problem is the half space z ≥ position above an infinite PEC ground plane (image theory). The ground is not a part in parts. The viewer draws it as a ground plane. The far-field surface and polar cuts show only θ ≤ 90°, because directions below the ground do not exist physically. For any other boundary setup, half_space is null. Only the z- PEC case is currently encoded here.
mirror_planes (per far-field entry) is the number of PEC or PMC boundaries, on any face. openEMS mirrors the NF2FF recording surface at such boundaries and integrates the image as well. The raw Prad therefore covers the full image space, and the Dmax derived from it is too low by the same factor. Fairbeam corrects both by 2^mirror_planes:
prad_w = Prad_openEMS / 2^m
Dmax = Dmax_openEMS · 2^mso directivity, efficiency and gain refer to the physical half space (or quarter space when m = 2, and so on).
mesh
| Field | Type | Description |
|---|---|---|
x, y, z |
number[] | Mesh line coordinates, sorted, in drawing units |
cells |
[nx, ny, nz] | Cells per axis (lines − 1) |
total_cells |
number | nx · ny · nz |
min_cell, max_cell |
number | Smallest and largest cell width over all axes. The smallest cell sets the FDTD timestep |
auto |
object? | Present when the model used Simulation.auto_mesh(): the settings and report (cells, min/max cell, neighbor ratio, timestep and memory estimates, warnings). See Automatic meshing |
run
Statistics parsed from openEMS' captured stdout/stderr. null if the model was not simulated. Fields marked ? are absent if the pattern was not found in the log.
| Field | Type | Description |
|---|---|---|
grid |
Vec3? | FDTD grid size reported by openEMS |
timesteps |
number? | Timesteps run |
solver_time_s |
number? | Solver time reported by openEMS, in s |
timestep_s |
number? | FDTD timestep reported by openEMS, in s |
excitation_timesteps |
number? | Timesteps the custom (Gaussian-derivative) excitation pulse lasts: a run cannot converge before it has ended |
wall_time_s |
number | Wall time of Simulation.run, including setup |
speed_mcells_s |
number? | Throughput in MCells/s |
energy_trace |
{timestep, db}[] |
Field energy relative to its maximum, in dB (≤ 0), as printed during the run |
final_energy_db |
number? | Last value of energy_trace, when that sample is the final energy (logged at the last timestep, at or below the criterion, or the run hit the timestep limit) |
final_energy_bound_db |
number? | Present instead of final_energy_db for a converged run whose last energy line (if any) predates the stop: openEMS prints one only every ~4 s of wall time (the GPU engine every few thousand timesteps), and a run that stopped before max_timesteps stopped because the energy reached the end criterion. The final energy is at most this value, the criterion. Bundles written before Fairbeam handled this may carry the stale sample as final_energy_db; the viewer recognizes it (energy_trace ends before timesteps above the criterion) |
hit_timestep_limit |
boolean | timesteps ≥ max_timesteps (or openEMS printed its limit warning, which it only does for its own Gaussian excitation) |
converged |
boolean | timesteps < max_timesteps: a run can only stop early through the energy end criterion |
threads |
number | Requested thread count (0 = all cores) |
engine, engine_requested |
"cpu" | "gpu" |
The engine that ran and the one asked for (--engine). They differ when the openEMS build has no GPU engine. Absent in older bundles, which ran on the CPU |
engine_warning |
string? | Present when the GPU engine was requested but the run used the CPU |
exact_endcriteria |
boolean? | Whether the end criterion was checked every Nyquist period (the default) rather than every ~4 s of wall time (--no-exact) |
host |
{os, machine, cpu} |
Platform info. cpu may be null |
log_tail |
string[] | Last 12 lines of the openEMS log |
port_runs |
PortRun[]? | Multi-port runs only: one entry {port, timesteps, solver_time_s, wall_time_s, final_energy_db, converged, engine} (plus final_energy_bound_db when set) per excited port. The other run fields describe the first run; converged is true only if every run converged |
wall_time_total_s |
number? | Wall time of all runs of a multi-port simulation, in s |
efficiency_time_s |
number? | Post-processing time of the efficiency over the band (first run), in s. Present only with results.efficiency |
results
null if the model was not simulated.
| Field | Type | Description |
|---|---|---|
frequency |
number[] | --points frequencies (default 801), linear from f_min to f_max, in Hz |
ports |
object | Keyed by port number as a string ("1"). See below |
bands |
Band[] | Matched bands of the excited port |
farfield |
FarField[] | One entry per pattern frequency |
signals |
object | Time signals of the excited port, or {} |
sparams |
object? | N-port S-matrix. See sparams |
element_patterns |
object? | Complex embedded element patterns of multi-port antennas. See element_patterns |
efficiency |
EfficiencySweep[]? | Radiation efficiency over the band (fairbeam run --efficiency, a design's monitors.efficiency). See efficiency |
If no port is excited, bands and farfield are empty and signals is {}. For multi-port runs,
ports, bands and signals refer to the first excited port. ports holds only driven ports,
each with its own reflection from the run in which it was driven.
ports[k]. All arrays are aligned with frequency.
| Field | Type | Description |
|---|---|---|
s11_re, s11_im |
number[] | Reflection coefficient u_ref / u_inc (complex, linear) |
zin_re, zin_im |
number[] | Input impedance u_tot / i_tot in Ω |
z_ref |
number | Reference impedance in Ω (waveguide ports: Z_TE at the band center) |
z_ref_f |
number[]? | Waveguide ports only: the frequency-dependent reference impedance S11 refers to, in Ω |
probe_power_factor |
number? | Waveguide ports only: the factor the port's incident, reflected and accepted power were multiplied by (Waveguide port power); absent when it was not applied |
Band. A band is a contiguous run of frequency samples with 20·log10|S11| < -10 dB.
| Field | Type | Description |
|---|---|---|
f_lo, f_hi |
number | First and last sample below the threshold, in Hz |
f_center |
number | Frequency of minimum S11 within the band, in Hz. The app shows it as the band's "Best match"; its band "Center" is (f_lo + f_hi) / 2 (How results are computed) |
s11_min_db |
number | Minimum S11 in dB |
fractional_bw |
number | (f_hi − f_lo) / f_center |
edge_lo, edge_hi |
boolean | The band touches the start or end of the simulated range, so its true edge lies outside it |
The JSON band fields retain their original meanings. In the CSV export,
f_center_GHz is the middle of the edges, f_best_GHz is the JSON f_center, and
fractional_bw divides by the middle; edge_lo and edge_hi retain the open-edge flags.
FarField. The pattern frequencies are the bands' f_center values, the frequency of minimum S11 in each band (at most 4). If there are no bands, the frequency of minimum S11 is used. A model can choose its own (sim.pattern_freqs, e.g. the band edges and center of a broadband horn). --pattern overrides all of these.
| Field | Type | Description |
|---|---|---|
f |
number | Frequency in Hz |
theta |
number[] | θ in degrees: 0 to 180 in 3° steps (61 values) |
phi |
number[] | φ in degrees: 0 to 355 in 5° steps (72 values; 360 is not repeated) |
directivity_dbi |
number[][] | Directivity in dBi, indexed [theta][phi] (shape len(theta) × len(phi)). Floored at dmax_dbi − 60 |
dmax_dbi |
number | Maximum directivity in dBi, mirror-corrected |
dmax_pattern_dbi |
number? | Maximum directivity from the pattern alone, 4π·U_max / ∮U dΩ over the θ/φ grid, mirror-corrected. openEMS' dmax_dbi divides by the power flowing through the NF2FF box instead. The two should agree within ~0.1 dB; a larger gap points at boundary reflections (MUR too close) or a coarse NF2FF surface. Absent in older bundles |
prad_w |
number | Radiated power in W, mirror-corrected |
pacc_w |
number | Power accepted at the port, ½·Re(u·i*) interpolated at f, in W |
rad_efficiency |
number | null | prad_w / pacc_w. null if pacc_w ≤ 0. Above 1 is unphysical for a passive antenna (the two powers come from independent numerical measurements); the value is kept and flagged in qa_warnings. For a model marked lossless (see below) 1 while prad_w / pacc_w is within 5 % of 1 |
rad_efficiency_raw |
number | null? | Lossless models only: the simulated prad_w / pacc_w, the power balance of the two numerical measurements |
qa_warnings |
string[]? | Exporter QA notes for this entry, e.g. a radiation efficiency above 100 % (gain then exceeds Dmax by the same factor), or a lossless model's power balance |
gain_dbi |
number? | 10·log10(efficiency · Dmax). Present only if efficiency > 0 |
realized_gain_dbi |
number? | 10·log10(efficiency · Dmax · (1 − |S11|²)). Present only if efficiency > 0 |
mirror_planes |
number | Number of PEC/PMC boundaries used for the correction. See above |
port |
number? | Port that was driven for this entry (bundles since multi-port support). Multi-port runs have one entry per excited port and pattern frequency, all at the same frequencies |
cp |
object? | Circular polarisation, for models that set sim.cp_outputs = True. See below |
cp. With the IEEE convention and openEMS' e^{jωt} phasors, the right- and left-hand circular
components of the far field are E_R = (E_θ + jE_φ)/√2 and E_L = (E_θ − jE_φ)/√2 (for a wave along +z,
RHCP rotates from x to y). Grids are indexed [theta][phi] like directivity_dbi.
| Field | Type | Description |
|---|---|---|
rhcp_dbi, lhcp_dbi |
number[][] | Partial directivities D·|E_R|²/|E|² and D·|E_L|²/|E|² in dBi (they add up to directivity_dbi in linear units), floored at dmax_dbi − 60 |
axial_ratio_db |
number[][] | Axial ratio (|E_R| + |E_L|) / ||E_R| − |E_L|| in dB: 0 = circular, capped at 60 dB (linear) |
peak |
object | {theta, phi, rhcp_dbi, lhcp_dbi, axial_ratio_db} at the directivity maximum |
boresight |
object | {theta: 0, rhcp_dbi, lhcp_dbi, axial_ratio_db} at θ = 0 |
The pattern CSV export adds rhcp_dBi, lhcp_dBi and axial_ratio_dB columns and the far-field
CSV the boresight values.
prad_w and pacc_w are absolute values for the unit-amplitude excitation. They are typically very small numbers, and only their ratio is meaningful.
Lossless models. A model with no loss mechanism at all (PEC metals, loss-free materials, no
resistors) radiates everything it accepts, so its radiation efficiency is 1 and prad_w / pacc_w
only measures how well the two numerical powers agree. A Python model says so with
sim.lossless = True (the pyramidal horn does); evaluate() checks the claim (a lossy material or
a resistor voids it, with a warning) and then reports rad_efficiency 1, gain = directivity, while
the simulated ratio is within 5 % of 1, keeping it in rad_efficiency_raw and a note; beyond 5 %
the simulated value is reported with a warning. prad_w and pacc_w are never altered. The horn
read Prad / Pacc 1.037 at cpw 20 and 1.08 at cpw 30. The NF2FF box was not the cause: an
exact Poynting flux (raw E and H on the Yee lattice) through the feed guide, the five-face box and
planes across the horn agree to 0.1-0.4 %, whereas the waveguide port's mode-matching probes read
the guide power 4.3 % (cpw 20) and 8.6 % (cpw 30) low. The earlier "box +2.4 % / +5.8 % against the
feed flux" compared with a node-interpolated flux, which has the same deficit at the metal walls.
Simulation.evaluate now calibrates the waveguide port (below, probe_power_factor); the horn's
Prad / Pacc is 0.992-0.995 (cpw 20) and 0.995-0.998 (cpw 30), and the 5 % rule above is kept as a
guard. The diagnosis also found a mesh bug: polyhedron vertices, which CSXCAD stores in single
precision, put a mesh line at y = 5.0799999 instead of the wall at 5.08, so the edges on it were not
metal and the guide was a cell too tall (E_z at 6 % of E_y in the guide; fairbeam.automesh now
snaps such coordinates back).
efficiency
EfficiencySweep. Optional (absent unless requested): the radiation efficiency as a 1D result
over frequency. The NF2FF box records time-domain dumps, so after the
run openEMS transforms them at N frequencies spread evenly from f_min to f_max (default 21,
3 to 201). Only the powers are kept, no patterns. One entry per driven port, with port set as for
farfield[].port (a 1-port run has one entry).
| Field | Type | Description |
|---|---|---|
port |
number? | Port that was driven |
f |
number[] | Frequencies in Hz, linspace(f_min, f_max, N) |
prad_w |
number[] | Radiated power in W, mirror-corrected as for farfield (divided by 2^mirror_planes) |
pacc_w |
number[] | Power accepted at the port, ½·Re(u·i*) interpolated at each f, in W |
rad_efficiency |
(number | null)[] | prad_w / pacc_w, 4 decimals; null where pacc_w ≤ 0. Values above 1 are kept, as for farfield; a lossless model reports 1 within 5 %, as there |
rad_efficiency_raw |
(number | null)[]? | Lossless models only: the simulated prad_w / pacc_w |
pacc_error |
(number | null)[]? | Estimated relative error of pacc_w from where the run stopped (below). null where pacc_w ≤ 0 |
reliable |
boolean[]? | false where pacc_error exceeds 0.1 (10 %) or pacc_w ≤ 0: the value is kept, but it is not trustworthy. The viewer leaves these out of the curves and draws them as flagged points. Absent in older bundles (every value counts as reliable) |
mirror_planes |
number | PEC/PMC boundaries used for the correction |
theta_step, phi_step |
number | Angular grid of the transform in degrees (90 and 180: θ 0/90/180, φ 0/180). openEMS' Prad is the Poynting flux through the NF2FF box surface and does not depend on this grid (bit-identical from 3°/5° to 90°/180° on the patch example, and at all 11 frequencies across the patch starter's band), so the coarsest grid is used; it only sets the cost |
qa_warnings |
string[]? | Summary notes: the frequencies marked unreliable, and reliable ones whose efficiency exceeds 1 |
Total efficiency is rad_efficiency · (1 − |S11|²) with S11 from ports[port] at f (interpolated
on frequency). At a pattern frequency the sweep's Prad equals farfield[].prad_w.
Reliability. Away from the match the port reflects almost all of the incident power, and
pacc_w = P_inc (1 − |S11|²) is a small difference of two large numbers. Stopping the run at the end
criterion truncates the ring-down of the port signals, which leaves an error of about the same
absolute size at every frequency; relative to a small pacc_w it is large, and the ratio zig-zags.
pacc_error estimates it as the change of ½·Re(U·I*) when the last tenth of the ring-down (after the
excitation pulse) is left out of the DFT; while the signals decay, what the run did not record is of
that order or smaller. Simulated on the patch starter (11 frequencies, 1.47–3.19 GHz, GPU):
| End criterion | Radiation efficiency over the band | Reliable |
|---|---|---|
| −50 dB | 15, 33, 44, 52, 68, 62, 69, 61, 85, 53, 69 % | 7 of 11 (the last 4 not) |
| −60 dB | 17, 32, 46, 56, 63, 66, 68, 70, 71, 70, 68 % | 8 of 11 |
| −70 dB | 17, 31, 44, 55, 61, 66, 67, 66, 67, 65, 63 % | 11 of 11 |
Prad moved by at most a few % between the three runs, pacc_w by up to 33 % above resonance (where
1 − |S11|² is 3 to 4 % and the excitation spectrum is weakest). The estimate was 2–51 % at −50 dB
against 0.4–24 % actual and 0.2–20 % at −60 dB against 0.1–7 % (the actual taken against the −70 dB
run), so it errs on the safe side. The NF2FF angular grid (90°/180° against 5°/10°: identical Prad)
and the dump sampling (every Nyquist/4 = 37 timesteps, 8 samples per period at f_max, as for the
port probes) are not the cause. The truncation error of Prad (a few %) is not included.
signals. Port time signals, downsampled by a constant stride to at most about 1500 points.
| Field | Type | Description |
|---|---|---|
time_ns |
number[] | Time in ns |
u_inc, u_ref, u_tot |
number[] | Incident, reflected and total port voltage in V |
i_tot_scaled |
number[] | Total port current multiplied by z_ref, in V, so it can share the voltage axis |
dt_s |
number | null | Timestep of the original (not downsampled) signal, in s |
samples |
number | Sample count of the original signal |
Multi-port runs
fairbeam run (and sweep/converge) build the model once per excited port inside
fairbeam.simulation.excite_only(n). That run drives port n and leaves every other port in place,
terminated in its resistance. Default: all ports for models with up to 4 ports, otherwise port 1.
--excite all|1,3 overrides. A 1-port model runs exactly as before, but its bundle also gets a
1 × 1 sparams.
sparams
| Field | Type | Description |
|---|---|---|
ports |
number[] | Matrix indices 1..N, in matrix order (model port order) |
z_ref |
number[] | Reference impedance of each port in Ω (the lumped port resistance) |
excited |
number[] | Indices of the driven ports, which are the known columns |
complete |
boolean | Every port was excited, so the full matrix is known |
method |
string | "B A^-1" (complete) or "b_i / a_j" (partial) |
s |
object | Keys "i,j" = S_ij: wave out of port i per wave into port j. Each value is {re: number[], im: number[]} aligned with frequency, rounded to 5 decimals. Only columns j in excited are present |
qa |
object | See below |
port_numbers |
number[]? | Present only when the model's port numbers are not 1..N; index k refers to port port_numbers[k-1] |
Definition. For port i with real reference Z_i, U_i and I_i are the frequency-domain port
voltage and current (current flowing into the structure). The power waves are
a_i = (U_i + Z_i I_i) / (2 sqrt(Z_i)) and b_i = (U_i − Z_i I_i) / (2 sqrt(Z_i)), which are
openEMS' uf_inc and uf_ref divided by sqrt(Z_i). With the waves of the run that drives port j as
column j of the matrices A and B, the complete matrix is S = B A^-1 at every frequency. This is
exact even though a terminated FDTD port has a small nonzero incident wave. With a partial
excitation, the known columns are S_ij = b_i / a_j. For equal Z_i these are the usual
S-parameters; otherwise they are power-wave S-parameters referenced to each port's own Z_i.
results.ports[k].s11_* equals S_kk for every driven port k.
qa.
| Field | Type | Description |
|---|---|---|
reciprocity_max |
number | null | max over frequency and pairs of |S_ij − S_ji| (both columns known), linear. FDTD typically gives 1e-4..1e-2 |
reciprocity_pairs |
object | The same per pair, keys "i,j" with i < j |
column_power_max, column_power_min |
object | Per driven port j (key "j"): max / min over frequency of Σ_i |S_ij|². 1 − column power is the fraction dissipated or radiated |
passivity_max |
number | null | Largest column power. It must be ≤ 1 for a passive structure |
passive |
boolean | passivity_max ≤ 1 + passive_tol |
passive_tol |
number | Tolerance, 0.01 |
element_patterns
Present for antennas (models with an NF2FF box) when several ports are driven, or when
--element-patterns on is given. It holds the complex far field of each driven port per unit
incident wave, with all other ports terminated, so mutual coupling is included. See
Arrays and beam steering for how to combine them.
| Field | Type | Description |
|---|---|---|
normalization |
string | Human-readable normalization note |
radius_m |
number | Far-field radius of the stored E values, in m (1) |
encoding |
string | "i16le-base64-scaled" (written since this change) or "f32le-base64" (older bundles). Readers accept both; see Encodings |
shape |
[number, number] | [n_theta, n_phi] |
theta, phi |
number[] | Grid in degrees (the far-field grid, possibly decimated) |
frequencies |
number[] | Frequencies in Hz, the same as the far-field entries |
decimation |
number | Angular decimation factor (1 = full far-field grid). θ = 180° is always kept |
mirror_planes |
number | PEC/PMC boundaries. The fields cover the image space; divide sphere integrals by 2^m |
phase_center |
Vec3 | Common phase center of all ports (drawing units) |
ports |
object[] | One per driven port: {port, position, fields}. position is the port center (drawing units), used as the element position for steering. fields is one {f, scale, e_theta_re, e_theta_im, e_phi_re, e_phi_im} per frequency (scale only with "i16le-base64-scaled") |
Units: E in V/m (peak phasor) at radius_m, for an incident power wave a = U_inc / sqrt(Z_ref) =
1 sqrt(W). Radiation intensity is U = r² (|E_θ|² + |E_φ|²) / (2 η0) and incident power is
½ |a|². The array field for weights w_j is Σ w_j E_j.
Element-pattern encodings
Each of e_theta_re, e_theta_im, e_phi_re, e_phi_im is a base64 string of n_θ · n_φ
little-endian values, row-major [theta][phi].
"i16le-base64-scaled"(the writer's default,python/fairbeam/multiport.py): signed int16 q = round(v /scale· 32767), and v = q ·scale/ 32767.scaleis a number in eachfieldsentry: the largest |value| over that entry's four arrays (one scale per port and frequency, so E_θ and E_φ share it;scale0 means all zeros). The quantisation step is 1/32767 of the peak component, about −90 dB. On the 2×1 and 4×1 patch arrays this changes synthesized directivity by at most 0.01 dB where D > Dmax − 50 dB, for uniform, steered (0°, 30°, 60°) and tapered weights, with the same beam direction and HPBW within 0.003°."f32le-base64"(bundles written before): float32, noscale. Twice the size; float32 noise hardly compresses, so gzip gains almost nothing on either form.
The viewer (src/lib/sparams.ts) and fairbeam.array read both. Non-finite far fields are
written as "f32le-base64". fairbeam.multiport.reencode_element_patterns(bundle) converts an
older bundle in place without touching anything else.
fields
Optional; present only for runs with fairbeam run --fields (python/fairbeam/fields.py). For every plane
that holds zero-thickness metal (a box sheet or a polygon; at most 4 planes, smallest first), openEMS
records a frequency-domain rot(H) dump exactly on the sheet's mesh plane. On a PEC sheet the in-plane
components equal the surface current density divided by the constant normal dual-cell width, so the stored
map is the surface current magnitude |J_s|, up to one
constant per plane. It is resampled from the FDTD mesh onto a regular grid and normalized per plane and
frequency.
| Field | Type | Description |
|---|---|---|
quantity |
string | "surface_current" |
definition |
string | Human-readable definition |
units |
string | Normalization note: 1000 = maximum over the metal of the plane at that frequency, -1 = no metal |
phase_version |
number | 1 identifies the optional signed-int8 complex-current payload format described below. Older readers may ignore the additional phasors field; bundles without phase_version retain their original magnitude-only meaning |
port |
number? | Number of the port driven in the run that recorded the maps. The maps come from one excitation: a multi-port run (one openEMS run per excited port) records them in the first excited port's run only, with the other ports terminated; there are no per-port maps. Absent when no single driven port is known and in bundles written before it was added. The viewer then falls back to the one port flagged excite in ports (that list comes from the same build that recorded the maps) and otherwise shows the excitation as unknown, never as port 1 |
planes |
FieldPlane[] | See below |
FieldPlane
| Field | Type | Description |
|---|---|---|
name |
string | Dump name (fairbeam_J_<axis><i>) |
parts |
string[] | Metal parts with sheets in this plane |
axis, position |
number | Normal axis index and plane coordinate (drawing units) |
u_axis, v_axis |
number | In-plane axes, (axis + 1) % 3 and (axis + 2) % 3 (same convention as polygons) |
u_range, v_range |
[number, number] | Extent of the metal in the plane; the first and last samples lie on these values |
nu, nv |
number | Samples along u and v (at most 100 along the longer side) |
frequencies |
FieldFrequency[] | One entry per far-field frequency |
FieldFrequency
| Field | Type | Description |
|---|---|---|
f |
number | Frequency actually dumped (Hz). Without explicit --fields frequencies a grid with ~0.5 % steps is dumped and the nearest sample is used |
f_target |
number | Far-field frequency this map belongs to (Hz) |
values |
number[] | nv · nu integers, row-major with v as the row index (values[j * nu + i] is at u_range[0] + i·Δu, v_range[0] + j·Δv). 0..1000 = |J_s| / max (linear, phasor magnitude); -1 = outside the metal (exact primitives, not the staircase) |
phasors |
string | With phase_version: 1, base64 bytes of signed int8 samples, four per pixel in [Ju.re, Ju.im, Jv.re, Jv.im] order. Pixels use the same v-row/u-column order and in-plane axes as values. Each component is bilinearly resampled as a real or imaginary scalar before quantisation. For each frequency, peak is the same maximum over metal of the resampled vector magnitude used by values; each component is round(127 · component / peak), clipped to [-128, 127]. All four bytes are zero outside the exact metal mask. The stored values are relative to peak, which is not retained as an absolute current density. The phase convention is Re{J exp(+j phase)} = re cos(phase) - im sin(phase), so at +90° the real current is -im. This field is additive: magnitude-only consumers can continue using values and ignore phasors |
The peak value sits at the metal edges, where the current is singular and only as resolved as the mesh; compare shapes and relative levels, not the absolute peak.
field_planes
Optional; present only for runs with field planes (fairbeam run --field-plane, a design's
monitors.field_planes; python/fairbeam/field_planes.py). One entry per plane and frequency.
For each plane openEMS records a frequency-domain E (dump type 10) or H (type 11) dump with
node interpolation on the mesh line nearest the requested position, across the whole simulation
domain in the plane. The stored map is the phasor magnitude, resampled bilinearly from the FDTD mesh
onto a regular grid (the step of the finest mesh cell in the plane, at most 200 samples along the
longer side) and rounded to 3 significant digits. The complex components are stored beside it as an
optional 8-bit phasor.
Normalization. openEMS' frequency-domain dumps are the single-sided Fourier transform
2 Δt Σ f(t) e^(−jωt), the same transform as its port voltages. Scaled by sqrt(1 W / P_inc(f)),
with P_inc = |u_inc|² / (2 Re Z_ref) of the driven port, they become the peak phasor amplitude
for 1 W incident (stimulated) power at the driven port. Without a single driven port they stay the
raw transform (unit: "arb.", normalization: "none"). The maps come from one excitation, as for
fields: a multi-port run records them in the first excited port's run only.
FieldPlaneMap
| Field | Type | Description |
|---|---|---|
quantity |
string | "E" or "H" |
component |
string | "abs": sqrt(|Fx|² + |Fy|² + |Fz|²); "x", "y", "z": the magnitude of that component |
normal, axis |
string, number | Plane normal ("x", "y", "z") and its axis index |
u_axis, v_axis |
number | In-plane axes, (axis + 1) % 3 and (axis + 2) % 3 (as for fields) |
position_mm |
number | Coordinate of the mesh line the map was recorded on (drawing units) |
requested_mm |
number | Position asked for; outside the domain it is recorded at the domain's edge |
f |
number | Frequency (Hz) |
u_range, v_range |
[number, number] | Extent (the domain in the plane); the first and last samples lie on these values |
nu, nv |
number | Samples along u and v |
unit |
string | "V/m" (E) or "A/m" (H) when normalized, else "arb." |
normalization |
string | Human-readable normalization, "none" without a driven port |
max |
number | Largest value of the map (4 significant digits) |
magnitude |
number[][] | nv rows of nu values: magnitude[j][i] is at u_range[0] + i·Δu, v_range[0] + j·Δv |
port |
number? | Number of the driven port (absent when unknown) |
phasor |
FieldPlanePhasor? | The complex components behind the map, for its phase and the field over one period. Absent in bundles written before it existed, or when the field is zero: viewers then show the magnitude only |
FieldPlanePhasor (optional). The complex components of the same resampled nu × nv grid, so a
viewer can show the phase and the instantaneous field Re{F e^(jωt)} over one period (the designer's
2D field map tab and the 3D view animate it).
| Field | Type | Description |
|---|---|---|
components |
string[] | ["x","y","z"] for component: "abs", else the one stored component, e.g. ["z"] |
peak |
number | Largest magnitude of any stored component (4 significant digits), in the map's unit; the int8 full scale |
data |
string | Base64 of signed 8-bit integers, row-major with v as the row: [v][u][component][re, im], nv · nu · len(components) · 2 bytes. Value = int / 127 · peak, so the resolution is peak / 127 (0.8 % of the peak), coarser than magnitude; phase is only meaningful where the magnitude is well above that step |
The values are scaled like magnitude (1 W incident power) and, with a driven port, turned by the
phase of its incident voltage wave (× conj(u_inc) / |u_inc|): phase 0 is that wave at t = 0, not
the arbitrary time origin of the excitation pulse. Without a driven port (normalization: "none")
the phase is that of the raw transform and has no physical reference. The time convention is
e^(+jωt): the field at phase φ is Re{F e^(jφ)}.
Size: about 80 kB of JSON per 106 × 106 map, about 150 kB at the 200-sample cap; with the design
limits (4 planes × 4 frequencies) at most about 2.5 MB. The phasor adds 6 bytes per pixel for abs
maps (2 for a single component) as base64: about 90 kB at 106 × 106 (abs), 320 kB at 200 × 200,
so at most about 5 MB more for 16 full-size abs maps. Caveats: with node interpolation the
normal E component right at a metal sheet or a dielectric interface is averaged over the two
neighboring cells, so put the plane a cell away for clean fringing fields; the outer cells of a PML
boundary hold absorbed, non-physical fields.
Index file
public/projects/index.json is rebuilt after every write, or by fairbeam index [folder]. It covers every *.json in the folder (except itself) whose schema starts with fairbeam.project/. Entries are sorted by model id, then name.
{
"projects": [
{"file": "patch-antenna.json", "name": "Rectangular patch antenna", "model": "patch-antenna",
"created": "2026-09-24T22:24:15+0300", "simulated": true, "bands": [2.433], "cells": 97152}
],
"updated": "2026-09-24T22:25:42+0300"
}| Field | Type | Description |
|---|---|---|
file |
string | Bundle file name, relative to the index |
name, model, created |
string | Copied from the bundle (model is model.id) |
simulated |
boolean | results is present |
bands |
number[] | Each band's f_center (the frequency of minimum S11) in GHz, 3 decimals |
band_ranges |
object[]? | Each band's edges in GHz (4 decimals) and whether it touches the simulated range: {lo, hi, edge_lo, edge_hi}. The example picker shows a band by the middle of its edges, or by its range when it touches the simulated range. Absent when there are no bands, and in older indexes (the picker then shows bands) |
cells |
number | mesh.total_cells |
engine |
string? | "CPU", "Metal", "CUDA" or "GPU": what ran the simulation (from run.engine and the log). Absent without a run |
params |
object? | The model parameters set to something other than their default, {key: value}. Absent when there are none |
Versioning
Adding optional fields does not change the schema id: lumped_elements, results.sparams,
results.element_patterns, farfield[].port, run.port_runs, fields.port, results.efficiency, run.efficiency_time_s, run.engine / engine_requested / exact_endcriteria, field_planes, efficiency[].pacc_error / reliable, rad_efficiency_raw and parts[].conductor and parts[].void were added this way. Removing or renaming fields, or changing units or indexing, bumps it to fairbeam.project/2. When the schema changes, src/types.ts changes in the same commit.