Skip to content

Layout

layout

Layout integration: GDS files and gdsfactory components/netlists.

  • :func:resolve_layout turns the GdsLayout of a DeviceProblem into extruded :class:~lightsmith.problems.Prism structures. It uses a small built-in GDSII reader (no dependencies; flattens SREF/AREF with rotation, magnification and reflection), so every solver can call it.
  • :func:from_gdsfactory_component builds a DeviceProblem from a gdsfactory component: polygons become prisms with z extents and materials, gdsfactory ports become Port planes.
  • :func:from_gdsfactory_netlist builds a CircuitProblem from a gdsfactory netlist, mapping gdsfactory cells to lightsmith compact models.

gdsfactory (MIT) is optional and imported lazily; install it with pip install gdsfactory (or uv run --with gdsfactory). Note that recent gdsfactory releases depend on kfactory and KLayout's Python module, which is GPL-3.0-or-later; lightsmith itself never imports KLayout.

Units: gdsfactory works in um, lightsmith in nm; conversion happens here.

UM module-attribute

UM = 1000.0

LayerKey module-attribute

LayerKey = tuple[int, int]

Translator module-attribute

Translator = Callable[[dict[str, Any], dict[str, Any]], 'Instance | dict[str, Any]']

DEFAULT_GDSFACTORY_MODELS module-attribute

DEFAULT_GDSFACTORY_MODELS: dict[str, Translator] = {'straight': _straight, 'taper': _straight, 'taper_strip_to_ridge': _straight, 'bend_euler': _bend, 'bend_circular': _bend, 'mmi1x2': _same('mmi1x2'), 'mmi2x2': _same('mmi2x2'), 'coupler': _same('coupler'), 'ring_single': _ring('ring_single'), 'ring_double': _ring('ring_double'), 'grating_coupler_elliptical': _same('grating_coupler'), 'grating_coupler_elliptical_trenches': _same('grating_coupler'), 'grating_coupler_rectangular': _same('grating_coupler')}

gdsfactory cell name -> translator(settings, info) giving an lightsmith instance (lengths um -> nm). Geometry-dependent optics (coupling vs gap, neff vs width) are not derived: set them through defaults.

LayerSpec

Bases: BaseModel

How one GDS layer/datatype is extruded.

model_config class-attribute instance-attribute

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

material instance-attribute

material: Material

z_min_nm instance-attribute

z_min_nm: float

z_max_nm instance-attribute

z_max_nm: float

sidewall_angle_deg class-attribute instance-attribute

sidewall_angle_deg: float = Field(0.0, description='0 = vertical; positive = narrower at the top.')

name class-attribute instance-attribute

name: str | None = None

parse_layer

parse_layer(key: Any) -> LayerKey

(layer, datatype) from a tuple, 'L/D' string, int (datatype 0) or gdsfactory layer enum/LayerInfo.

normalize_layer_map

normalize_layer_map(layer_map: Mapping[Any, Any]) -> dict[LayerKey, LayerSpec]

Accept {'1/0': {'material': ..., 'z_min_nm': .., 'z_max_nm': ..}} or LayerSpec values, any key form.

read_gds

read_gds(path: str | Path, cell: str | None = None) -> dict[LayerKey, list[ndarray]]

Flattened polygons per (layer, datatype) in nm from a GDSII file.

cell defaults to the single top cell (one that no other cell references).

write_gds

write_gds(path: str | Path, cells: Mapping[str, Mapping[str, Any]], dbu_nm: float = 1.0, libname: str = 'lightsmith') -> Path

Write a minimal GDSII file.

cells maps cell name -> {"polygons": {(layer, datatype) or 'L/D': [array (N, 2) in nm, ...]}, "refs": [{"cell": name, "origin_nm": (x, y), "angle_deg": 0, "magnification": 1, "x_reflection": False, "columns": 1, "rows": 1, "spacing_nm": ((dx, 0), (0, dy))}]}.

polygons_to_structures

polygons_to_structures(polygons: Mapping[LayerKey, list[ndarray]], layer_map: Mapping[Any, Any], warn_unmapped: bool = True) -> list[Structure]

Extrude polygons (nm) into Prism structures, in layer_map order (later layers override earlier ones).

layout_structures

layout_structures(layout: GdsLayout, base_dir: str | Path | None = None) -> list[Structure]

Structures for a GdsLayout (path resolved against base_dir when relative).

resolve_layout

resolve_layout(problem: DeviceProblem, base_dir: str | Path | None = None) -> list[Structure]

All structures of a device: explicit structures first, then the extruded GDS layout.

Later structures override earlier ones where they overlap, so the layout (waveguide cores) wins over explicit background slabs (substrate, cladding). Solvers call this instead of reading problem.structures directly.

resolved_device

resolved_device(problem: DeviceProblem, base_dir: str | Path | None = None) -> DeviceProblem

A copy of problem with the layout expanded into structures and layout cleared.

component_polygons

component_polygons(component, merge: bool = False) -> dict[LayerKey, list[ndarray]]

Flattened polygons of a gdsfactory component per (layer, datatype), in nm.

gdsfactory_port

gdsfactory_port(port, layer_map: Mapping[LayerKey, LayerSpec], margin_nm: float, num_modes: int, z_range: tuple[float, float]) -> Port

A gdsfactory port as an lightsmith Port plane (normal along x or y).

from_gdsfactory_component

from_gdsfactory_component(component, layer_map: Mapping[Any, Any], *, background, wavelengths: Wavelengths, padding_nm: float = 1000.0, port_margin_nm: float = 1000.0, num_modes: int = 1, port_types: Iterable[str] | None = ('optical',), dimension: int = 3, extra_structures: Iterable[Structure] = (), merge: bool = False, options: dict[str, dict[str, Any]] | None = None) -> DeviceProblem

A DeviceProblem from a gdsfactory component.

layer_map maps layers ((1, 0), "1/0", a gdsfactory LAYER.WG or a PDK layer name) to {"material": ..., "z_min_nm": ..., "z_max_nm": ...}; its order is the override order. Each polygon becomes a Prism; ports of the given port_types become Port planes sized width + 2 * port_margin_nm by thickness + 2 * port_margin_nm, with direction pointing into the device. The domain is the bounding box of the mapped geometry plus padding_nm. extra_structures (substrate, cladding) come first.

from_gdsfactory_netlist

from_gdsfactory_netlist(netlist, wavelengths: Wavelengths, *, models: Mapping[str, str | Translator | Instance | dict] | None = None, defaults: Mapping[str, Mapping[str, Any]] | None = None, options: dict[str, dict[str, Any]] | None = None) -> CircuitProblem

A CircuitProblem from a gdsfactory component (its get_netlist()) or netlist dict.

models maps gdsfactory cell names to an lightsmith model name, a fixed instance (dict/Instance) or a translator f(settings, info) -> instance; it extends :data:DEFAULT_GDSFACTORY_MODELS (straight/taper -> waveguide using the netlist's info.length, bends, MMIs, coupler, rings, grating couplers). defaults adds settings per lightsmith model, e.g. {"waveguide": {"neff": 2.4, "ng": 4.2}, "bend": {...}} (instance-specific settings win). Nets that touch ports the target model does not have (e.g. electrical pads) are skipped with a warning.