Skip to content

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). With auto_fit true (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, so w_mm/h_mm in the spec are ignored (a warning names them). Set auto_fit false to size the box yourself, and then w_mm and h_mm are required and used verbatim. The auto-fit padding round-trips through pad_mode (percent or mm), pad_percent, and pad_mm, so export-spec then build reproduces the same box. Unknown domain keys are rejected.
  • layers: the stackup, each conductor, dielectric, or air, top to bottom. An air layer is a free-space spacer that sets the gap between conductors through its thickness_mm and emits no material entry, so it renders as air. z_mm is recomputed from stack order, so omit it. Unknown layer keys are rejected, so eps_r in place of epsilon_r is a clear error.
  • shapes: geometry, each with a kind and a layer. See CLI reference and rayrf schema --what shapes|ports for the per-kind parameters.
  • variables, bindings, constraints, generators: the parametric model. A bindings entry maps a param path to an expression over variables. Path prefixes are primitive:ID:param (a poly vertex is primitive:ID:points.INDEX.x), layer:NAME:field, domain:field, fdtd:field, and generator:ID:param. A domain:w_mm or domain:h_mm binding takes effect only with auto_fit false. With auto_fit on the geometry fit replaces it, and a warning names it.
  • settings: any FDTDSettings field, set verbatim. rayrf schema --what settings lists every field with its type, default, units, and description.

Only layers and shapes are required. Any setting not present takes the project default.

Note
The GUI resolves the 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.rfsim

A 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-only lists only settings that differ from their default.
  • --category NAME limits to one category (an unknown name exits 2).
  • --advanced true|false filters by the advanced flag.
  • --json emits 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=value overrides a variable, resolved before export.
  • rayrf run --setting name=value sets any FDTDSettings field. 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.
Note
A setting fixed in auto mode is derived and overwritten at run time. The run prints the discarded derived value so an override never loses silently.

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: true

The 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:

FieldTypeDefault
projectPathrequired
save_after_runboolfalse
save_asOptional[Path]none
qualityOptional[str]none
quality_fracOptional[float]none
sim_modeOptional[str]none
mesh_styleOptional[str]none
vram_budget_mbOptional[float]none
backendOptional[str]none
device_indexOptional[int]none
overridesDict[str, Any]factory
settingsDict[str, Any]factory
convergenceOptional[bool]none
perf_modeboolfalse
max_stepsOptional[int]none
sparam_stopboolfalse
sparam_stop_dbfloat-50
sparam_stop_periodsfloat2
sparam_stop_min_periodsfloat8
sparam_stop_consecint3
force_disable_surface_currentsboolfalse
force_disable_nf2ffboolfalse
force_disable_save_vtkboolfalse
force_skip_geometry_exportboolfalse
out_dirOptional[Path]none
exportsExportSpecfactory

The exports block selects what is written after the run:

FieldTypeDefault
sparamsstrall
sparams_touchstoneboolfalse
smithboolfalse
polarboolfalse
polar_freq_mhzOptional[float]none
rad3dboolfalse
rad3d_freq_mhzOptional[float]none
rad3d_vtkboolfalse
rad_pattern_csvboolfalse
mesh_previewboolfalse
mesh_preview_vtkboolfalse
mesh_preview_sizeOptional[int]none
surface_currentsboolfalse
surface_currents_freq_mhzOptional[float]none
no_3d_rendersboolfalse

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-mb estimate 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.