# Geometry and Materials

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

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

This page lists the calls that define what is in the domain: materials,
shapes, metal sheets and dispersive media. The guide
[Materials and Geometry](https://remilab.ai/rfx/model/materials/) explains how shapes
land on the grid and which kind of conductor to use. All lengths are in metres
and conductivity is in S/m.

## Materials

```text
Simulation.add_material(name, *, eps_r=1.0, sigma=0.0, mu_r=1.0,
                        debye_poles=None, lorentz_poles=None, chi3=0.0)
    -> Simulation
```

Registers a named, isotropic material. Use the name in `add(...)`.

```python
import numpy as np
from rfx import (Box, Cylinder, DebyePole, PolylineWire, Simulation, Sphere,
                 Via, drude_pole, lorentz_pole)

sim = Simulation(freq_max=5e9, domain=(0.04, 0.03, 0.02), dx=1e-3,
                 boundary="cpml", cpml_layers=6)

sim.add_material("substrate", eps_r=4.3, sigma=0.02)
sim.add_material("lossy_block", sigma=5.0e5)            # below the PEC threshold
sim.add_material("water_body", eps_r=4.9,
                 debye_poles=[DebyePole(delta_eps=74.1, tau=8.3e-12)])
sim.add_material("resonant", lorentz_poles=[
    lorentz_pole(delta_eps=2.0, omega_0=2 * np.pi * 10e9, delta=1e8)])
sim.add_material("plasma",                              # 10 GHz plasma frequency
                 lorentz_poles=[drude_pole(omega_p=2 * np.pi * 10e9, gamma=2 * np.pi * 1e9)])
```

| Argument | Meaning |
|---|---|
| `eps_r` | relative permittivity; for a dispersive material this is ε∞ |
| `sigma` | conductivity in S/m. At or above `1e6` the material is a perfect conductor (see below). |
| `mu_r` | relative permeability |
| `debye_poles` | list of `DebyePole(delta_eps, tau)`: Δε = εs − ε∞, τ in seconds |
| `lorentz_poles` | list of `LorentzPole`. Build them with `lorentz_pole(delta_eps, omega_0, delta)` or `drude_pole(omega_p, gamma)` (all rates in rad/s) rather than by hand. A pole set above its own time-step limit on the model's cells is refused when the model is assembled (an optical-frequency metal such as gold always is on RF cells). |
| `chi3` | Kerr (third-order) nonlinearity |

**Library materials** need no `add_material` call: `"vacuum"`, `"air"`,
`"fr4"`, `"rogers4003c"`, `"rogers4350b"`, `"rt_duroid_5880"`, `"alumina"`,
`"silicon"`, `"ptfe"`, `"water_20c"`, `"copper"`, `"aluminum"` and `"pec"`.
The values are in `rfx.MATERIAL_LIBRARY`. The dielectric losses there are a
constant conductivity matched at one frequency, so use your laminate's data
for real designs.

**The conductor threshold.** Any material with `sigma >= 1e6` S/m is treated
as PEC: tangential E is forced to zero and the conductivity is not used. So
`"copper"` and `"aluminum"` are lossless PEC, and their skin depth is not
resolved. Keep `sigma` below `1e6` for a lossy bulk medium. For conductor loss
on a metal sheet, use `add_thin_conductor(..., surface_impedance_f0=f0)`.

**Fitting measured data.** `fit_debye(freqs, eps_measured, n_poles=1, *,
eps_inf=None)` and `fit_lorentz(...)` fit poles to complex permittivity data;
`load_material_csv(path, ...)` reads it from a CSV file. See the
[generated reference](https://remilab.ai/rfx/api/generated/).

## Placing shapes

```text
Simulation.add(shape, *, material) -> Simulation
```

Fills the shape with a registered or library material. Shapes are rasterized
in the order you add them. Where dielectrics overlap, the later one wins.
Metal accumulates: a later shape cannot cut a hole in a conductor.

```python
sim.add(Box((0.005, 0.005, 0.004), (0.035, 0.025, 0.0056)), material="substrate")
sim.add(Sphere((0.020, 0.015, 0.012), radius=0.003), material="alumina")
sim.add(Cylinder((0.010, 0.010, 0.0048), radius=0.0005, height=0.0016, axis="z"),
        material="pec")
sim.add(PolylineWire(((0.028, 0.010, 0.008), (0.028, 0.020, 0.008)),
                     radius=0), material="pec")
sim.add(Via(center=(0.015, 0.020), drill_radius=0.3e-3, pad_radius=0.6e-3,
            layers=[(0.004, 0.0056)]), material="pec")
```

| Shape | Signature | Notes |
|---|---|---|
| `Box` | `Box(corner_lo, corner_hi)` | axis-aligned block; a zero-thickness `Box` is a sheet |
| `Sphere` | `Sphere(center, radius)` | |
| `Cylinder` | `Cylinder(center, radius, height, axis="z")` | `center` is the middle of the cylinder, not its base |
| `PolylineWire` | `PolylineWire(points, radius)` | a wire through the given vertices; `radius=0` selects the legacy filament, while a positive PEC radius must resolve as a volume wire |
| `Via` | `Via(*, center, drill_radius, pad_radius, layers, material="pec")` | a PCB via with pads; `center` is `(x, y)` and `layers` is a list of `(z_bottom, z_top)` |
| `CurvedPatch` | `CurvedPatch(*, center, length, width, radius, axis="x")` | a patch bent along `axis`, built from flat segments |
| `MeshShape` | `MeshShape.from_file(path, *, scale, translate=(0.0, 0.0, 0.0))` | CAD import, see below |

`sim.fidelity_report()` prints where every shape actually landed next to what
you drew. Run it before a long solve.

### CAD import

`MeshShape.from_file` reads a watertight STL, OBJ or PLY mesh, or STEP/STP
with the `cad` extra installed (`pip install "rfx-fdtd[cad]"`). `scale` is
required: STL, OBJ and PLY have no units, so pass `1e-3` for a file drawn in
millimetres. STEP files are converted to metres on load, so pass `1.0`. The
mesh is rasterized on the host, so a CAD shape cannot carry a gradient.

## Metal sheets

```text
Simulation.add_thin_conductor(shape, *, sigma_bulk=5.8e7, thickness=35e-6,
                              eps_r=1.0, surface_impedance_f0=None)
    -> Simulation
```

Declares copper foil (ground plane, patch, trace) as a **sheet**: one grid
plane, zero thickness, no cell of its own. Give the shape zero thickness along
its normal.

```python
sim.add_thin_conductor(Box((0.005, 0.005, 0.004), (0.035, 0.025, 0.004)))
sim.add_thin_conductor(Box((0.012, 0.010, 0.0056), (0.028, 0.020, 0.0056)),
                       surface_impedance_f0=3e9)
```

- With the default `sigma_bulk` (copper) the sheet is **lossless PEC**, and rfx
  warns once to say so. `thickness` and `eps_r` are then not used.
- `surface_impedance_f0=f0` adds conductor loss as a surface resistance
  matched at `f0`. The sheet keeps exactly the same footprint, so `f0` changes
  the loss and never the geometry. Combinations the loss model does not
  support raise.
- `sigma_bulk < 1e6` makes a resistive sheet whose sheet conductance is
  `sigma_bulk * thickness`.

A metal plate or wall that really is thick is a **volume**:
`add(Box(...), material="pec")`. A PEC volume has walls at both drawn faces
at any thickness.

The removed `two_plane=True` keyword of `add()` raises `TypeError`. Delete it:
a PEC volume already gets both faces.

## Limits

- Metal with `sigma >= 1e6` S/m is lossless. For ohmic loss, use a sheet with
  `surface_impedance_f0` or a below-threshold `sigma`.
- A shape face between two grid planes snaps to the nearest plane. For thin
  layers, choose `dx` or a graded profile so the faces fall on grid planes, and
  check with `fidelity_report()`.
- A sheet declared through `add_thin_conductor` must be one grid plane thick.
  A PEC shape that is thinner than a cell but not zero is ambiguous; see
  [How conductors land on the lattice](https://remilab.ai/rfx/model/materials/#how-conductors-land-on-the-lattice).
- `MeshShape` geometry is not differentiable.
