Source code for optiland.nonsequential.ir.medium_ir

"""MediumIR -- data-only description of an optical medium.

Kramer Harrison, 2026
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


[docs] @dataclass(frozen=True) class MediumIR: """A single optical medium, as plain data. ``n_model``/``k_model`` describe *how* to evaluate dispersion/absorption without embedding a live evaluator: a dict tagged by ``"kind"`` rather than a bound method, so the description survives a JSON round-trip and a non-Python backend can interpret it. Both use the same three kinds and are always populated together by :func:`~optiland.nonsequential.ir.lower.lower`: - ``{"kind": "constant", "n": <float>}`` / ``{"kind": "constant", "k": <float>}`` -- wavelength-independent (vacuum is ``n=1.0``, ``k=0.0``). - ``{"kind": "catalog", "name": <str>}`` -- a glass catalog name (e.g. ``"N-BK7"``), resolved via :meth:`NSQMaterial.from_glass` the same way :mod:`optiland.nonsequential.serialization` already round-trips materials. - ``{"kind": "opaque"}`` -- a non-catalog material kept only for dispatch (``lower(scene, strict=False)``); not losslessly serializable. A custom dispersion model with no catalog name cannot be lowered losslessly and raises at lowering time (see :func:`~optiland.nonsequential.ir.lower.lower`) rather than silently dropping to a constant approximation. Note that ``k_model`` here is descriptive only, matching the existing ``n_model`` precedent: the interpreter's Beer-Lambert absorption (D-13, see ``optiland.nonsequential.backends.array_backend.ArrayBackend.trace``) reads ``k(wavelength)`` from the live ``NSQMaterial`` on ``rays.k_current``, never from this IR. Attributes: id: Index into ``SceneIR.media``. name: Human-readable label (glass name, or ``"vacuum"``). n_model: Refractive-index model descriptor, as above. k_model: Extinction-coefficient model descriptor, as above. """ id: int name: str n_model: dict[str, Any] k_model: dict[str, Any] | None = field(default=None)