Source code for photonforge.typing

import typing as _typ
from collections.abc import Sequence as _Sequence

import tidy3d as _td

from . import extension as _ext


# We cannot use a dataclass here because sphinx autodoc will not respect the custom __repr__
# implementation
class _Metadata:
    __slots__ = (
        "brand",
        "description",
        "exclusiveMaximum",
        "exclusiveMinimum",
        "kwargs_for",
        "label",
        "maxItems",
        "maxLength",
        "maximum",
        "minItems",
        "minLength",
        "minimum",
        "optional",
        "units",
    )

    def __init__(
        self,
        brand: str | None = None,
        description: str | None = None,
        exclusiveMaximum: float | int | None = None,
        exclusiveMinimum: float | int | None = None,
        kwargs_for: str | None = None,
        label: str | None = None,
        maxItems: int | None = None,
        maxLength: int | None = None,
        maximum: float | int | None = None,
        minItems: int | None = None,
        minLength: int | None = None,
        minimum: float | int | None = None,
        optional: bool = False,
        units: str | None = None,
    ):
        object.__setattr__(self, "brand", brand)
        object.__setattr__(self, "description", description)
        object.__setattr__(self, "exclusiveMaximum", exclusiveMaximum)
        object.__setattr__(self, "exclusiveMinimum", exclusiveMinimum)
        object.__setattr__(self, "kwargs_for", kwargs_for)
        object.__setattr__(self, "label", label)
        object.__setattr__(self, "maxItems", maxItems)
        object.__setattr__(self, "maxLength", maxLength)
        object.__setattr__(self, "maximum", maximum)
        object.__setattr__(self, "minItems", minItems)
        object.__setattr__(self, "minLength", minLength)
        object.__setattr__(self, "minimum", minimum)
        object.__setattr__(self, "optional", optional)
        object.__setattr__(self, "units", units)

    def items(self):
        return (
            ("brand", self.brand),
            ("description", self.description),
            ("exclusiveMaximum", self.exclusiveMaximum),
            ("exclusiveMinimum", self.exclusiveMinimum),
            ("kwargs_for", self.kwargs_for),
            ("label", self.label),
            ("maxItems", self.maxItems),
            ("maxLength", self.maxLength),
            ("maximum", self.maximum),
            ("minItems", self.minItems),
            ("minLength", self.minLength),
            ("minimum", self.minimum),
            ("optional", self.optional),
            ("units", self.units),
        )

    def values(self):
        return (
            self.brand,
            self.description,
            self.exclusiveMaximum,
            self.exclusiveMinimum,
            self.kwargs_for,
            self.label,
            self.maxItems,
            self.maxLength,
            self.maximum,
            self.minItems,
            self.minLength,
            self.minimum,
            self.optional,
            self.units,
        )

    def non_optional_copy(self):
        return _Metadata(
            brand=self.brand,
            description=self.description,
            exclusiveMaximum=self.exclusiveMaximum,
            exclusiveMinimum=self.exclusiveMinimum,
            kwargs_for=self.kwargs_for,
            label=self.label,
            maxItems=self.maxItems,
            maxLength=self.maxLength,
            maximum=self.maximum,
            minItems=self.minItems,
            minLength=self.minLength,
            minimum=self.minimum,
            optional=False,
            units=self.units,
        )

    def __setattr__(self, _n, _v):
        raise RuntimeError("Immutable object.")

    def __delattr__(self, _n):
        raise RuntimeError("Immutable object.")

    def __repr__(self):
        skip = ("brand", "label", "description", "kwargs_for", "optional")
        values = [f"{k}={v!r}" for k, v in self.items() if v is not None and k not in skip]
        return "_" if len(values) == 0 else ", ".join(values)

    def __str__(self):
        return self.__repr__()

    def __eq__(self, other):
        return self.values() == other.values()

    def __hash__(self):
        return hash(self.values())


[docs] def annotate(field, **kwargs): """Annotate a type with metadata. Args: field: Type to be annotated. kwargs: Type metadata. See below. Return: Annotated type. Type metadata closely resembles specifications for JSON Schema DRAFT7 with a few additions. A few commonly used keywords are: ``label``, ``description``, ``units``, ``maximum``, ``minimum``, ``exclusiveMaximum``, ``exclusiveMinimum``, ``maxItems``, ``minItems``, ``maxLength``, and ``minLength``. """ orig = _typ.get_origin(field) args = _typ.get_args(field) if orig is _typ.Annotated and len(args) == 2 and isinstance(args[1], _Metadata): field = args[0] metadata = dict(args[1].items()) metadata.update(kwargs) else: metadata = kwargs return _typ.Annotated[field, _Metadata(**metadata)]
[docs] def kwargs_for(fn_or_cls): """Type that represents keyword arguments for a function or class. Args: fn_or_cls: Target function or class. Return: Data type. """ full_name = f"{fn_or_cls.__module__}.{fn_or_cls.__qualname__}" return annotate(dict[str, _typ.Any], kwargs_for=full_name)
[docs] def array(dtype, ndims, ndims_max=None): """Type for arrays with specified number of dimensions. Args: dtype: Scalar type for the array elements. ndims: Number of dimensions for the array. ndims_max: If set, maximal number of dimensions for the array. Any dimensions between ``ndims`` and ``ndims_max`` (inclusive) are valid. Return: Array type. """ if ndims_max is None: ndims_max = ndims if ndims < 0 or ndims_max < ndims: raise RuntimeError( "'ndims' cannot be negative and 'ndims_max' cannot be less than 'ndims'." ) field = dtype for _ in range(ndims): field = _Sequence[field] if ndims_max > ndims: u = field for _ in range(ndims, ndims_max): field = _Sequence[field] u = u | field field = u return field
[docs] def expression(num_variables, min_expressions): """Type for :class:`Expression` with a minimal number of values. Args: num_variables: Required number of independent variables. min_expressions: Minimal number of required expressions. Return: Expression type. """ return annotate( str | _ext.Expression, brand="Expression", # TODO independentVariables=num_variables, # TODO minExpressions=min_expressions, )
PositiveInt = annotate(int, exclusiveMinimum=0) #: NonNegativeInt = annotate(int, minimum=0) #: NegativeInt = annotate(int, exclusiveMaximum=0) #: NonPositiveInt = annotate(int, maximum=0) #: PositiveFloat = annotate(float, exclusiveMinimum=0) #: NonNegativeFloat = annotate(float, minimum=0) #: NegativeFloat = annotate(float, exclusiveMaximum=0) #: NonPositiveFloat = annotate(float, maximum=0) #: Fraction = annotate(float, minimum=0, maximum=1) #: Coordinate = annotate(float, units="μm") #: Dimension = annotate(NonNegativeFloat, units="μm") #: PositiveDimension = annotate(PositiveFloat, units="μm") #: Coordinate2D = annotate(_Sequence[Coordinate], minItems=2, maxItems=2) #: Dimension2D = annotate(_Sequence[Dimension], minItems=2, maxItems=2) #: PositiveDimension2D = annotate(_Sequence[PositiveDimension], minItems=2, maxItems=2) #: Angle = annotate(float, units="°") #: Frequency = annotate(float, minimum=0, units="Hz") #: Time = annotate(float, units="s") #: TimeDelay = annotate(float, minimum=0, units="s") #: Temperature = annotate(float, minimum=0, units="K") #: Voltage = annotate(float, units="V") #: Impedance = annotate(complex, units="Ω") #: Power = annotate(float, minimum=0, units="W") #: Loss = annotate(float, minimum=0, units="dB") #: PropagationLoss = annotate(float, minimum=0, units="dB/μm") #: Dispersion = annotate(float, units="s/μm²") #: DispersionSlope = annotate(float, units="s/μm³") #: Layer = annotate(str | tuple[int, int], brand="PhotonForgeLayer") #: PortSpecOrName = annotate(str | _ext.PortSpec, brand="PhotonForgePortSpecOrName") #: PortReference = annotate( tuple[_ext.Reference, str] | tuple[_ext.Reference, str, int], brand="PhotonForgePortReference", ) #: TerminalReference = annotate( tuple[_ext.Reference, str] | tuple[_ext.Reference, str, int], brand="PhotonForgeTerminalReference", ) #: Medium = annotate(_td.components.medium.MediumType3D, brand="Tidy3dAnyMedium") #: