# Automation

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

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

These helpers propose a mesh, domain, absorber and run length from your
geometry and frequency range, and build graded cell profiles. Use them for a
first setup, then read what they chose and check it with preflight. For when
to grade a mesh at all, see [Non-Uniform Mesh](https://remilab.ai/rfx/model/domain-mesh/).

## Planning from geometry

| Helper | Signature | Returns |
|---|---|---|
| `plan_mesh` | `plan_mesh(geometry, freq_range, materials=None, accuracy="standard", *, boundary="cpml", dx_override=None, margin_override=None, n_steps_override=None, max_memory_mb=None, artifact_root=None)` | `MeshPlan` |
| `auto_configure` | `auto_configure(geometry, freq_range, materials=None, accuracy="standard", *, boundary="cpml", dx_override=None, margin_override=None, n_steps_override=None, max_memory_mb=None, waveform=None, thin_conductors=())` | `SimConfig` |

- `geometry`: a list of `(shape, material_name)` pairs.
- `freq_range`: `(f_min, f_max)` in Hz. `f_max` sets the cell size, `f_min`
  the margin around the geometry.
- `materials`: `{name: {"eps_r": ..., "sigma": ...}}` for every material in
  `geometry`. Pass it even for library names such as `"fr4"`: a material the
  planner cannot find is treated as vacuum, and the cell comes out too
  coarse.
- `accuracy`: `"draft"` (10 cells per wavelength), `"standard"` (20) or
  `"high"` (40). The thinnest feature can force a finer cell than that.
- `max_memory_mb`: coarsen `dx` until the estimated memory fits.
- `thin_conductors`: declared sheets. Each sheet's plane becomes a z mesh
  line, so the sheet lands where you drew it.
- A thin z feature makes the plan grade z and return a `dz_profile`.

`plan_mesh` wraps `auto_configure` and adds a serializable record of what it
chose. Prefer it.

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

geometry = [(Box((0.0, 0.0, 0.0), (0.02, 0.01, 0.0016)), "fr4")]
materials = {"fr4": {"eps_r": 4.4, "sigma": 0.025}}
plan = plan_mesh(geometry, freq_range=(1.5e9, 3.5e9), materials=materials,
                 accuracy="standard")
print(plan.to_markdown())

kwargs = plan.to_sim_kwargs()   # freq_max, domain, boundary, cpml_layers, dx (+ dz_profile)
sim = Simulation(**kwargs)
print(sorted(kwargs))
```

`MeshPlan` holds the derived `domain`, `cell_sizes`, `cfl`, `absorber`,
`memory`, `grid_shape`, `support_checks` and `warnings`. Export it with
`to_dict()`, `to_json()` or `to_markdown()`. Values rfx could not determine are
`None`, not missing. `artifact_root` only records intended output paths; it
writes nothing.

`to_sim_kwargs()` gives the constructor arguments only. It does not add your
geometry or move it into the new domain. Add the shapes yourself, a few cells
in from the domain edges (where the absorber begins) and with thin layers aligned to the final profile's cell
edges, then run `sim.preflight()` and resolve what it reports.
`SimConfig.summary()` prints the same choices as text.

`sim.plan_mesh(...)` is the equivalent for a simulation you have already built.
It also runs preflight and can check an S-parameter calculator; see
[Simulation](https://remilab.ai/rfx/api/simulation/#checking-before-you-run).
`Simulation.auto(freq_range, ...)` builds a simulation from the frequency
range alone, with no geometry; see
[Simulation](https://remilab.ai/rfx/api/simulation/#simulationauto).

## Building graded profiles by hand

| Helper | Signature | What it does |
|---|---|---|
| `make_band_profile` | `make_band_profile(edges, cell_sizes, *, max_ratio=1.4, protected=None, boundary_cell=None, min_cells=1)` | Builds a profile from a declared stack. Every interface in `edges` lands exactly on a node plane, and every adjacent ratio stays at or below `max_ratio`. |
| `smooth_grading` | `smooth_grading(cells, max_ratio=1.3, *, preserve_regions=None)` | Inserts transition cells so no adjacent ratio exceeds `max_ratio`. |
| `apply_thirds_rule` | `apply_thirds_rule(cells, boundary_indices)` | Splits the cells at the given material interfaces into 1/3 + 2/3 pairs. |

```python
from rfx import apply_thirds_rule, make_band_profile, smooth_grading

# 10 mm of 1 mm air, a 1.5 mm substrate at 0.25 mm, 13.5 mm of 1 mm air.
dz = make_band_profile([0.0, 0.010, 0.0115, 0.025], [1e-3, 0.25e-3, 1e-3],
                       max_ratio=1.4, protected=[False, True, False])
print(len(dz), f"{dz.sum() * 1e3:.3f} mm", f"max ratio {np.max(dz[1:] / dz[:-1]):.2f}")

graded = smooth_grading([1e-3] * 4 + [0.25e-3] * 6 + [1e-3] * 4, max_ratio=1.3)
print(len(graded), f"{graded.sum() * 1e3:.3f} mm")   # longer: cells were inserted
```

`make_band_profile` is the one to use when layer faces must sit on grid
planes: the total length and every interface are exact. `smooth_grading`
**adds** cells, so the total length and the positions after the inserted
cells change. Recompute your layer coordinates from the final profile's
cumulative sum. Use `max_ratio=1.4` along z and 1.3 in-plane; those are the
ratios rfx has evidence for, and preflight flags larger ones.

## Limits

- The plan is a starting point, not a validation. It has not run
  `Simulation.preflight()` for geometry you add later, nor a convergence study.
- The cell count per wavelength in `accuracy` is a free-space figure. Check
  that dielectrics still get enough cells: preflight warns below 15 per
  wavelength in the material.
- On a graded mesh, a `Box` whose faces fall between grid planes snaps to the
  nearest one. Preflight reports this as `graded_box_rasterization`. Align
  layers to the profile's cell edges.
- Keep the grading out of the absorber: the first and last `cpml_layers` cells
  of a profile on a CPML axis should be uniform. Preflight warns with
  `nu_grading_reaches_absorber`.
