142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
"""
|
|
Tutorial: using `LazyLibrary` and `Pather.interface()`.
|
|
|
|
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.
|
|
"""
|
|
from typing import Any
|
|
from pprint import pformat
|
|
|
|
|
|
from masque import Pather, LazyLibrary
|
|
from masque.file.gdsii import writefile, load_libraryfile
|
|
|
|
import basic_shapes
|
|
import devices
|
|
from devices import data_to_ports
|
|
from basic_shapes import GDS_OPTS
|
|
|
|
|
|
def main() -> None:
|
|
# A `LazyLibrary` delays work until a pattern is actually needed.
|
|
# That applies both to GDS cells we load from disk and to python callables
|
|
# that generate patterns on demand.
|
|
lib = LazyLibrary()
|
|
|
|
#
|
|
# Load some devices from a GDS file
|
|
#
|
|
|
|
# Scan circuit.gds and prepare to lazy-load its contents
|
|
gds_lib, _properties = load_libraryfile('circuit.gds', postprocess=data_to_ports)
|
|
|
|
# Add those cells into our lazy library.
|
|
# Nothing is read yet; we are only registering how to fetch and postprocess
|
|
# each pattern when it is first requested.
|
|
lib.add(gds_lib)
|
|
|
|
print('Patterns loaded from GDS into library:\n' + pformat(list(lib.keys())))
|
|
|
|
#
|
|
# Add some new devices to the library, this time from python code rather than GDS
|
|
#
|
|
|
|
lib['triangle'] = lambda: basic_shapes.triangle(devices.RADIUS)
|
|
opts: dict[str, Any] = dict(
|
|
lattice_constant = devices.LATTICE_CONSTANT,
|
|
hole = 'triangle',
|
|
)
|
|
|
|
# Triangle-based variants. These lambdas are only recipes for building the
|
|
# patterns; they do not execute until someone asks for the cell.
|
|
lib['tri_wg10'] = lambda: devices.waveguide(length=10, mirror_periods=5, **opts)
|
|
lib['tri_wg05'] = lambda: devices.waveguide(length=5, mirror_periods=5, **opts)
|
|
lib['tri_wg28'] = lambda: devices.waveguide(length=28, mirror_periods=5, **opts)
|
|
lib['tri_bend0'] = lambda: devices.bend(mirror_periods=5, **opts)
|
|
lib['tri_ysplit'] = lambda: devices.y_splitter(mirror_periods=5, **opts)
|
|
lib['tri_l3cav'] = lambda: devices.perturbed_l3(xy_size=(4, 10), **opts, hole_lib=lib)
|
|
|
|
#
|
|
# Build a mixed waveguide with an L3 cavity in the middle
|
|
#
|
|
|
|
# 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
|
|
|
|
|
|
#
|
|
# Build a second device that is explicitly designed to mate with `circ2`.
|
|
#
|
|
|
|
# `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
|
|
|
|
#
|
|
# Write all devices into a GDS file
|
|
#
|
|
print('Writing library to file...')
|
|
writefile(lib, '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
|
|
#
|