Model files
A model is a plain Python file that defines three things:
MODEL: a dict withid,name,descriptionand optionallyreference.PARAMS: a list offairbeam.Param(key, default, label, unit, description, minimum, maximum). The type ofdefaultsets how--setvalues are parsed (int, float or str).build(p: dict) -> Simulation: receives the resolved parameter values and returns a fully configuredfairbeam.Simulationwith geometry, ports, mesh and (optionally) an NF2FF box.
Meshing: call sim.auto_mesh() after the geometry and ports (and before add_nf2ff_box) to generate the whole mesh from the geometry: thirds rule at metal edges, fine cells across narrow strips, dielectric layers resolved, graded air and λ/4 padding. It matches the converged hand-tuned meshes to 0.1 % (see Automatic meshing). The editor templates use it.
Simulation wraps the usual openEMS/CSXCAD objects (sim.fdtd, sim.csx, sim.mesh). You can use the normal CSXCAD calls. The helpers metal, dielectric, lumped_port, waveguide_port, lumped_resistor (and lumped_element, lumped_inductor, lumped_capacitor for ideal R/L/C, see Project bundle format), add_nf2ff_box, smooth_mesh and set_focus also record the metadata the viewer and the VBA macro exporter need (loss tangent, port definitions, phase center, default framing). sim.metal(name) is a perfect conductor; sim.metal(name, conductivity=5.8e7, thickness=0.035) (S/m, mm) is a lossy metal: sheets become openEMS conducting sheets of that thickness and volumes a material of that conductivity.
A model can also be a designer file (<id>.design.json, see The designer): fairbeam run, params, geometry, sweep, converge and optimize take it in place of a .py file, and examples/designs/ holds ready-made ones. The Start screen creates new designs from starters (empty project, half-wave dipole, quarter-wave monopole, open-ended waveguide, patch, printed sleeve dipole, microstrip line).
# minimal strip dipole (the shipped python/models/dipole.py adds PML, thirds-rule edges and a finer
# mesh around the strip; this bare version resonates ~8 % low, see docs/VALIDATION.md)
from fairbeam import Param, Simulation
MODEL = {"id": "dipole", "name": "Half-wave dipole",
"description": "Thin-strip dipole with a centre lumped feed."}
PARAMS = [
Param("length", 58.0, "Total length", "mm", minimum=5),
Param("width", 1.0, "Strip width", "mm", minimum=0.1),
Param("gap", 1.0, "Feed gap", "mm", minimum=0.2),
Param("f_min", 1.5, "Band start", "GHz", minimum=0.1),
Param("f_max", 3.5, "Band stop", "GHz", minimum=0.2),
]
def build(p: dict) -> Simulation:
sim = Simulation(p["f_min"] * 1e9, p["f_max"] * 1e9) # MUR boundaries, DC-free pulse
L, w, g = p["length"], p["width"], p["gap"]
arm = sim.metal("dipole", label="Dipole arms")
arm.AddBox(priority=10, start=[-w / 2, 0, g / 2], stop=[w / 2, 0, L / 2])
arm.AddBox(priority=10, start=[-w / 2, 0, -L / 2], stop=[w / 2, 0, -g / 2])
sim.lumped_port(1, 73, [-w / 2, 0, -g / 2], [w / 2, 0, g / 2], "z")
sim.mesh.AddLine("x", [-80, -w / 2, w / 2, 80])
sim.mesh.AddLine("y", [-80, 0, 80])
sim.mesh.AddLine("z", [-100, -L / 2, -g / 2, g / 2, L / 2, 100])
sim.smooth_mesh() # λ/20 at f_max
sim.set_focus([-10, -10, -L / 2 - 5], [10, 10, L / 2 + 5])
sim.add_nf2ff_box(center=[0, 0, 0])
return simfairbeam params python/models/dipole.py
fairbeam run python/models/dipole.py --set length=60Keep at least λ/4 at f_min of free space between the structure and absorbing boundaries (PML_8 inside that margin; MUR needs about λ/2). The included models (python/models/patch_antenna.py, python/models/sierpinski_monopole.py) show edge meshing, dielectrics, PEC ground boundaries and fractal geometry.
Included models
Every model in python/models/ has a simulated bundle in public/projects/ and a validation
section in Validation.
| Model | What it is | Shows | Validation |
|---|---|---|---|
dipole.py |
Half-wave strip dipole, 2.4 GHz | thirds-rule edges, PML, mesh study | §1 |
patch_antenna.py |
Rectangular patch, RO4003C, probe feed | dielectric meshing, --set mesh=auto |
§2 |
inset_patch.py |
Inset-fed patch on FR4 | microstrip feed, notches | §2b |
minkowski_patch.py |
Minkowski fractal patch | polygon geometry | §2c |
sierpinski_monopole.py |
Sierpinski gasket over PEC ground | half space, fractal iterations | §3 |
microstrip_line.py |
50 Ω through line | two-port S-matrix | §7 |
wilkinson_divider.py |
Wilkinson divider with isolation resistor | lumped resistor, three ports | §8 |
patch_array_2x1.py, patch_array_4x1.py |
Patch arrays | embedded element patterns, steering | §9, Arrays and beam steering |
branchline_coupler.py |
90° hybrid | four ports, auto_mesh |
§11 |
lowpass_stepped.py |
5th-order stepped-impedance low-pass | narrow lines, auto_mesh |
§12 |
pyramidal_horn.py |
Optimum 16 dBi horn, WR-90, 10 GHz | polyhedron walls, waveguide port, NF2FF with a skipped face | §13 |
helix_axial.py |
Kraus axial-mode helix, 2.4 GHz | thin-wire Curve, circular-polarisation outputs |
§14 |
Non-planar geometry, waveguide ports and circular polarisation
- Slanted solids: build them as a CSXCAD
Polyhedronwith triangular faces (CSXCAD rasterises only triangles correctly; seeslab()inpyramidal_horn.py). The bundle, the viewer and the drawing reproduce polyhedra exactly;auto_meshputs lines at every vertex coordinate. Rotated primitives (AddTransform) simulate fine but are exported only as their bounding box. - Thin wires: a CSXCAD
Curve(a list of points, no radius) is put on the nearest mesh edges;auto_meshcovers its extent with half-size cells. Its effective radius is a fraction of a cell, so the input impedance depends on the mesh (see §14). AWire(with radius) is rasterised as a volume and needs cells smaller than the radius. - Waveguide ports:
sim.waveguide_port(n, start, stop, "z", a, b, "TE10")spans the guide cross-section; the excitation is in thestartplane and the probes in thestopplane. Run the guide into a PML behind the port (auto_mesh(pad=[q, q, q, q, 0, q])leaves that face on the structure) and skip the NF2FF face it crosses (add_nf2ff_box(directions=[1, 1, 1, 1, 0, 1])). S11 is referred to the TE wave impedance (Project bundle format). For a guide filled across its whole cross-section with one homogeneous, nondispersive material, pass the same values to the port (eps_r=2.08,mu_r=1.0, Python models only): the reference then uses the filled guide's β and Z_TE. These keywords only set the reference; draw the material as usual. Without them the port keeps the air reference (python/examples/rectangular_guide_loss.pyuses them). - Circular polarisation:
sim.cp_outputs = Trueadds RHCP/LHCP directivity and axial-ratio grids to every far field. - Pattern frequencies:
sim.pattern_freqs = [...](Hz) replaces the default (band centers), e.g. band edges and design frequency for broadband antennas.
Material library
fairbeam.materials holds nominal values of common antenna materials with their source; the
designer's Material library dialog (ribbon Modeling tab, Materials group, Library) shows the same list
(src/designer/materials.ts) and copies an entry's values into the design, so a design file stays
self-contained. In a Python model: ro = fairbeam.materials.get("ro4003c"), then
sim.dielectric("sub", ro["eps_r"], tan_d=ro["tan_d"], tan_d_freq=ro["tan_d_freq"] * 1e9).
Laminates vary by batch, thickness and frequency: for a design that matters, use the datasheet of
the laminate you buy. openEMS models loss as a constant conductivity, so tan δ is exact at the
frequency given (empty: the band center). The library's PEC entry is a perfect conductor; to model conductor loss, give a design's metal material a conductivity (S/m, empty = PEC) and, for sheets, a thickness (mm; default 0.035).
| Material | Kind | εr | tan δ | at | Source and caveats |
|---|---|---|---|---|---|
| PEC (copper) | metal | Perfect electric conductor (openEMS metal). Copper's finite conductivity (5.8e7 S/m) and surface roughness are not modeled; the conductor loss of printed antennas is small. | |||
| FR4 | dielectric | 4.3 | 0.02 | 1 GHz | Generic glass-epoxy laminate. εr 4.2–4.7 and tan δ 0.015–0.025 vary with the resin content, the supplier and the frequency: use your laminate's datasheet. |
| Rogers RO4003C | dielectric | 3.38 | 0.0027 | 10 GHz | Rogers RO4003C datasheet: process εr 3.38 ± 0.05 and tan δ 0.0027 at 10 GHz, 23 °C (Rogers recommends the design εr 3.55 for circuit design). |
| Rogers RO4350B | dielectric | 3.48 | 0.0037 | 10 GHz | Rogers RO4350B datasheet: process εr 3.48 ± 0.05 and tan δ 0.0037 at 10 GHz, 23 °C (design εr 3.66). |
| Rogers RT/duroid 5880 | dielectric | 2.2 | 0.0009 | 10 GHz | Rogers RT/duroid 5880 datasheet: εr 2.20 ± 0.02 and tan δ 0.0009 at 10 GHz. |
| Taconic TLY-5 | dielectric | 2.2 | 0.0009 | 10 GHz | Taconic (AGC) TLY-5 datasheet: εr 2.20 ± 0.02 and tan δ 0.0009 at 10 GHz. |
| Alumina 99.5 % | dielectric | 9.8 | 0.0001 | 10 GHz | Typical of 99.5 % alumina thin-film substrates (εr 9.7–9.9, tan δ about 0.0001 at microwave frequencies); check the supplier's value. |
| PTFE (Teflon) | dielectric | 2.1 | 0.0002 | 10 GHz | Bulk PTFE, typical: εr 2.0–2.1, tan δ 0.0001–0.0003 from 1 to 10 GHz. |
| Air / vacuum | dielectric | 1 | 0 | Free space. The background is vacuum already; use it with a higher priority to cut an air gap or a hole out of another dielectric. |
The designer also has a personal list next to this built-in one, My materials (saved from a design's
materials, stored in the workspace's materials.json; see The designer).
fairbeam.usermaterials validates that file; Python models do not read it, and a design never refers to it.
Writing models in the app
With fairbeam serve running (the desktop app starts it), a Python model opened from Start › Python models has a Run panel with a Model code tab: the model file in a code editor (CodeMirror, Python highlighting, search with ⌘F / Ctrl+F). New in the Run panel opens the New model dialog, where Python model creates the file from a template and Visual design creates a .design.json.
From Start, New Python model… creates the source file, converts its geometry into an editable Design without running the solver, and opens the Design's Python panel with the saved source code. Each existing Python model row keeps its Run action and also has Open as Design; it reopens the linked Design when one already exists. If conversion fails, the Python source stays saved and can be opened in the Run panel's code editor. Editing the source file after conversion is reported in the Design Python panel, where the code can be reviewed and applied again.
New model (Python model) creates
<id>.pyin the models folder (python/models/in a checkout,~/Documents/Fairbeam/models/in the desktop app) from a template inpython/templates/:Template What it shows blank.pyA metal post on a finite ground plane with a lumped port; every section (identity, parameters, solver, geometry, port, mesh, framing) is explained dipole.pyThin-strip half-wave dipole with a center feed monopole_on_ground.pyQuarter-wave monopole over an infinite ground (PEC half-space boundary) patch_probe_fed.pySubstrate, ground and patch with a probe (lumped) feed microstrip_line.pyA line with lumped ports at both ends. Port 2 is declared excite=False, butfairbeam rundrives every port of a model with up to four (--excite, see Multi-port structures), so the run gives the two-port S-matrix including S21The id is the file name (
^[a-z][a-z0-9_]{1,40}$); an existing file is never overwritten.Save with ⌘S / Ctrl+S (or "Save and preview"). The server writes the file, loads and builds it (geometry only, like
fairbeam geometry) and reports the result: the parameter form updates to the newPARAMSand the 3D view shows the new geometry. A syntax error or an exception inbuild()is marked at its line in the editor, with the message, the source line and the traceback below.History. Every save keeps the previous version under
.sim/model-history/<id>/; the last 20 are listed and can be loaded back into the editor (save to restore).Conflicts. If the file changed on disk (another editor) since it was opened, saving is refused with "Reload from disk" or "Save anyway".
The bundled models (every file in the table of included models above) are read-only here; Duplicate makes an editable copy.
Model files are plain Python, so you can keep editing them in any editor as well; the app picks up changes when you reselect the model.
Automatic meshing (sim.auto_mesh()), its options, rules and validation are described in
Automatic meshing.