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.
eps
¶
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).
pole_wavelength_um
property
¶
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.
omega0_dt
¶
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
¶
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
¶
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).
Polynomial
dataclass
¶
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.
n2
¶
Square the linearly interpolated index at wavelengths in microns.
Direct calls use endpoint values outside the table. Material checks
its valid range.
k
¶
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
¶
Bases: _IndexModel
Wavelength-independent n (and optional k).
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,
)
}