Skip to content

Materials

ph.materials is the built-in dispersive material library. Each entry is a Material with a fitted dispersion model; call it at a frequency or band to get a Medium for a Structure.

Material model

Material dataclass

Material(
    name: str,
    model: _IndexModel,
    valid_range_um: Tuple[float, float],
    reference: str,
    comments: str = "",
)

A named optical material: a literature dispersion model plus its citation and validity range, convertible to engine Medium objects.

name instance-attribute

name: str

model instance-attribute

model: _IndexModel

valid_range_um instance-attribute

valid_range_um: Tuple[float, float]

reference instance-attribute

reference: str

comments class-attribute instance-attribute

comments: str = ''

n

n(wlen_um) -> Union[float, ndarray]

Real refractive index at wlen_um (µm; scalar or array).

k

k(wlen_um) -> Union[float, ndarray]

Extinction coefficient at wlen_um (µm).

eps

eps(wlen_um) -> Union[complex, ndarray]

Complex relative permittivity (n + ik)^2 under the engine's e^{-i omega t} convention (Im eps >= 0 is absorbing).

medium

medium(
    wlen_um: Optional[float] = None,
    *,
    band_um: Optional[Tuple[float, float]] = None,
    **fit_kwargs,
) -> Medium

An engine Medium for this material.

Exactly one of wlen_um (non-dispersive, frozen at that wavelength, permittivity n^2 - k^2, absorption mapped to Ohmic conductivity) or band_um (dispersive single-pole Lorentz fit over the band, see lorentz_fit; extra keyword arguments are forwarded to it) must be given.

lorentz_fit

lorentz_fit(
    band_um: Tuple[float, float],
    *,
    pole_wlen_um: Optional[float] = None,
    num_samples: int = 128,
    min_n_inf_ratio: float = 0.7,
) -> LorentzFit

Fit the engine's single lossless Lorentz pole (+ eps_inf) to this material's real permittivity over band_um.

For each candidate pole placement the model is linear in (eps_inf, delta_eps) and solved by least squares; the placement is scanned on both sides of the band (a pole INSIDE the band is a singularity) and the best feasible fit wins, with near-ties broken toward the longest pole wavelength (lowest omega0, the largest ADE stability margin). Feasibility enforces the wire bound eps_inf >= 1, passivity delta_eps >= 0, and n_inf >= min_n_inf_ratio * min(n_band), the Courant-edge guard learned from the c-Si benchmark work (an unconstrained near-UV pole drives eps_inf toward 1 and the run diverges).

pole_wlen_um pins the pole instead of scanning (the c-Si benchmark's hand placement at 0.6 µm is reproduced this way).

Absorption is NOT fitted by the pole: any tabulated k becomes the band-centre Ohmic conductivity on the resulting medium.

If no feasible pole exists (e.g. the band slope is anomalous, which a passive lossless pole cannot produce), a degenerate constant fit is returned (delta_eps = 0; the medium carries no pole) with a warning.

pole_fit

pole_fit(
    band_um: Tuple[float, float],
    *,
    n_lorentz: int = 2,
    drude: bool = False,
    num_samples: int = 128,
) -> PoleFit

Fit the material's COMPLEX permittivity over band_um with n_lorentz Lorentz poles plus (optionally) one Drude term, the schema-1.17 multi-pole fit for metals and wideband dielectrics.

Where lorentz_fit fits Re eps with ONE lossless pole and moves absorption into a band-centre Ohmic sigma, this fits real AND imaginary parts together with damped poles (passivity enforced through the bounds: delta_eps >= 0, gamma >= 0, wp > 0). Deterministic multi-start least squares (log-spaced pole ladders on both sides of the band; no RNG). Raises when the request exceeds the engine's pole budget or scipy is unavailable.

Returns a PoleFit; use fit.medium in structures, check fit.max_rel_eps_error and fit.max_dl_um(courant) before running.

from_nk_data classmethod

from_nk_data(
    name: str,
    *,
    wlen_um: Sequence[float],
    n: Sequence[float],
    k: Optional[Sequence[float]] = None,
    reference: str = "user-supplied measurement data",
    comments: str = "",
) -> "Material"

Wrap measured (lambda, n[, k]) samples (e.g. ellipsometry) as a Material; validity range = the table's span.

uniaxial_medium

uniaxial_medium(
    ordinary: "Material",
    extraordinary: "Material",
    wlen_um: float,
    *,
    optic_axis: str = "z",
) -> Medium

A DIAGONAL anisotropic Medium (schema 1.18 permittivity_xyz) for a uniaxial crystal frozen at one wavelength: the ordinary index on the two transverse axes, the extraordinary index on optic_axis. This is what the split _o / _e library entries exist for:

lno = uniaxial_medium(LiNbO3_o, LiNbO3_e, 1.55, optic_axis="z")

Tabulated absorption is not mapped: the result is a lossless diagonal tensor. The engine does accept a scalar conductivity_s_per_m on an anisotropic medium, applied equally to all three axes. The scalar permittivity carries the ordinary value (required by the wire, ignored by the engine when permittivity_xyz is set).

Dispersion models

LorentzFit dataclass

LorentzFit(
    material_name: str,
    band_um: Tuple[float, float],
    eps_inf: float,
    delta_eps: float,
    resonance_frequency_hz: Optional[float],
    conductivity_s_per_m: float,
    max_abs_n_error: float,
)

A single-pole Lorentz fit of a material over a wavelength band, ready to become a dispersive Medium (.medium).

eps(omega) = eps_inf + delta_eps * omega0^2 / (omega0^2 - omega^2) (lossless pole; any tabulated absorption is carried separately as the band-centre Ohmic conductivity_s_per_m). A degenerate fit (delta_eps == 0, e.g. anomalous band slope no passive lossless pole can produce, or negligible dispersion) yields a constant-index medium with lorentz=None.

The fit is an IN-BAND surrogate: the pole placement is numerical, not the material's physical resonance, so the model has no meaning outside band_um (weakly-dispersive materials often get a far-IR pole whose static eps(0) = eps_inf + delta_eps is wildly unphysical, irrelevant for a source whose spectrum lives in the band).

material_name instance-attribute

material_name: str

band_um instance-attribute

band_um: Tuple[float, float]

eps_inf instance-attribute

eps_inf: float

delta_eps instance-attribute

delta_eps: float

resonance_frequency_hz instance-attribute

resonance_frequency_hz: Optional[float]

conductivity_s_per_m instance-attribute

conductivity_s_per_m: float

max_abs_n_error instance-attribute

max_abs_n_error: float

pole_wavelength_um property

pole_wavelength_um: Optional[float]

Pole wavelength in microns, or None when no resonance is fitted.

pole property

pole: Optional[LorentzPole]

Construct the fitted undamped Lorentz pole, or None for a degenerate fit.

medium property

medium: Medium

Construct a Medium from the fitted permittivity, conductivity, and pole.

eps_model

eps_model(wavelength_um) -> ndarray

The fitted real permittivity at wavelength_um (µm).

n_model

n_model(wavelength_um) -> ndarray

The fitted real index at wavelength_um (µm).

omega0_dt

omega0_dt(dl_um: float, courant: float = 0.99) -> float

The pole's resonance product omega0 * dt at grid spacing dl_um (µm) with the engine's 3-D Courant timestep dt = courant * dl / (c0 * sqrt(3)). omega0 * dt < 2 is only the pole's own bound; the binding ADE rule is the discrete-Nyquist inequality (eps_inf - delta_eps*x/(1-x) >= courant^2, x = (omega0*dt/2)^2), which max_dl_um applies. NB: a quasi-2D run (one 1-cell plain periodic axis) steps sqrt(3/2) LARGER than this 3-D estimate: validate() remains authoritative there.

max_dl_um

max_dl_um(
    courant: float = 0.99, active_axes: int = 3
) -> float

The coarsest uniform grid spacing (µm) that meets the discrete-Nyquist ADE bound eps_inf - delta_eps*x/(1-x) >= courant^2 (x = (omega0*dt/2)^2), which implies omega0 * dt < 2. active_axes is the number of axes with more than one cell (2 for quasi-2-D, whose timestep is sqrt(3/2) larger). Finer grids only lower dt, so any dl below this is ADE-safe; validate() is authoritative on a graded grid.

Sellmeier dataclass

Sellmeier(
    a0: float, terms: Tuple[Tuple[float, float], ...]
)

Bases: _IndexModel

n^2 = a0 + sum B_i * lam^2 / (lam^2 - C_i) with lam in µm and C_i in µm^2 (refractiveindex.info "formula 1" terms enter with C_i = coeff^2, "formula 2" terms verbatim).

a0 instance-attribute

a0: float

terms instance-attribute

terms: Tuple[Tuple[float, float], ...]

n2

n2(wlen_um)

Evaluate the Sellmeier squared index at wavelengths in microns.

Polynomial dataclass

Polynomial(terms: Tuple[Tuple[float, float], ...])

Bases: _IndexModel

n^2 = sum c_i * lam^p_i (refractiveindex.info "formula 3").

terms instance-attribute

terms: Tuple[Tuple[float, float], ...]

n2

n2(wlen_um)

Evaluate the squared-index polynomial at wavelengths in microns.

TabulatedNK dataclass

TabulatedNK(
    wavelength_um: Tuple[float, ...],
    n: Tuple[float, ...],
    k_table: Optional[Tuple[float, ...]] = None,
)

Bases: _IndexModel

Measured (lambda, n[, k]) samples, linearly interpolated. Wavelengths must be strictly ascending; queries outside the table raise via the owning Material's range check.

wavelength_um instance-attribute

wavelength_um: Tuple[float, ...]

n instance-attribute

n: Tuple[float, ...]

k_table class-attribute instance-attribute

k_table: Optional[Tuple[float, ...]] = None

n2

n2(wlen_um)

Square the linearly interpolated index at wavelengths in microns.

Direct calls use endpoint values outside the table. Material checks its valid range.

k

k(wlen_um)

Linearly interpolate the extinction coefficient at wavelengths in microns.

Return zeros when no extinction table was supplied. Direct calls use endpoint values outside the table; Material checks its valid range.

ConstantIndex dataclass

ConstantIndex(n_const: float, k_const: float = 0.0)

Bases: _IndexModel

Wavelength-independent n (and optional k).

n_const instance-attribute

n_const: float

k_const class-attribute instance-attribute

k_const: float = 0.0

n2

n2(wlen_um)

Constant squared refractive index, broadcast to the supplied wavelength shape.

k

k(wlen_um)

Constant extinction coefficient, broadcast to the supplied wavelength shape.

Registry

MATERIALS module-attribute

MATERIALS = {
    m.name: m
    for m in (
        Vacuum,
        Si,
        SiO2,
        Si3N4,
        GaAs,
        InP,
        Ge,
        Sapphire,
        AlN,
        TiO2,
        LiNbO3_o,
        LiNbO3_e,
        MgF2_o,
        MgF2_e,
        CaF2,
        PMMA,
        Au,
        Ag,
        Cu,
        Al,
    )
}

get

get(name: str) -> Material

Look up a built-in material by name (see MATERIALS for the list).

Built-in materials

Vacuum module-attribute

Vacuum

Si module-attribute

Si

SiO2 module-attribute

SiO2

Si3N4 module-attribute

Si3N4

GaAs module-attribute

GaAs

InP module-attribute

InP

Ge module-attribute

Ge

Sapphire module-attribute

Sapphire

AlN module-attribute

AlN

TiO2 module-attribute

TiO2

LiNbO3_o module-attribute

LiNbO3_o

LiNbO3_e module-attribute

LiNbO3_e

MgF2_o module-attribute

MgF2_o

MgF2_e module-attribute

MgF2_e

CaF2 module-attribute

CaF2

PMMA module-attribute

PMMA

Au module-attribute

Au

Ag module-attribute

Ag

Cu module-attribute

Cu

Al module-attribute

Al