# Quick Start

Channel: development; package: 2.0.0rc1; source: 2c32b7cf79b796fae825bfa08fc73d9914e61723

Page: https://remilab.ai/rfx/start/first-simulation/
Source: https://github.com/bk-squared/rfx/blob/2c32b7cf79b796fae825bfa08fc73d9914e61723/docs/public/start/first-simulation.mdx

This page walks through one complete simulation: a Gaussian pulse passing
through a dielectric slab in open space. Every rfx model follows the same six
steps:

1. **Domain and mesh.** Create a `Simulation` with its size, cell size and
   boundaries.
2. **Materials and geometry.** Register materials and place shapes.
3. **Excitation.** Add a source, or a port if you want S-parameters.
4. **Observables.** Add probes, monitors or far-field boxes.
5. **Preflight.** Let rfx check the setup before you spend time on a solve.
6. **Run and read.** Call `run()` and read the returned `Result`.

The example runs in a few seconds on a laptop CPU.

## 1. Domain and mesh

```python
import numpy as np
from rfx import Simulation, Box, GaussianPulse

sim = Simulation(
    freq_max=5e9,                # highest frequency you care about (Hz)
    domain=(0.06, 0.04, 0.04),   # x, y, z size in metres
    dx=2e-3,                     # cubic cell size in metres
    boundary="cpml",             # absorbing (open) boundary on every face
    cpml_layers=8,               # absorber thickness in cells
)
```

- All lengths are in **metres**, frequencies in **hertz**, and conductivity in
  **S/m**.
- `freq_max` does two things. It sets the default time step, and it is the
  frequency preflight uses to judge whether the mesh is fine enough.
- The CPML absorber is added **outside** the `domain` you give, so `domain`
  is the region you model. Structures that reach the domain edge continue into
  the absorber, which is how an infinite slab or a line leaving the box is
  modelled. Keep sources and probes a few cells in from the edge.

## 2. Materials and geometry

```python
sim.add_material("slab", eps_r=3.0, sigma=0.005)
sim.add(Box((0.028, 0.0, 0.0), (0.036, 0.04, 0.04)), material="slab")
```

`Box(corner_lo, corner_hi)` is an axis-aligned block. Here the slab is 8 mm
thick along x and spans the whole cross-section. Library materials such as
`"fr4"`, `"rogers4350b"` and `"pec"` need no `add_material` call. See
[Materials and Geometry](https://remilab.ai/rfx/model/materials/).

## 3. Excitation

```python
sim.add_source(
    (0.014, 0.02, 0.02),
    "ez",
    waveform=GaussianPulse(f0=3e9, bandwidth=0.8),
    amplitude_kind="current",
)
```

This is a soft point source that drives `Ez` in one cell. `amplitude_kind="current"`
means the waveform amplitude is a current in amperes. Always pass it
explicitly. A point source has no impedance, so it gives fields, resonances and
far fields but **not** S-parameters. Use a port for those; see
[Sources and Ports](https://remilab.ai/rfx/model/ports/).

## 4. Observables

```python
sim.add_probe((0.020, 0.02, 0.02), "ez")   # probe 0: between source and slab
sim.add_probe((0.046, 0.02, 0.02), "ez")   # probe 1: behind the slab
```

A probe records one field component at one point for every time step.

## 5. Preflight

```python
report = sim.preflight()
print(report.format())
report.raise_for_failure()
```

Preflight looks for mistakes that give a wrong answer without crashing:
- a mesh too coarse for the material and `freq_max`;
- structures or probes inside the absorber;
- geometry that does not land where you drew it;
- a lossless resonator whose Q can never converge.

Errors stop the run. Warnings are advisory, but read every one of them.
`run()` repeats the check and prints any warnings.

Try `eps_r=4.0` on the slab. At 2 mm cells that leaves exactly 15 cells per
wavelength inside the dielectric, and preflight will warn about phase accuracy.

## 6. Run and read the result

```python
result = sim.run(n_steps=1000)

ts = np.asarray(result.time_series)      # shape (n_steps, n_probes)
print(ts.shape, f"dt = {result.dt:.3e} s")

spectrum = np.abs(np.fft.rfft(ts, axis=0))
freqs = np.fft.rfftfreq(ts.shape[0], d=result.dt)
in_band = (freqs > 1e9) & (freqs < 5e9)
ratio = spectrum[in_band, 1] / spectrum[in_band, 0]
print("settling:", result.settling_witness["status"])   # "absent" here: nothing to judge
```

- `result.time_series` has one column per probe, in registration order.
- `result.settling_db` judges whether the record was long enough for the
  spectral results the run computed (S-parameters, DFT planes, flux monitors,
  far fields). After the source ends, rfx fits the poles still ringing in the
  record and estimates, at every frequency those results read, how much of the
  value comes from what rings on past the record's end. `settling_db` is the
  worst of those shares in dB; at or below −40 dB the record is long enough.
  `result.settling_witness["status"]` says `pass`, `fail`, `undetermined`
  (the source was still on, or the fit could not be judged) or `absent`
  (nothing the run reads was recorded to judge, as here). Only `pass` is a
  pass.
- This run reads only probe time series, so the check is `absent` and the FFT
  above is yours to judge: if the probe signals have not decayed to a small
  fraction of their peak by the last step, run longer.

The ratio above only compares the spectra at two points. It is **not** a
transmission coefficient, because probe 0 sees the incident and reflected
waves together. For reflection and transmission, use two flux monitors and a
reference run without the slab. The
[Probes and S-Parameters](https://remilab.ai/rfx/measure/s-parameters/) guide shows how.

## Choosing the run length

`run()` offers three ways to decide how long to step:

| Argument | Meaning |
|---|---|
| `n_steps=N` | exactly N time steps |
| `num_periods=P` | P periods at `freq_max` (default 20) |
| `until_decay=1e-3` | step until the field energy has decayed to that fraction of its peak, up to `decay_max_steps` |

With absorbing boundaries, `until_decay` watches the total energy inside the
domain, which is a reliable stop for spectra, flux and S-parameters. A closed
PEC box never loses energy, so there it falls back to a single field sample
(on a graded mesh, a closed-boundary `until_decay` is refused instead).
That fallback is only reliable for lossy or radiating ring-down, so use a fixed
`n_steps` in a closed structure. Also use a fixed `n_steps` when you compare
runs, for example in a convergence study.

## Where to go next

- [Core Concepts](https://remilab.ai/rfx/start/concepts/): the mental model behind the six steps,
  including cells, boundaries, conductors, sources and ports.
- [Your First Patch Antenna](https://remilab.ai/rfx/examples/first-patch/): a real device on a
  graded mesh.
- [Examples](https://remilab.ai/rfx/examples/): the ordered learning path of runnable scripts.
