[wip] introduce BuildLibrary and make overlay first-class

This commit is contained in:
Jan Petykiewicz 2026-04-22 21:24:05 -07:00
commit 6d494142fe
7 changed files with 1335 additions and 98 deletions

View file

@ -1,136 +1,114 @@
"""
Tutorial: using a source-backed lazy GDS library and `Pather.interface()`.
Tutorial: authoring a mixed library with `BuildLibrary`.
This example assumes you have already read `devices.py` and generated the
`circuit.gds` file it writes. The goal here is not the photonic-crystal geometry
itself, but rather how Masque lets you mix lazily loaded GDS content with
python-generated devices inside one library.
itself, but rather how Masque lets you combine imported GDS cells with
python-generated recipes, then turn that declaration set into a normal library
for downstream assembly and writing.
"""
from typing import Any
from pprint import pformat
from masque import Pather
from masque import BuildLibrary, Pather, Pattern, cell
from masque.file.gdsii import writefile
from masque.file.gdsii_lazy import OverlayLibrary, readfile
from masque.file.gdsii_lazy import readfile
import basic_shapes
import devices
from basic_shapes import GDS_OPTS
def make_mixed_waveguide(lib: BuildLibrary) -> Pattern:
"""
Recipe which assembles imported and generated cells behind the builder API.
"""
circ = Pather(library=lib, ports='tri_l3cav')
# First way to specify what we are plugging in: request an explicit abstract.
circ.plug(lib.abstract('wg10'), {'input': 'right'})
# Second way: use an AbstractView, which behaves like a mapping of names
# to abstracts.
abstracts = lib.abstract_view()
circ.plug(abstracts['wg10'], {'output': 'left'})
# Third way: let Pather resolve a pattern name through its own library.
circ.plug('tri_wg10', {'input': 'right'})
circ.plug('tri_wg10', {'output': 'left'})
return circ.pattern
def main() -> None:
# `OverlayLibrary` lets us mix source-backed GDS cells with python-generated
# patterns behind the same library interface.
lib = OverlayLibrary()
builder = BuildLibrary()
cells = builder.cells
#
# Load some devices from a GDS file
#
# Scan circuit.gds and prepare to lazy-load its contents. Port labels are
# imported on first materialization, but the raw source remains untouched.
# imported on first materialization, but the raw source remains untouched
# until we build the final library.
gds_lib, _properties = readfile('circuit.gds')
lib.add_source(gds_lib.with_ports_from_data(layers=[(3, 0)], max_depth=1))
builder.add_source(gds_lib.with_ports_from_data(layers=[(3, 0)], max_depth=1))
print('Patterns loaded from GDS into library:\n' + pformat(list(lib.keys())))
print('Registered imported cells:\n' + pformat(list(gds_lib.keys())))
#
# Add some new devices to the library, this time from python code rather than GDS
# Register some new devices, this time from python code rather than GDS.
#
lib['triangle'] = basic_shapes.triangle(devices.RADIUS)
cells.triangle = basic_shapes.triangle(devices.RADIUS)
opts: dict[str, Any] = dict(
lattice_constant = devices.LATTICE_CONSTANT,
hole = 'triangle',
)
lattice_constant=devices.LATTICE_CONSTANT,
hole='triangle',
)
lib['tri_wg10'] = devices.waveguide(length=10, mirror_periods=5, **opts)
lib['tri_wg05'] = devices.waveguide(length=5, mirror_periods=5, **opts)
lib['tri_wg28'] = devices.waveguide(length=28, mirror_periods=5, **opts)
lib['tri_bend0'] = devices.bend(mirror_periods=5, **opts)
lib['tri_ysplit'] = devices.y_splitter(mirror_periods=5, **opts)
lib['tri_l3cav'] = devices.perturbed_l3(xy_size=(4, 10), **opts, hole_lib=lib)
cells.tri_wg10 = cell(devices.waveguide)(length=10, mirror_periods=5, **opts)
cells.tri_wg05 = cell(devices.waveguide)(length=5, mirror_periods=5, **opts)
cells.tri_wg28 = cell(devices.waveguide)(length=28, mirror_periods=5, **opts)
cells.tri_bend0 = cell(devices.bend)(mirror_periods=5, **opts)
cells.tri_ysplit = cell(devices.y_splitter)(mirror_periods=5, **opts)
cells.tri_l3cav = cell(devices.perturbed_l3)(xy_size=(4, 10), **opts, hole_lib=builder)
cells.mixed_wg_cav = cell(make_mixed_waveguide)(builder)
print('Declared cells waiting to be built:\n' + pformat(list(builder.keys())))
#
# Build a mixed waveguide with an L3 cavity in the middle
# Build the declaration set into a normal library.
#
# Start a new design by copying the ports from an existing library cell.
# This gives `circ2` the same external interface as `tri_l3cav`.
circ2 = Pather(library=lib, ports='tri_l3cav')
# First way to specify what we are plugging in: request an explicit abstract.
# This works with `Pattern` methods directly as well as with `Pather`.
circ2.plug(lib.abstract('wg10'), {'input': 'right'})
# Second way: use an `AbstractView`, which behaves like a mapping of names
# to abstracts.
abstracts = lib.abstract_view()
circ2.plug(abstracts['wg10'], {'output': 'left'})
# Third way: let `Pather` resolve a pattern name through its own library.
# This shorthand is convenient, but it is specific to helpers that already
# carry a library reference.
circ2.plug('tri_wg10', {'input': 'right'})
circ2.plug('tri_wg10', {'output': 'left'})
# Add the circuit to the device library.
lib['mixed_wg_cav'] = circ2.pattern
built = builder.build()
print('Built library contains:\n' + pformat(list(built.keys())))
#
# Build a second device that is explicitly designed to mate with `circ2`.
# Continue designing against the built library.
#
# `Pather.interface()` makes a new pattern whose ports mirror an existing
# design's external interface. That is useful when you want to design an
# adapter, continuation, or mating structure.
circ3 = Pather.interface(source=circ2)
# Continue routing outward from those inherited ports.
circ3.plug('tri_bend0', {'input': 'right'})
circ3.plug('tri_bend0', {'input': 'left'}, mirrored=True) # mirror since no tri y-symmetry
circ3.plug('tri_bend0', {'input': 'right'})
circ3.plug('bend0', {'output': 'left'})
circ3.plug('bend0', {'output': 'left'})
circ3.plug('bend0', {'output': 'left'})
circ3.plug('tri_wg10', {'input': 'right'})
circ3.plug('tri_wg28', {'input': 'right'})
circ3.plug('tri_wg10', {'input': 'right', 'output': 'left'})
lib['loop_segment'] = circ3.pattern
# The built result behaves like a normal mutable library, so downstream code
# can use Pather, abstract views, and writing without going back through the
# builder interface.
circ = Pather.interface(source='mixed_wg_cav', library=built)
circ.plug('tri_bend0', {'input': 'right'})
circ.plug('tri_bend0', {'input': 'left'}, mirrored=True) # mirror since no tri y-symmetry
circ.plug('tri_bend0', {'input': 'right'})
circ.plug('bend0', {'output': 'left'})
circ.plug('bend0', {'output': 'left'})
circ.plug('bend0', {'output': 'left'})
circ.plug('tri_wg10', {'input': 'right'})
circ.plug('tri_wg28', {'input': 'right'})
circ.plug('tri_wg10', {'input': 'right', 'output': 'left'})
built['loop_segment'] = circ.pattern
#
# Write all devices into a GDS file
# Write all devices into a GDS file.
#
print('Writing library to file...')
writefile(lib, 'library.gds', **GDS_OPTS)
writefile(built, 'library.gds', **GDS_OPTS)
if __name__ == '__main__':
main()
#
#class prout:
# def place(
# self,
# other: Pattern,
# label_layer: layer_t = 'WATLAYER',
# *,
# port_map: Dict[str, str | None] | None = None,
# **kwargs,
# ) -> 'prout':
#
# Pattern.place(self, other, port_map=port_map, **kwargs)
# name: str | None
# for name in other.ports:
# if port_map:
# assert(name is not None)
# name = port_map.get(name, name)
# if name is None:
# continue
# self.pattern.label(string=name, offset=self.ports[name].offset, layer=label_layer)
# return self
#