Lots of progress on tutorials

This commit is contained in:
Jan Petykiewicz 2023-01-24 23:25:10 -08:00 committed by jan
commit f4537a0feb
16 changed files with 579 additions and 510 deletions

View file

@ -1,174 +0,0 @@
from typing import Tuple, Sequence
import numpy # type: ignore
from numpy import pi
from masque import layer_t, Pattern, SubPattern, Label
from masque.shapes import Polygon, Circle
from masque.builder import Device, Port
from masque.library import Library, DeviceLibrary
from masque.file.klamath import writefile
import pcgen
HOLE_SCALE: float = 1000
''' Radius for the 'hole' cell. Should be significantly bigger than
1 (minimum database unit) in order to have enough precision to
reasonably represent a polygonized circle (for GDS)
'''
def hole(layer: layer_t,
radius: float = HOLE_SCALE * 0.35,
) -> Pattern:
"""
Generate a pattern containing a single circular hole.
Args:
layer: Layer to draw the circle on.
radius: Circle radius.
Returns:
Pattern, named `'hole'`
"""
pat = Pattern('hole', shapes=[
Circle(radius=radius, offset=(0, 0), layer=layer)
])
return pat
def perturbed_l3(lattice_constant: float,
hole: Pattern,
trench_layer: layer_t = (1, 0),
shifts_a: Sequence[float] = (0.15, 0, 0.075),
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
xy_size: Tuple[int, int] = (10, 10),
perturbed_radius: float = 1.1,
trench_width: float = 1200,
) -> Device:
"""
Generate a `Device` representing a perturbed L3 cavity.
Args:
lattice_constant: Distance between nearest neighbor holes
hole: `Pattern` object containing a single hole
trench_layer: Layer for the trenches, default `(1, 0)`.
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
(1 - multiplicative factor) for shifting holes adjacent to
the defect (same row). Default `(0.15, 0, 0.075)` for first,
second, third holes.
shifts_r: passed to `pcgen.l3_shift`; specifies radius for perturbing
holes adjacent to the defect (same row). Default 1.0 for all holes.
Provided sequence should have same length as `shifts_a`.
xy_size: `(x, y)` number of mirror periods in each direction; total size is
`2 * n + 1` holes in each direction. Default (10, 10).
perturbed_radius: radius of holes perturbed to form an upwards-driected beam
(multiplicative factor). Default 1.1.
trench width: Width of the undercut trenches. Default 1200.
Returns:
`Device` object representing the L3 design.
"""
xyr = pcgen.l3_shift_perturbed_defect(mirror_dims=xy_size,
perturbed_radius=perturbed_radius,
shifts_a=shifts_a,
shifts_r=shifts_r)
pat = Pattern(f'L3p-a{lattice_constant:g}rp{perturbed_radius:g}')
pat.subpatterns += [SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y), scale=r * lattice_constant / HOLE_SCALE)
for x, y, r in xyr]
min_xy, max_xy = pat.get_bounds()
trench_dx = max_xy[0] - min_xy[0]
pat.shapes += [
Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer),
]
ports = {
'input': Port((-lattice_constant * xy_size[0], 0), rotation=0, ptype=1),
'output': Port((lattice_constant * xy_size[0], 0), rotation=pi, ptype=1),
}
return Device(pat, ports)
def waveguide(lattice_constant: float,
hole: Pattern,
length: int,
mirror_periods: int,
) -> Device:
xy = pcgen.waveguide(length=length + 2, num_mirror=mirror_periods)
pat = Pattern(f'_wg-a{lattice_constant:g}l{length}')
pat.subpatterns += [SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y), scale=lattice_constant / HOLE_SCALE)
for x, y in xy]
ports = {
'left': Port((-lattice_constant * length / 2, 0), rotation=0, ptype=1),
'right': Port((lattice_constant * length / 2, 0), rotation=pi, ptype=1),
}
return Device(pat, ports)
def bend(lattice_constant: float,
hole: Pattern,
mirror_periods: int,
) -> Device:
xy = pcgen.wgbend(num_mirror=mirror_periods)
pat_half = Pattern(f'_wgbend_half-a{lattice_constant:g}l{mirror_periods}')
pat_half.subpatterns += [SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y), scale=lattice_constant / HOLE_SCALE)
for x, y in xy]
pat = Pattern(f'_wgbend-a{lattice_constant:g}l{mirror_periods}')
pat.addsp(pat_half, offset=(0, 0), rotation=0, mirrored=(False, False))
pat.addsp(pat_half, offset=(0, 0), rotation=-2 * pi / 3, mirrored=(True, False))
ports = {
'left': Port((-lattice_constant * mirror_periods, 0), rotation=0, ptype=1),
'right': Port((lattice_constant * mirror_periods / 2,
lattice_constant * mirror_periods * numpy.sqrt(3) / 2), rotation=pi * 4 / 3, ptype=1),
}
return Device(pat, ports)
def label_ports(device: Device, layer: layer_t = (3, 0)) -> Device:
for name, port in device.ports.items():
angle_deg = numpy.rad2deg(port.rotation)
device.pattern.labels += [
Label(string=f'{name} (angle {angle_deg:g})', layer=layer, offset=port.offset)
]
return device
def main():
hole_layer = (1, 2)
a = 512
hole_pat = hole(layer=hole_layer)
wg0 = label_ports(waveguide(lattice_constant=a, hole=hole_pat, length=10, mirror_periods=5))
wg1 = label_ports(waveguide(lattice_constant=a, hole=hole_pat, length=5, mirror_periods=5))
bend0 = label_ports(bend(lattice_constant=a, hole=hole_pat, mirror_periods=5))
l3cav = label_ports(perturbed_l3(lattice_constant=a, hole=hole_pat, xy_size=(4, 10)))
dev = Device(name='my_bend', ports={})
dev.place(wg0, offset=(0, 0), port_map={'left': 'in', 'right': 'signal'})
dev.plug(wg0, {'signal': 'left'})
dev.plug(bend0, {'signal': 'left'})
dev.plug(wg1, {'signal': 'left'})
dev.plug(bend0, {'signal': 'right'})
dev.plug(wg0, {'signal': 'left'})
dev.plug(l3cav, {'signal': 'input'})
dev.plug(wg0, {'signal': 'left'})
writefile(dev.pattern, 'phc.gds', 1e-9, 1e-3)
dev.pattern.visualize()
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
TODO write tutorial readme

View file

@ -3,19 +3,19 @@ from typing import Tuple, Sequence
import numpy
from numpy import pi
from masque import layer_t, Pattern, SubPattern, Label
from masque.shapes import Circle, Arc, Polygon
from masque.builder import Device, Port
from masque.library import Library, DeviceLibrary
from masque import (
layer_t, Pattern, Label, Port,
Circle, Arc, Polygon,
)
import masque.file.gdsii
# Note that masque units are arbitrary, and are only given
# physical significance when writing to a file.
GDS_OPTS = {
'meters_per_unit': 1e-9, # GDS database unit, 1 nanometer
'logical_units_per_unit': 1e-3, # GDS display unit, 1 micron
}
GDS_OPTS = dict(
meters_per_unit = 1e-9, # GDS database unit, 1 nanometer
logical_units_per_unit = 1e-3, # GDS display unit, 1 micron
)
def hole(
@ -30,10 +30,10 @@ def hole(
layer: Layer to draw the circle on.
Returns:
Pattern, named `'hole'`
Pattern containing a circle.
"""
pat = Pattern('hole', shapes=[
Circle(radius=radius, offset=(0, 0), layer=layer)
pat = Pattern(shapes=[
Circle(radius=radius, offset=(0, 0), layer=layer),
])
return pat
@ -50,15 +50,15 @@ def triangle(
layer: Layer to draw the circle on.
Returns:
Pattern, named `'triangle'`
Pattern containing a triangle
"""
vertices = numpy.array([
(numpy.cos( pi / 2), numpy.sin( pi / 2)),
(numpy.cos(pi + pi / 6), numpy.sin(pi + pi / 6)),
(numpy.cos( - pi / 6), numpy.sin( - pi / 6)),
]) * radius
]) * radius
pat = Pattern('triangle', shapes=[
pat = Pattern(shapes=[
Polygon(offset=(0, 0), layer=layer, vertices=vertices),
])
return pat
@ -78,37 +78,38 @@ def smile(
secondary_layer: Layer to draw eyes and smile on.
Returns:
Pattern, named `'smile'`
Pattern containing a smiley face
"""
# Make an empty pattern
pat = Pattern('smile')
pat = Pattern()
# Add all the shapes we want
pat.shapes += [
Circle(radius=radius, offset=(0, 0), layer=layer), # Outer circle
Circle(radius=radius / 10, offset=(radius / 3, radius / 3), layer=secondary_layer),
Circle(radius=radius / 10, offset=(-radius / 3, radius / 3), layer=secondary_layer),
Arc(radii=(radius * 2 / 3, radius * 2 / 3), # Underlying ellipse radii
Arc(
radii=(radius * 2 / 3, radius * 2 / 3), # Underlying ellipse radii
angles=(7 / 6 * pi, 11 / 6 * pi), # Angles limiting the arc
width=radius / 10,
offset=(0, 0),
layer=secondary_layer),
layer=secondary_layer,
),
]
return pat
def main() -> None:
hole_pat = hole(1000)
smile_pat = smile(1000)
tri_pat = triangle(1000)
lib = {}
units_per_meter = 1e-9
units_per_display_unit = 1e-3
lib['hole'] = hole(1000)
lib['smile'] = smile(1000)
lib['triangle'] = triangle(1000)
masque.file.gdsii.writefile([hole_pat, tri_pat, smile_pat], 'basic_shapes.gds', **GDS_OPTS)
masque.file.gdsii.writefile(lib, 'basic_shapes.gds', **GDS_OPTS)
smile_pat.visualize()
lib['triangle'].visualize()
if __name__ == '__main__':

View file

@ -1,12 +1,15 @@
from typing import Tuple, Sequence, Dict
# TODO update tutorials
from typing import Tuple, Sequence, Dict, Mapping
import numpy
from numpy import pi
from masque import layer_t, Pattern, SubPattern, Label
from masque.shapes import Polygon
from masque.builder import Device, Port, port_utils
from masque.file.gdsii import writefile
from masque import (
layer_t, Pattern, Ref, Label, Builder, Port, Polygon,
WrapLibrary, Library,
)
from masque.builder import port_utils
from masque.file.gdsii import writefile, check_valid_names
import pcgen
import basic_shapes
@ -17,38 +20,41 @@ LATTICE_CONSTANT = 512
RADIUS = LATTICE_CONSTANT / 2 * 0.75
def dev2pat(dev: Device) -> Pattern:
def dev2pat(dev: Pattern) -> Pattern:
"""
Bake port information into the device.
Bake port information into the pattern.
This places a label at each port location on layer (3, 0) with text content
'name:ptype angle_deg'
"""
return port_utils.dev2pat(dev, layer=(3, 0))
def pat2dev(pat: Pattern) -> Device:
def pat2dev(lib: Mapping[str, Pattern], name: str, pat: Pattern) -> Pattern:
"""
Scans the Pattern to determine port locations. Same format as `dev2pat`
"""
return port_utils.pat2dev(pat, layers=[(3, 0)])
return port_utils.pat2dev(layers=[(3, 0)], library=lib, pattern=pat, name=name)
def perturbed_l3(
lattice_constant: float,
hole: Pattern,
hole: str,
hole_lib: Mapping[str, Pattern],
trench_layer: layer_t = (1, 0),
shifts_a: Sequence[float] = (0.15, 0, 0.075),
shifts_r: Sequence[float] = (1.0, 1.0, 1.0),
xy_size: Tuple[int, int] = (10, 10),
perturbed_radius: float = 1.1,
trench_width: float = 1200,
) -> Device:
) -> Pattern:
"""
Generate a `Device` representing a perturbed L3 cavity.
Generate a `Pattern` representing a perturbed L3 cavity.
Args:
lattice_constant: Distance between nearest neighbor holes
hole: `Pattern` object containing a single hole
hole: name of a `Pattern` containing a single hole
hole_lib: Library which contains the `Pattern` object for hole.
Necessary because we need to know how big it is...
trench_layer: Layer for the trenches, default `(1, 0)`.
shifts_a: passed to `pcgen.l3_shift`; specifies lattice constant
(1 - multiplicative factor) for shifting holes adjacent to
@ -64,8 +70,10 @@ def perturbed_l3(
trench width: Width of the undercut trenches. Default 1200.
Returns:
`Device` object representing the L3 design.
`Pattern` object representing the L3 design.
"""
print('Generating perturbed L3...')
# Get hole positions and radii
xyr = pcgen.l3_shift_perturbed_defect(mirror_dims=xy_size,
perturbed_radius=perturbed_radius,
@ -73,15 +81,14 @@ def perturbed_l3(
shifts_r=shifts_r)
# Build L3 cavity, using references to the provided hole pattern
pat = Pattern(f'L3p-a{lattice_constant:g}rp{perturbed_radius:g}')
pat.subpatterns += [
SubPattern(hole, scale=r,
offset=(lattice_constant * x,
lattice_constant * y))
pat = Pattern()
pat.refs += [
Ref(hole, scale=r, offset=(lattice_constant * x,
lattice_constant * y))
for x, y, r in xyr]
# Add rectangular undercut aids
min_xy, max_xy = pat.get_bounds_nonempty()
min_xy, max_xy = pat.get_bounds_nonempty(hole_lib)
trench_dx = max_xy[0] - min_xy[0]
pat.shapes += [
@ -91,168 +98,180 @@ def perturbed_l3(
# Ports are at outer extents of the device (with y=0)
extent = lattice_constant * xy_size[0]
ports = {
'input': Port((-extent, 0), rotation=0, ptype='pcwg'),
'output': Port((extent, 0), rotation=pi, ptype='pcwg'),
}
pat.ports = dict(
input=Port((-extent, 0), rotation=0, ptype='pcwg'),
output=Port((extent, 0), rotation=pi, ptype='pcwg'),
)
return Device(pat, ports)
dev2pat(pat)
return pat
def waveguide(
lattice_constant: float,
hole: Pattern,
hole: str,
length: int,
mirror_periods: int,
) -> Device:
) -> Pattern:
"""
Generate a `Device` representing a photonic crystal line-defect waveguide.
Generate a `Pattern` representing a photonic crystal line-defect waveguide.
Args:
lattice_constant: Distance between nearest neighbor holes
hole: `Pattern` object containing a single hole
hole: name of a `Pattern` containing a single hole
length: Distance (number of mirror periods) between the input and output ports.
Ports are placed at lattice sites.
mirror_periods: Number of hole rows on each side of the line defect
Returns:
`Device` object representing the waveguide.
`Pattern` object representing the waveguide.
"""
# Generate hole locations
xy = pcgen.waveguide(length=length, num_mirror=mirror_periods)
# Build the pattern
pat = Pattern(f'_wg-a{lattice_constant:g}l{length}')
pat.subpatterns += [SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y))
for x, y in xy]
pat = Pattern()
pat.refs += [
Ref(hole, offset=(lattice_constant * x,
lattice_constant * y))
for x, y in xy]
# Ports are at outer edges, with y=0
extent = lattice_constant * length / 2
ports = {
'left': Port((-extent, 0), rotation=0, ptype='pcwg'),
'right': Port((extent, 0), rotation=pi, ptype='pcwg'),
}
return Device(pat, ports)
pat.ports = dict(
left=Port((-extent, 0), rotation=0, ptype='pcwg'),
right=Port((extent, 0), rotation=pi, ptype='pcwg'),
)
pat2dev(pat)
print(pat)
return pat
def bend(
lattice_constant: float,
hole: Pattern,
hole: str,
mirror_periods: int,
) -> Device:
) -> Pattern:
"""
Generate a `Device` representing a 60-degree counterclockwise bend in a photonic crystal
Generate a `Pattern` representing a 60-degree counterclockwise bend in a photonic crystal
line-defect waveguide.
Args:
lattice_constant: Distance between nearest neighbor holes
hole: `Pattern` object containing a single hole
hole: name of a `Pattern` containing a single hole
mirror_periods: Minimum number of mirror periods on each side of the line defect.
Returns:
`Device` object representing the waveguide bend.
`Pattern` object representing the waveguide bend.
Ports are named 'left' (input) and 'right' (output).
"""
# Generate hole locations
xy = pcgen.wgbend(num_mirror=mirror_periods)
# Build the pattern
pat= Pattern(f'_wgbend-a{lattice_constant:g}l{mirror_periods}')
pat.subpatterns += [
SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y))
pat= Pattern()
pat.refs += [
Ref(hole, offset=(lattice_constant * x,
lattice_constant * y))
for x, y in xy]
# Figure out port locations.
extent = lattice_constant * mirror_periods
ports = {
'left': Port((-extent, 0), rotation=0, ptype='pcwg'),
'right': Port((extent / 2,
extent * numpy.sqrt(3) / 2),
rotation=pi * 4 / 3, ptype='pcwg'),
}
return Device(pat, ports)
pat.ports = dict(
left=Port((-extent, 0), rotation=0, ptype='pcwg'),
right=Port((extent / 2,
extent * numpy.sqrt(3) / 2),
rotation=pi * 4 / 3, ptype='pcwg'),
)
pat2dev(pat)
return pat
def y_splitter(
lattice_constant: float,
hole: Pattern,
hole: str,
mirror_periods: int,
) -> Device:
) -> Pattern:
"""
Generate a `Device` representing a photonic crystal line-defect waveguide y-splitter.
Generate a `Pattern` representing a photonic crystal line-defect waveguide y-splitter.
Args:
lattice_constant: Distance between nearest neighbor holes
hole: `Pattern` object containing a single hole
hole: name of a `Pattern` containing a single hole
mirror_periods: Minimum number of mirror periods on each side of the line defect.
Returns:
`Device` object representing the y-splitter.
`Pattern` object representing the y-splitter.
Ports are named 'in', 'top', and 'bottom'.
"""
# Generate hole locations
xy = pcgen.y_splitter(num_mirror=mirror_periods)
# Build pattern
pat = Pattern(f'_wgsplit_half-a{lattice_constant:g}l{mirror_periods}')
pat.subpatterns += [
SubPattern(hole, offset=(lattice_constant * x,
lattice_constant * y))
pat = Pattern()
pat.refs += [
Ref(hole, offset=(lattice_constant * x,
lattice_constant * y))
for x, y in xy]
# Determine port locations
extent = lattice_constant * mirror_periods
ports = {
pat.ports = {
'in': Port((-extent, 0), rotation=0, ptype='pcwg'),
'top': Port((extent / 2, extent * numpy.sqrt(3) / 2), rotation=pi * 4 / 3, ptype='pcwg'),
'bot': Port((extent / 2, -extent * numpy.sqrt(3) / 2), rotation=pi * 2 / 3, ptype='pcwg'),
}
return Device(pat, ports)
pat2dev(pat)
return pat
def main(interactive: bool = True):
def main(interactive: bool = True) -> None:
# Generate some basic hole patterns
smile = basic_shapes.smile(RADIUS)
hole = basic_shapes.hole(RADIUS)
shape_lib = {
'smile': basic_shapes.smile(RADIUS),
'hole': basic_shapes.hole(RADIUS),
}
# Build some devices
a = LATTICE_CONSTANT
wg10 = waveguide(lattice_constant=a, hole=hole, length=10, mirror_periods=5).rename('wg10')
wg05 = waveguide(lattice_constant=a, hole=hole, length=5, mirror_periods=5).rename('wg05')
wg28 = waveguide(lattice_constant=a, hole=hole, length=28, mirror_periods=5).rename('wg28')
bend0 = bend(lattice_constant=a, hole=hole, mirror_periods=5).rename('bend0')
ysplit = y_splitter(lattice_constant=a, hole=hole, mirror_periods=5).rename('ysplit')
l3cav = perturbed_l3(lattice_constant=a, hole=smile, xy_size=(4, 10)).rename('l3cav') # uses smile :)
# Autogenerate port labels so that GDS will also contain port data
for device in [wg10, wg05, wg28, l3cav, ysplit, bend0]:
dev2pat(device)
devices = {}
devices['wg05'] = waveguide(lattice_constant=a, hole='hole', length=5, mirror_periods=5)
devices['wg10'] = waveguide(lattice_constant=a, hole='hole', length=10, mirror_periods=5)
devices['wg28'] = waveguide(lattice_constant=a, hole='hole', length=28, mirror_periods=5)
devices['wg90'] = waveguide(lattice_constant=a, hole='hole', length=90, mirror_periods=5)
devices['bend0'] = bend(lattice_constant=a, hole='hole', mirror_periods=5)
devices['ysplit'] = y_splitter(lattice_constant=a, hole='hole', mirror_periods=5)
devices['l3cav'] = perturbed_l3(lattice_constant=a, hole='smile', hole_lib=shape_lib, xy_size=(4, 10)) # uses smile :)
# Turn our dict of devices into a Library -- useful for getting abstracts
lib = WrapLibrary(devices)
abv = lib.abstract_view() # lets us use abv[cell] instead of lib.abstract(cell)
#
# Build a circuit
#
circ = Device(name='my_circuit', ports={})
circ = Builder(library=lib)
# Start by placing a waveguide. Call its ports "in" and "signal".
circ.place(wg10, offset=(0, 0), port_map={'left': 'in', 'right': 'signal'})
circ.place(abv['wg10'], offset=(0, 0), port_map={'left': 'in', 'right': 'signal'})
# Extend the signal path by attaching the "left" port of a waveguide.
# Since there is only one other port ("right") on the waveguide we
# are attaching (wg10), it automatically inherits the name "signal".
circ.plug(wg10, {'signal': 'left'})
circ.plug(abv['wg10'], {'signal': 'left'})
# Attach a y-splitter to the signal path.
# Since the y-splitter has 3 ports total, we can't auto-inherit the
# port name, so we have to specify what we want to name the unattached
# ports. We can call them "signal1" and "signal2".
circ.plug(ysplit, {'signal': 'in'}, {'top': 'signal1', 'bot': 'signal2'})
circ.plug(abv['ysplit'], {'signal': 'in'}, {'top': 'signal1', 'bot': 'signal2'})
# Add a waveguide to both signal ports, inheriting their names.
circ.plug(wg05, {'signal1': 'left'})
circ.plug(wg05, {'signal2': 'left'})
circ.plug(abv['wg05'], {'signal1': 'left'})
circ.plug(abv['wg05'], {'signal2': 'left'})
# Add a bend to both ports.
# Our bend's ports "left" and "right" refer to the original counterclockwise
@ -261,22 +280,22 @@ def main(interactive: bool = True):
# to "signal2" to bend counterclockwise.
# We could also use `mirrored=(True, False)` to mirror one of the devices
# and then use same device port on both paths.
circ.plug(bend0, {'signal1': 'right'})
circ.plug(bend0, {'signal2': 'left'})
circ.plug(abv['bend0'], {'signal1': 'right'})
circ.plug(abv['bend0'], {'signal2': 'left'})
# We add some waveguides and a cavity to "signal1".
circ.plug(wg10, {'signal1': 'left'})
circ.plug(l3cav, {'signal1': 'input'})
circ.plug(wg10, {'signal1': 'left'})
circ.plug(abv['wg10'], {'signal1': 'left'})
circ.plug(abv['l3cav'], {'signal1': 'input'})
circ.plug(abv['wg10'], {'signal1': 'left'})
# "signal2" just gets a single of equivalent length
circ.plug(wg28, {'signal2': 'left'})
circ.plug(abv['wg28'], {'signal2': 'left'})
# Now we bend both waveguides back towards each other
circ.plug(bend0, {'signal1': 'right'})
circ.plug(bend0, {'signal2': 'left'})
circ.plug(wg05, {'signal1': 'left'})
circ.plug(wg05, {'signal2': 'left'})
circ.plug(abv['bend0'], {'signal1': 'right'})
circ.plug(abv['bend0'], {'signal2': 'left'})
circ.plug(abv['wg05'], {'signal1': 'left'})
circ.plug(abv['wg05'], {'signal2': 'left'})
# To join the waveguides, we attach a second y-junction.
# We plug "signal1" into the "bot" port, and "signal2" into the "top" port.
@ -284,23 +303,37 @@ def main(interactive: bool = True):
# This operation would raise an exception if the ports did not line up
# correctly (i.e. they required different rotations or translations of the
# y-junction device).
circ.plug(ysplit, {'signal1': 'bot', 'signal2': 'top'}, {'in': 'signal_out'})
circ.plug(abv['ysplit'], {'signal1': 'bot', 'signal2': 'top'}, {'in': 'signal_out'})
# Finally, add some more waveguide to "signal_out".
circ.plug(wg10, {'signal_out': 'left'})
# We can visualize the design. Usually it's easier to just view the GDS.
if interactive:
print('Visualizing... this step may be slow')
circ.pattern.visualize()
circ.plug(abv['wg10'], {'signal_out': 'left'})
# We can also add text labels for our circuit's ports.
# They will appear at the uppermost hierarchy level, while the individual
# device ports will appear further down, in their respective cells.
dev2pat(circ)
pat2dev(circ.pattern)
# Write out to GDS
writefile(circ.pattern, 'circuit.gds', **GDS_OPTS)
# Add the pattern into our library
lib['my_circuit'] = circ.pattern
# Check if we forgot to include any patterns... ooops!
if dangling := lib.dangling_refs():
print('Warning: The following patterns are referenced, but not present in the'
f'library! {dangling}')
print('We\'ll solve this by merging in shape_lib, which contains those shapes...')
lib.add(shape_lib)
assert not lib.dangling_refs()
# We can visualize the design. Usually it's easier to just view the GDS.
if interactive:
print('Visualizing... this step may be slow')
circ.pattern.visualize(lib)
#Write out to GDS, only keeping patterns referenced by our circuit (including itself)
subtree = lib.subtree('my_circuit') # don't include wg90, which we don't use
check_valid_names(subtree.keys())
writefile(subtree, 'circuit.gds', **GDS_OPTS)
if __name__ == '__main__':

View file

@ -4,8 +4,7 @@ from pprint import pformat
import numpy
from numpy import pi
from masque.builder import Device
from masque.library import Library, LibDeviceLibrary
from masque import Pattern, Builder, WrapLibrary, LazyLibrary, Library
from masque.file.gdsii import writefile, load_libraryfile
import pcgen
@ -16,66 +15,62 @@ from basic_shapes import GDS_OPTS
def main() -> None:
# Define a `Library`-backed `DeviceLibrary`, which provides lazy evaluation
# for device generation code and lazy-loading of GDS contents.
device_lib = LibDeviceLibrary()
# Define a `LazyLibrary`, which provides lazy evaluation for generating
# patterns and lazy-loading of GDS contents.
lib = LazyLibrary()
#
# Load some devices from a GDS file
#
# Scan circuit.gds and prepare to lazy-load its contents
pattern_lib, _properties = load_libraryfile('circuit.gds', tag='mycirc01')
gds_lib, _properties = load_libraryfile('circuit.gds', postprocess=pat2dev)
# Add it into the device library by providing a way to read port info
# This maintains the lazy evaluation from above, so no patterns
# are actually read yet.
device_lib.add_library(pattern_lib, pat2dev=pat2dev)
print('Devices loaded from GDS into library:\n' + pformat(list(device_lib.keys())))
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
#
a = devices.LATTICE_CONSTANT
tri = basic_shapes.triangle(devices.RADIUS)
# Convenience function for adding devices
# This is roughly equivalent to
# `device_lib[name] = lambda: dev2pat(fn())`
# but it also guarantees that the resulting pattern is named `name`.
def add(name: str, fn: Callable[[], Device]) -> None:
device_lib.add_device(name=name, fn=fn, dev2pat=dev2pat)
lib['triangle'] = lambda: basic_shapes.triangle(devices.RADIUS)
opts = dict(
lattice_constant = devices.LATTICE_CONSTANT,
hole = 'triangle',
)
# Triangle-based variants. These are defined here, but they won't run until they're
# retrieved from the library.
add('tri_wg10', lambda: devices.waveguide(lattice_constant=a, hole=tri, length=10, mirror_periods=5))
add('tri_wg05', lambda: devices.waveguide(lattice_constant=a, hole=tri, length=5, mirror_periods=5))
add('tri_wg28', lambda: devices.waveguide(lattice_constant=a, hole=tri, length=28, mirror_periods=5))
add('tri_bend0', lambda: devices.bend(lattice_constant=a, hole=tri, mirror_periods=5))
add('tri_ysplit', lambda: devices.y_splitter(lattice_constant=a, hole=tri, mirror_periods=5))
add('tri_l3cav', lambda: devices.perturbed_l3(lattice_constant=a, hole=tri, xy_size=(4, 10)))
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
#
# Immediately start building from an instance of the L3 cavity
circ2 = device_lib['tri_l3cav'].build('mixed_wg_cav')
circ2 = Builder(library=lib, ports='tri_l3cav')
print(device_lib['wg10'].ports)
circ2.plug(device_lib['wg10'], {'input': 'right'})
circ2.plug(device_lib['wg10'], {'output': 'left'})
circ2.plug(device_lib['tri_wg10'], {'input': 'right'})
circ2.plug(device_lib['tri_wg10'], {'output': 'left'})
print(lib['wg10'].ports)
circ2.plug(lib.abstract('wg10'), {'input': 'right'})
abstracts = lib.abstract_view() # Alternate way to get abstracts
circ2.plug(abstracts['wg10'], {'output': 'left'})
circ2.plug(abstracts['tri_wg10'], {'input': 'right'})
circ2.plug(abstracts['tri_wg10'], {'output': 'left'})
# Add the circuit to the device library.
# It has already been generated, so we can use `set_const` as a shorthand for
# `device_lib['mixed_wg_cav'] = lambda: circ2`
device_lib.set_const(circ2)
# `lib['mixed_wg_cav'] = lambda: circ2.pattern`
lib.set_const('mixed_wg_cav', circ2.pattern)
#
@ -83,29 +78,26 @@ def main() -> None:
#
# We'll be designing against an existing device's interface...
circ3 = circ2.as_interface('loop_segment')
# ... that lets us continue from where we left off.
circ3.plug(device_lib['tri_bend0'], {'input': 'right'})
circ3.plug(device_lib['tri_bend0'], {'input': 'left'}, mirrored=(True, False)) # mirror since no tri y-symmetry
circ3.plug(device_lib['tri_bend0'], {'input': 'right'})
circ3.plug(device_lib['bend0'], {'output': 'left'})
circ3.plug(device_lib['bend0'], {'output': 'left'})
circ3.plug(device_lib['bend0'], {'output': 'left'})
circ3.plug(device_lib['tri_wg10'], {'input': 'right'})
circ3.plug(device_lib['tri_wg28'], {'input': 'right'})
circ3.plug(device_lib['tri_wg10'], {'input': 'right', 'output': 'left'})
circ3 = Builder.interface(source=circ2)
device_lib.set_const(circ3)
# ... that lets us continue from where we left off.
circ3.plug(abstracts['tri_bend0'], {'input': 'right'})
circ3.plug(abstracts['tri_bend0'], {'input': 'left'}, mirrored=(True, False)) # mirror since no tri y-symmetry
circ3.plug(abstracts['tri_bend0'], {'input': 'right'})
circ3.plug(abstracts['bend0'], {'output': 'left'})
circ3.plug(abstracts['bend0'], {'output': 'left'})
circ3.plug(abstracts['bend0'], {'output': 'left'})
circ3.plug(abstracts['tri_wg10'], {'input': 'right'})
circ3.plug(abstracts['tri_wg28'], {'input': 'right'})
circ3.plug(abstracts['tri_wg10'], {'input': 'right', 'output': 'left'})
lib.set_const('loop_segment', circ3.pattern)
#
# Write all devices into a GDS file
#
# This line could be slow, since it generates or loads many of the devices
# since they were not all accessed above.
all_device_pats = [dev.pattern for dev in device_lib.values()]
writefile(all_device_pats, 'library.gds', **GDS_OPTS)
print('Writing library to file...')
writefile(lib, 'library.gds', **GDS_OPTS)
if __name__ == '__main__':
@ -116,14 +108,14 @@ if __name__ == '__main__':
#class prout:
# def place(
# self,
# other: Device,
# other: Pattern,
# label_layer: layer_t = 'WATLAYER',
# *,
# port_map: Optional[Dict[str, Optional[str]]] = None,
# **kwargs,
# ) -> 'prout':
#
# Device.place(self, other, port_map=port_map, **kwargs)
# Pattern.place(self, other, port_map=port_map, **kwargs)
# name: Optional[str]
# for name in other.ports:
# if port_map:

View file

@ -29,8 +29,11 @@ def triangular_lattice(
Returns:
`[[x0, y0], [x1, 1], ...]` denoting lattice sites.
"""
sx, sy = numpy.meshgrid(numpy.arange(dims[0], dtype=float),
numpy.arange(dims[1], dtype=float), indexing='ij')
sx, sy = numpy.meshgrid(
numpy.arange(dims[0], dtype=float),
numpy.arange(dims[1], dtype=float),
indexing='ij',
)
sx[sy % 2 == 1] += 0.5
sy *= numpy.sqrt(3) / 2