Skip to content

Materials

materials

Optical material models.

Convention (used everywhere in lightsmith): time dependence exp(-iomegat), so absorbing materials have Im(eps) > 0 and Im(n) > 0. Backends that use the opposite convention convert at their boundary.

Measured data come from the refractiveindex.info database (CC0 1.0, https://github.com/polyanskiy/refractiveindex.info-database). A small, cited subset ships with the package ({"kind": "library", "name": "Au", "dataset": "Johnson-Christy"}, see :func:list_materials); any other entry can be downloaded with :func:fetch_refractiveindex.

HC_EV_NM module-attribute

HC_EV_NM = 1239.841984

RII_REPOSITORY module-attribute

RII_REPOSITORY = 'https://github.com/polyanskiy/refractiveindex.info-database'

RII_RAW_URL module-attribute

RII_RAW_URL = 'https://raw.githubusercontent.com/polyanskiy/refractiveindex.info-database/{ref}/database/{path}'

RII_PAGE_URL module-attribute

RII_PAGE_URL = 'https://refractiveindex.info/?shelf={shelf}&book={book}&page={page}'

RII_CITATION module-attribute

RII_CITATION = 'M. N. Polyanskiy, Sci. Data 11, 94 (2024), doi:10.1038/s41597-023-02898-2'

Material module-attribute

Material = Annotated[Union[ConstantIndex, ConstantPermittivity, Drude, DrudeLorentz, Tabulated, LibraryMaterial], Field(discriminator='kind')]

MaterialRangeError

Bases: ValueError

A wavelength lies outside the range where a material's data are valid.

ConstantIndex

Bases: _Material

Wavelength-independent complex refractive index n + ik.

kind class-attribute instance-attribute

kind: Literal['constant_index'] = 'constant_index'

n_real class-attribute instance-attribute

n_real: float = Field(description='Real part of the refractive index.')

n_imag class-attribute instance-attribute

n_imag: float = Field(0.0, ge=0, description='Extinction coefficient k (>= 0 for loss).')

eps

eps(wavelength_nm)

ConstantPermittivity

Bases: _Material

Wavelength-independent complex relative permittivity.

kind class-attribute instance-attribute

kind: Literal['constant_permittivity'] = 'constant_permittivity'

eps_real instance-attribute

eps_real: float

eps_imag class-attribute instance-attribute

eps_imag: float = Field(0.0, ge=0)

eps

eps(wavelength_nm)

Drude

Bases: _Material

eps(w) = eps_inf - wp^2 / (w^2 + igammaw), energies in eV.

kind class-attribute instance-attribute

kind: Literal['drude'] = 'drude'

eps_inf class-attribute instance-attribute

eps_inf: float = 1.0

wp_ev class-attribute instance-attribute

wp_ev: float = Field(gt=0, description='Plasma frequency in eV.')

gamma_ev class-attribute instance-attribute

gamma_ev: float = Field(ge=0, description='Damping rate in eV.')

eps

eps(wavelength_nm)

LorentzPole

Bases: BaseModel

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='forbid', frozen=True)

delta_eps class-attribute instance-attribute

delta_eps: float = Field(description='Oscillator strength (permittivity step).')

omega0_ev class-attribute instance-attribute

omega0_ev: float = Field(gt=0, description='Resonance energy in eV.')

gamma_ev class-attribute instance-attribute

gamma_ev: float = Field(ge=0, description='Damping in eV.')

DrudeLorentz

Bases: _Material

eps(w) = eps_inf - wp^2/(w^2 + i g w) + sum_j d_j w0_j^2 / (w0_j^2 - w^2 - i G_j w).

The form FDTD needs for auxiliary-differential-equation dispersion. Fit one to measured data with :func:lightsmith.materials_fit.fit_drude_lorentz.

kind class-attribute instance-attribute

kind: Literal['drude_lorentz'] = 'drude_lorentz'

eps_inf class-attribute instance-attribute

eps_inf: float = 1.0

wp_ev class-attribute instance-attribute

wp_ev: float = Field(0.0, ge=0)

gamma_ev class-attribute instance-attribute

gamma_ev: float = Field(0.0, ge=0)

poles class-attribute instance-attribute

poles: list[LorentzPole] = Field(default_factory=list)

eps

eps(wavelength_nm)

Tabulated

Bases: _Material

Tabulated n, k data, linearly interpolated (no extrapolation).

kind class-attribute instance-attribute

kind: Literal['tabulated'] = 'tabulated'

wavelength_nm instance-attribute

wavelength_nm: list[float]

n_real instance-attribute

n_real: list[float]

n_imag instance-attribute

n_imag: list[float]

source class-attribute instance-attribute

source: str | None = Field(None, description='Citation / provenance of the data.')

range_nm property

range_nm: tuple[float, float]

from_csv classmethod

from_csv(path: str | Path, unit: Literal['nm', 'um'] = 'nm', source: str | None = None)

Load a 3-column CSV (wavelength, n, k), e.g. a refractiveindex.info export.

eps

eps(wavelength_nm)

LibraryMaterial

Bases: _Material

Measured n, k bundled with lightsmith, e.g. {"kind": "library", "name": "Au", "dataset": "Johnson-Christy"}. Linear interpolation in n and k, no extrapolation.

Names and datasets accept aliases ("gold", "JC") and are stored in canonical form; an omitted dataset is filled with the material's default so the spec stays reproducible. See :func:list_materials.

kind class-attribute instance-attribute

kind: Literal['library'] = 'library'

name class-attribute instance-attribute

name: str = Field(description="Material, e.g. 'Au', 'Ag', 'Al', 'Cu', 'Si', 'TiO2', 'SiO2', 'H2O'.")

dataset class-attribute instance-attribute

dataset: str | None = Field(None, description="Dataset, e.g. 'Johnson-Christy'; omitted = material default.")

range_nm property

range_nm: tuple[float, float]

source property

source: str

tabulated

tabulated() -> Tabulated

eps

eps(wavelength_nm)

read_refractiveindex_yaml

read_refractiveindex_yaml(path_or_text: str | Path, source: str | None = None) -> Tabulated

Load one refractiveindex.info YAML entry (a file path or its text) as :class:Tabulated.

default_cache_dir

default_cache_dir() -> Path

User cache directory for downloaded data (LIGHTSMITH_CACHE_DIR overrides).

fetch_refractiveindex

fetch_refractiveindex(shelf: str, book: str, page: str, *, cache_dir: str | Path | None = None, refresh: bool = False, ref: str = 'main', timeout: float = 30.0) -> Tabulated

Download one refractiveindex.info entry (e.g. "main", "Au", "Olmon-sc") as :class:Tabulated.

Names are those in the refractiveindex.info URL (?shelf=main&book=Au&page=Olmon-sc). The YAML is cached under :func:default_cache_dir (or cache_dir); later calls work offline. ref is a branch, tag or commit of the database repository. Tabulated n/k and formula types 1-9 are supported. The database is CC0; cite the dataset's own reference (kept in .source) and Polyanskiy, Sci. Data 11, 94 (2024).

list_materials

list_materials(name: str | None = None) -> list[dict]

Bundled datasets (no network needed): one dict per dataset with its name, range and citation.

Each row has material_json, ready to drop into a :class:Simulation.

load_material

load_material(name: str, dataset: str | None = None) -> Tabulated

Bundled dataset as a :class:Tabulated (e.g. load_material("Au", "Johnson-Christy")).

materials_fit

Fit measured permittivity with a passive Drude-Lorentz model.

FDTD needs a rational permittivity it can integrate with auxiliary differential equations; measured (tabulated) data are not usable directly. :func:fit_drude_lorentz turns any material into a :class:DrudeLorentz::

from lightsmith.materials_fit import fit_drude_lorentz, fit_quality

wl = np.linspace(450, 900, 91)
dl = fit_drude_lorentz({"kind": "library", "name": "Au"}, wl)
fit_quality(dl, "Au", wl)["rms_rel_error"]    # ~0.01 (JC data)

Every parameter is constrained to be physical: eps_inf >= 1, and plasma frequency, Drude damping, oscillator strengths, resonances and pole dampings are all strictly positive, so Im(eps) > 0 at every positive frequency (passive, hence stable in FDTD). The fit is a bounded nonlinear least-squares fit (scipy least_squares) of eps in eV, started from non-negative linear fits and continued pole by pole, so it does not depend on a user guess.

fit_drude_lorentz

fit_drude_lorentz(material, wavelengths_nm, n_poles: int | None = None, *, drude: bool | None = None, weights=None, max_poles: int = DEFAULT_MAX_POLES, tol: float = DEFAULT_TOL, eps_inf_min: float = 1.0, return_info: bool = False)

Fit a passive :class:DrudeLorentz to material over the band spanned by wavelengths_nm.

material is any material object, its JSON dict, or a library name ("Au"). The fit uses wavelengths_nm (vacuum, nm) as samples, densified to at least 64 points uniform in photon energy across [min, max] when fewer are given; outside the band the model is only constrained to stay physical. weights (same length as wavelengths_nm) up-weights parts of the band.

The objective weights Re(eps) by 1/|eps| and Im(eps) by 1/(|Im eps| + 0.05 |eps|), so the loss of good metals (which sets plasmon linewidths) is fitted too.

n_poles is the number of Lorentz poles. With None it is chosen automatically: the smallest number (up to max_poles) for which both rms_rel_error and rms_imag_error are below tol, or else the best found (stopping once a pole improves them by < 10 %). drude adds the free-electron term; None adds it when Re(eps) < 0 somewhere in the band (metals).

Returns the :class:DrudeLorentz; with return_info=True returns (model, DrudeLorentzFit) carrying the RMS / max errors. See also :func:fit_quality.

fit_quality

fit_quality(model, material, wavelengths_nm) -> dict[str, float]

Error of model (e.g. a fitted :class:DrudeLorentz) against material at wavelengths_nm.

Returns rms_rel_error and max_rel_error (of |eps_model - eps| / |eps|), rms_imag_error and max_imag_error (of |Im eps_model - Im eps| / (|Im eps| + 0.05 |eps|), the loss that sets resonance widths), rms_abs_error and max_abs_error (of |eps_model - eps|), plus band_nm.