Scripting and specs
A project is a declarative spec: a JSON or YAML document describing the domain, stackup and materials, shapes, variables, constraints, generators, and settings. rayrf project build turns a spec into a .rfsim, rayrf project export-spec turns an existing project back into a spec, and one JobSpec file can drive a whole run.
The project-as-spec model
rayrf schema --what spec prints the authoritative JSON Schema for the format. Read it once, author against it. The spec has these top-level keys:
domain: the editor rectangle (x_mm,y_mm,w_mm,h_mm,auto_fit). Withauto_fittrue (the default) the rectangle is derived from the geometry plus the project's auto-fit padding, both when the spec is built and again at run time, sow_mm/h_mmin the spec are ignored (a warning names them). Setauto_fitfalse to size the box yourself, and thenw_mmandh_mmare required and used verbatim. The auto-fit padding round-trips throughpad_mode(percentormm),pad_percent, andpad_mm, soexport-specthenbuildreproduces the same box. Unknown domain keys are rejected.layers: the stackup, eachconductor,dielectric, orair, top to bottom. Anairlayer is a free-space spacer that sets the gap between conductors through itsthickness_mmand emits no material entry, so it renders as air.z_mmis recomputed from stack order, so omit it. Unknown layer keys are rejected, soeps_rin place ofepsilon_ris a clear error.shapes: geometry, each with akindand alayer. See CLI reference andrayrf schema --what shapes|portsfor the per-kind parameters.variables,bindings,constraints,generators: the parametric model. Abindingsentry maps a param path to an expression over variables. Path prefixes areprimitive:ID:param(a poly vertex isprimitive:ID:points.INDEX.x),layer:NAME:field,domain:field,fdtd:field, andgenerator:ID:param. Adomain:w_mmordomain:h_mmbinding takes effect only withauto_fitfalse. Withauto_fiton the geometry fit replaces it, and a warning names it.settings: anyFDTDSettingsfield, set verbatim.rayrf schema --what settingslists every field with its type, default, units, and description.
Only layers and shapes are required. Any setting not present takes the project default.
cells and auto air-margin modes to air_*_mm on collect, but the exporter reads only air_around_mm, air_above_mm, and air_below_mm. A headless run of a spec-built project reads whatever those mm values hold, so set them (or air_mode=mm) to control the margins.rayrf project build --spec FILE --out p.rfsim writes the project. On a spec error or a failed resolve (an undefined variable, an unknown layer) it prints one line to stderr and exits 2.
rayrf project export-spec --project FILE --out FILE is the inverse: it writes the spec for an existing project, emitting the verbatim params form so a rebuild reproduces the project. Export, edit the text, rebuild.
A worked example
A single rectangular patch over a ground plane on FR4, with one vertical feed port and one variable driving the patch width. Built with rayrf project build and checked with rayrf validate.
{
"schema": "rayrf-project-spec/v1",
"domain": {"auto_fit": true},
"layers": [
{"name": "Top", "type": "conductor"},
{"name": "Core", "type": "dielectric", "thickness_mm": 1.6, "epsilon_r": 4.3,
"loss_tangent": 0.02},
{"name": "Bottom", "type": "conductor"}
],
"variables": [
{"name": "patchW", "value": "16", "min": 12, "max": 20, "group": "Geometry"}
],
"shapes": [
{"kind": "rect", "layer": "Top", "name": "Patch",
"x_mm": 0, "y_mm": 0, "w_mm": "patchW", "h_mm": 12.3},
{"kind": "rect", "layer": "Core", "name": "Substrate",
"x_mm": 0, "y_mm": 0, "w_mm": 22, "h_mm": 22},
{"kind": "rect", "layer": "Bottom", "name": "Ground",
"x_mm": 0, "y_mm": 0, "w_mm": 22, "h_mm": 22},
{"kind": "port", "layer": "Top", "name": "Feed",
"x_mm": 0, "y_mm": -4, "port_number": 1, "impedance_ohm": 50,
"active": true, "bottom_layer": "Bottom", "top_layer": "Top"}
],
"settings": {"freq_min_hz": 4.5e9, "freq_max_hz": 7.0e9, "bc_all": "PML"}
}rayrf project build --spec patch.json --out patch.rfsim
rayrf validate --project patch.rfsimA rect on a numeric field takes a plain number ("h_mm": 12.3) or, when the value is a non-numeric string ("w_mm": "patchW"), an expression binding on that field. The feed spans vertically from bottom_layer to top_layer, so the layer order matters: a port whose two conductors land on the same mesh cell injects nothing and validate reports it. The ground and substrate rects paint the Bottom and Core layers so the dielectric is real, not air.
Inspecting a project
rayrf project query --project FILE --json prints a machine-readable readout: every shape (id, kind, layer, name, params), every variable (source value, resolved value, range, group, where-used count), constraints, generators, bindings, settings, the domain, a geometry readout (per-shape center, bounds, anchors, resolved boundary faces, domain clearance), and a last-results summary. Use it to inspect a project you did not build.
rayrf project dump-settings --project FILE prints every setting with its value, default, units, and description, grouped by category. Flags narrow the output:
--changed-onlylists only settings that differ from their default.--category NAMElimits to one category (an unknown name exits 2).--advanced true|falsefilters by the advanced flag.--jsonemits a machine-readable list instead of the grouped text.
Mutating a project
rayrf project set --project FILE --var name=value [--out FILE] sets one or more variables (repeatable --var), re-resolves bindings and constraints, and saves the project in place or to --out. A value may be a number or an expression over other variables. An unknown reference or a bad expression exits 2 with the field message and leaves the geometry unchanged.
Two run-time override forms change values for a single run without editing the project:
rayrf run --set name=valueoverrides a variable, resolved before export.rayrf run --setting name=valuesets anyFDTDSettingsfield. It goes through the same value gate as the GUI: an unknown name, a bad type or enum, or a hard-bound violation exits 2 before the run starts, a soft-bound value prints one warning and runs with the value exactly as set. Both flags repeat.
Variables and expressions
A numeric field accepts an expression over the project variables: the operators + - * / // % **, parentheses, the trig and math functions, and the constants pi and e. Trig is in degrees. The full function list is in rayrf schema --what spec and rayrf guide geometry.
JobSpec: one payload per run
rayrf run --job FILE reads a YAML or JSON JobSpec that carries the project, the overrides, the save behavior, the output directory, and the artifact selection. The job file overrides every other run flag, including which project runs, so a job is a single reproducible run request. An unknown key in the file is refused, so a typo cannot silently revert to a default.
project: patch.rfsim
quality: medium
out_dir: _job_out
settings:
freq_max_hz: 6.5e9
exports:
sparams: all
sparams_touchstone: true
smith: trueThe settings block passes through the same value gate as --setting. The overrides block sets advanced numeric overrides (cell sizes, air margins, the frequency band, and material fields). The dielectric permittivity key is epsilon_r, with eps_r accepted as an alias. Setting both to different values is an error, not a last-wins pick. The JobSpec fields:
| Field | Type | Default |
|---|---|---|
project | Path | required |
save_after_run | bool | false |
save_as | Optional[Path] | none |
quality | Optional[str] | none |
quality_frac | Optional[float] | none |
sim_mode | Optional[str] | none |
mesh_style | Optional[str] | none |
vram_budget_mb | Optional[float] | none |
backend | Optional[str] | none |
device_index | Optional[int] | none |
overrides | Dict[str, Any] | factory |
settings | Dict[str, Any] | factory |
convergence | Optional[bool] | none |
perf_mode | bool | false |
max_steps | Optional[int] | none |
sparam_stop | bool | false |
sparam_stop_db | float | -50 |
sparam_stop_periods | float | 2 |
sparam_stop_min_periods | float | 8 |
sparam_stop_consec | int | 3 |
force_disable_surface_currents | bool | false |
force_disable_nf2ff | bool | false |
force_disable_save_vtk | bool | false |
force_skip_geometry_export | bool | false |
out_dir | Optional[Path] | none |
exports | ExportSpec | factory |
The exports block selects what is written after the run:
| Field | Type | Default |
|---|---|---|
sparams | str | all |
sparams_touchstone | bool | false |
smith | bool | false |
polar | bool | false |
polar_freq_mhz | Optional[float] | none |
rad3d | bool | false |
rad3d_freq_mhz | Optional[float] | none |
rad3d_vtk | bool | false |
rad_pattern_csv | bool | false |
mesh_preview | bool | false |
mesh_preview_vtk | bool | false |
mesh_preview_size | Optional[int] | none |
surface_currents | bool | false |
surface_currents_freq_mhz | Optional[float] | none |
no_3d_renders | bool | false |
The run manifest
Every run writes run_manifest.json into its output directory. It is the machine-readable record of the run, and a failed write is fatal so a missing manifest is never mistaken for success. status is one of:
ok: the run completed.preflight_refused: a pre-flight gate refused before launch.vram_overflow_predicted: the--vram-budget-mbestimate exceeded the budget.error: a runtime failure.
The manifest records the resolved sim params (cell sizes, frequency band, boundaries, PML and air), the auto-derived mesh with its reasoning, performance (duration, steps, cells, GCell/s, VRAM, ringdown, convergence history), and the list of artifacts written. overrides_requested and overrides_honored are both present so a value overwritten by auto derivation is visible. The full contract is at rayrf schema --what manifest.
Batch running
rayrf run --folder DIR runs every project in a folder in sequence, --recursive to descend into subfolders. One project that fails does not abandon the rest: the batch continues to the next project and exits 1 if any project failed, 0 if all succeeded. Each failure is also appended to batch_failures.jsonl in the working directory.