# Configuration recipes

## Automatic baseline

The fork's automatic setup mirrors the cell-count behavior used by the pinned Tidy3D scenes and fails early on an excessive voxel count.

```python
import fdtdx

wavelength = 1.55e-6
size = (8e-6, 5e-6, 0.22e-6)

config = fdtdx.auto_config(
    wavelength=wavelength,
    domain_size=size,
    time=300e-15,
    ppw=20,
    max_voxels=40_000_000,
)
boundaries = fdtdx.auto_boundary_config(pml_layers=10)
```

`auto_grid` chooses a target $\Delta=\lambda/\text{ppw}$, rounds each axis up to an integer number of cells, and adjusts per-axis spacing to cover the exact domain. Supply `dl=` to override the wavelength rule.

## Execution backend

`SimulationConfig.execution_mode` defaults to `"jax"`, the complete portable
solver. `"auto"` may select the optional typed CUDA backend when a validated
native library is installed and the scene uses only supported physics;
otherwise it falls back to JAX. `"cuda"` requires the native path and reports
unsupported sources, boundaries, monitors, or materials instead of changing
the scene silently. CPML and differentiated simulations currently stay on JAX.

```{admonition} Refractive index
:class: warning

`ppw` is based on the free-space wavelength passed to the function. To guarantee a chosen number of points inside index $n_\max$, pass `dl=wavelength/(ppw*n_max)` or adjust the wavelength input intentionally.
```

## Boundary overrides

```python
boundaries = fdtdx.auto_boundary_config(
    pml_layers=12,
    boundary_types={
        "min_z": "periodic",
        "max_z": "periodic",
        "min_x": "pec",
    },
)
```

Valid automatic boundary names are `pml`, `periodic`, `pec`, `pmc`, and `bloch`. For a nonzero Bloch vector, construct the corresponding boundary explicitly.

## Symmetry reduction

```python
config = config.aset("symmetry", (0, -1, 0))
```

Entries are `(x, y, z)` with `0` for none, `-1` for PEC/electric parity, and `+1` for PMC/magnetic parity. A reduced axis must resolve to an even number of cells. Reconstruct full arrays with `unfold_fields` and detector state with `unfold_detector_states`.

## Gradient configuration

Checkpointed differentiation:

```python
gradient = fdtdx.GradientConfig(method="checkpointed", num_checkpoints=8)
config = config.aset("gradient_config", gradient)
```

Reversible differentiation requires a `Recorder` and initialized recording state. `num_checkpoints_reversible` can place exact full-field anchors within the reverse reconstruction, improving robustness in lossy media at additional memory cost.

For a real, lossless dielectric design observed with phasor monitors, use the exact design-local adjoint and provide the reduced-grid spatial bounds of the only differentiable inverse-permittivity region:

```python
design_region = design_object.grid_slice_tuple
gradient = fdtdx.GradientConfig(
    method="adjoint",
    design_region=design_region,
)
config = config.aset("gradient_config", gradient)
arrays = arrays.aset("recording_state", None)
```

The adjoint returns zero inverse-permittivity gradient outside `design_region` and fails closed when its linear, lossless assumptions are not met. Use checkpointed differentiation for general material physics, or reversible differentiation when a compact design region is unavailable. Unrolling several adjacent `lax.scan` steps was tested but is intentionally not exposed: it increased compile size and made the representative inverse-design workload slower.

## Differentiable extruded thickness

`slab_fill_fractions` accepts a traced scalar thickness and returns exact
fractional Yee-cell overlaps. This adds a global layer dimension to an
extruded 2-D inverse design without making every z voxel independent:

```python
thickness = lower + (upper - lower) * jax.nn.sigmoid(thickness_logit)
z_fill = fdtdx.slab_fill_fractions(z_edges, thickness=thickness)
rho_xyz = fdtdx.extrude_density_2d(rho_xy, z_fill)
```

At a horizontal dielectric interface, use arithmetic permittivity mixing for
the tangential Ex/Ey constitutive components and harmonic mixing for the
normal Ez component. The overlap function defines a centered shape derivative
when a face lies exactly on a cell edge, so a grid-aligned starting thickness
does not receive a one-sided or vanishing gradient.

The live Fourier atom-hole campaign exposes two sigmoid-bounded dimensions:
the Si3N4 core and one SiO2 thickness shared by the symmetry-related top and
bottom claddings. Their default ranges can be changed with
`FDTDX_FOURIER_HOLE_CORE_MIN_NM`, `FDTDX_FOURIER_HOLE_CORE_MAX_NM`,
`FDTDX_FOURIER_HOLE_CLADDING_MIN_NM`, and
`FDTDX_FOURIER_HOLE_CLADDING_MAX_NM`. Set
`FDTDX_FOURIER_HOLE_OPTIMIZE_THICKNESS=0` to retain fixed layers.

## Memory estimate

A field-only lower bound is six components × voxel count × bytes per value. Real runs also hold material arrays, PML state, dispersive poles, detectors, compiler buffers, and—during differentiation—recording/checkpoint data. Keep meaningful headroom below physical VRAM and measure peak allocation on the actual scene.
