Lots of progress on tutorials
This commit is contained in:
parent
34a5369a55
commit
22735125d5
174
examples/phc.py
174
examples/phc.py
@ -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()
|
@ -0,0 +1 @@
|
||||
TODO write tutorial readme
|
@ -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,7 +50,7 @@ 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)),
|
||||
@ -58,7 +58,7 @@ def triangle(
|
||||
(numpy.cos( - pi / 6), numpy.sin( - pi / 6)),
|
||||
]) * 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__':
|
||||
|
@ -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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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'),
|
||||
}
|
||||
return Device(pat, ports)
|
||||
)
|
||||
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,
|
||||
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__':
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -1,27 +1,38 @@
|
||||
from typing import Dict, Union, Optional
|
||||
from typing import MutableMapping, TYPE_CHECKING
|
||||
from typing import Dict
|
||||
#from typing import Union, Optional, MutableMapping, TYPE_CHECKING
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from .pattern import Pattern
|
||||
#from .pattern import Pattern
|
||||
from .ports import PortList, Port
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .builder import Builder, Tool
|
||||
from .library import MutableLibrary
|
||||
#if TYPE_CHECKING:
|
||||
# from .builder import Builder, Tool
|
||||
# from .library import MutableLibrary
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AA = TypeVar('AA', bound='Abstract')
|
||||
|
||||
|
||||
class Abstract(PortList):
|
||||
__slots__ = ('name', 'ports')
|
||||
__slots__ = ('name', '_ports')
|
||||
|
||||
name: str
|
||||
""" Name of the pattern this device references """
|
||||
|
||||
ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to snap instances together"""
|
||||
_ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to instances together"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self._ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self._ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -31,22 +42,22 @@ class Abstract(PortList):
|
||||
self.name = name
|
||||
self.ports = copy.deepcopy(ports)
|
||||
|
||||
def build(
|
||||
self,
|
||||
library: 'MutableLibrary',
|
||||
tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None,
|
||||
) -> 'Builder':
|
||||
"""
|
||||
Begin building a new device around an instance of the current device
|
||||
(rather than modifying the current device).
|
||||
|
||||
Returns:
|
||||
The new `Builder` object.
|
||||
"""
|
||||
pat = Pattern(ports=self.ports)
|
||||
pat.ref(self.name)
|
||||
new = Builder(library=library, pattern=pat, tools=tools) # TODO should Abstract have tools?
|
||||
return new
|
||||
# def build(
|
||||
# self,
|
||||
# library: 'MutableLibrary',
|
||||
# tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None,
|
||||
# ) -> 'Builder':
|
||||
# """
|
||||
# Begin building a new device around an instance of the current device
|
||||
# (rather than modifying the current device).
|
||||
#
|
||||
# Returns:
|
||||
# The new `Builder` object.
|
||||
# """
|
||||
# pat = Pattern(ports=self.ports)
|
||||
# pat.ref(self.name)
|
||||
# new = Builder(library=library, pattern=pat, tools=tools) # TODO should Abstract have tools?
|
||||
# return new
|
||||
|
||||
# TODO do we want to store a Ref instead of just a name? then we can translate/rotate/mirror...
|
||||
|
||||
@ -56,3 +67,164 @@ class Abstract(PortList):
|
||||
s += f'\n\t{name}: {port}'
|
||||
s += ']>'
|
||||
return s
|
||||
|
||||
def translate_ports(self: AA, offset: ArrayLike) -> AA:
|
||||
"""
|
||||
Translates all ports by the given offset.
|
||||
|
||||
Args:
|
||||
offset: (x, y) to translate by
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.translate(offset)
|
||||
return self
|
||||
|
||||
def scale_by(self: AA, c: float) -> AA:
|
||||
"""
|
||||
Scale this Abstract by the given value
|
||||
(all port offsets are scaled)
|
||||
|
||||
Args:
|
||||
c: factor to scale by
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset *= c
|
||||
return self
|
||||
|
||||
def rotate_around(self: AA, pivot: ArrayLike, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate the Abstract around the a location.
|
||||
|
||||
Args:
|
||||
pivot: (x, y) location to rotate around
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pivot = numpy.array(pivot)
|
||||
self.translate_ports(-pivot)
|
||||
self.rotate_ports(rotation)
|
||||
self.rotate_port_offsets(rotation)
|
||||
self.translate_ports(+pivot)
|
||||
return self
|
||||
|
||||
def rotate_port_offsets(self: AA, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate the offsets of all ports around (0, 0)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset = rotation_matrix_2d(rotation) @ port.offset
|
||||
return self
|
||||
|
||||
def rotate_ports(self: AA, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate each port around its offset (i.e. in place)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.rotate(rotation)
|
||||
return self
|
||||
|
||||
def mirror_port_offsets(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
||||
|
||||
Args:
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset[across_axis - 1] *= -1
|
||||
return self
|
||||
|
||||
def mirror_ports(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror each port's rotation across an axis, relative to its
|
||||
offset
|
||||
|
||||
Args:
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.mirror(across_axis)
|
||||
return self
|
||||
|
||||
def mirror(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror the Pattern across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.mirror_ports(across_axis)
|
||||
self.mirror_port_offsets(across_axis)
|
||||
return self
|
||||
|
||||
def apply_ref_transform(self: AA, ref: Ref) -> AA:
|
||||
"""
|
||||
Apply the transform from a `Ref` to the ports of this `Abstract`.
|
||||
This changes the port locations to where they would be in the Ref's parent pattern.
|
||||
|
||||
Args:
|
||||
ref: The ref whose transform should be applied.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
mirror_across_x, angle = normalize_mirror(ref.mirrored)
|
||||
if mirrored_across_x:
|
||||
self.mirror(across_axis=0)
|
||||
self.rotate_ports(angle + ref.rotation)
|
||||
self.rotate_port_offsets(angle + ref.rotation)
|
||||
self.translate_ports(ref.offset)
|
||||
return self
|
||||
|
||||
def undo_ref_transform(self: AA, ref: Ref) -> AA:
|
||||
"""
|
||||
Apply the inverse transform from a `Ref` to the ports of this `Abstract`.
|
||||
This changes the port locations to where they would be in the Ref's target (from the parent).
|
||||
|
||||
Args:
|
||||
ref: The ref whose (inverse) transform should be applied.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
# TODO test undo_ref_transform
|
||||
"""
|
||||
mirror_across_x, angle = normalize_mirror(ref.mirrored)
|
||||
self.translate_ports(-ref.offset)
|
||||
self.rotate_port_offsets(-angle - ref.rotation)
|
||||
self.rotate_ports(-angle - ref.rotation)
|
||||
if mirrored_across_x:
|
||||
self.mirror(across_axis=0)
|
||||
return self
|
||||
|
@ -97,19 +97,30 @@ class Builder(PortList):
|
||||
_dead: bool
|
||||
""" If True, plug()/place() are skipped (for debugging)"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self.pattern.ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self.pattern.ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library: MutableLibrary,
|
||||
*,
|
||||
pattern: Optional[Pattern] = None,
|
||||
ports: Optional[Mapping[str, Port]] = None,
|
||||
ports: Union[None, str, Mapping[str, Port]] = None,
|
||||
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
If `ports` is `None`, two default ports ('A' and 'B') are created.
|
||||
Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0
|
||||
(attached devices will be placed to the left) and 'B' has rotation
|
||||
pi (attached devices will be placed to the right).
|
||||
# TODO documentation for Builder() constructor
|
||||
|
||||
# TODO MOVE THE BELOW DOCS to PortList
|
||||
# If `ports` is `None`, two default ports ('A' and 'B') are created.
|
||||
# Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0
|
||||
# (attached devices will be placed to the left) and 'B' has rotation
|
||||
# pi (attached devices will be placed to the right).
|
||||
"""
|
||||
self.library = library
|
||||
if pattern is not None:
|
||||
@ -120,7 +131,10 @@ class Builder(PortList):
|
||||
if ports is not None:
|
||||
if self.pattern.ports:
|
||||
raise BuildError('Ports supplied for pattern with pre-existing ports!')
|
||||
self.pattern.ports.update(copy.deepcopy(ports))
|
||||
if isinstance(ports, str):
|
||||
ports = library.abstract(ports).ports
|
||||
|
||||
self.pattern.ports.update(copy.deepcopy(dict(ports)))
|
||||
|
||||
if tools is None:
|
||||
self.tools = {}
|
||||
@ -509,7 +523,7 @@ class Builder(PortList):
|
||||
in_ptype = self.pattern[portspec].ptype
|
||||
pat = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
|
||||
name = self.library.get_name(base_name)
|
||||
self.library._set(name, pat)
|
||||
self.library.set_const(name, pat)
|
||||
return self.plug(Abstract(name, pat.ports), {portspec: tool_port_names[0]})
|
||||
|
||||
def path_to(
|
||||
@ -592,7 +606,7 @@ class Builder(PortList):
|
||||
for port_name, length in extensions.items():
|
||||
bld.path(port_name, ccw, length, tool_port_names=tool_port_names)
|
||||
name = self.library.get_name(base_name)
|
||||
self.library._set(name, bld.pattern)
|
||||
self.library.set_const(name, bld.pattern)
|
||||
return self.plug(Abstract(name, bld.pattern.ports), {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'?
|
||||
|
||||
# TODO def path_join() and def bus_join()?
|
||||
|
@ -5,10 +5,11 @@ Functions for writing port data into a Pattern (`dev2pat`) and retrieving it (`p
|
||||
the port locations. This particular approach is just a sensible default; feel free to
|
||||
to write equivalent functions for your own format or alternate storage methods.
|
||||
"""
|
||||
from typing import Sequence, Optional, Mapping
|
||||
from typing import Sequence, Optional, Mapping, Tuple, Dict
|
||||
import logging
|
||||
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from ..pattern import Pattern
|
||||
from ..label import Label
|
||||
@ -50,13 +51,16 @@ def dev2pat(pattern: Pattern, layer: layer_t) -> Pattern:
|
||||
|
||||
|
||||
def pat2dev(
|
||||
library: Mapping[str, Pattern],
|
||||
top: str,
|
||||
layers: Sequence[layer_t],
|
||||
library: Mapping[str, Pattern],
|
||||
pattern: Pattern,
|
||||
name: Optional[str] = None,
|
||||
max_depth: int = 999_999,
|
||||
skip_subcells: bool = True,
|
||||
) -> Pattern:
|
||||
"""
|
||||
# TODO fixup documentation in port_utils
|
||||
# TODO move port_utils to utils.file?
|
||||
Examine `pattern` for labels specifying port info, and use that info
|
||||
to fill out its `ports` attribute.
|
||||
|
||||
@ -64,8 +68,8 @@ def pat2dev(
|
||||
'name:ptype angle_deg'
|
||||
|
||||
Args:
|
||||
pattern: Pattern object to scan for labels.
|
||||
layers: Search for labels on all the given layers.
|
||||
pattern: Pattern object to scan for labels.
|
||||
max_depth: Maximum hierarcy depth to search. Default 999_999.
|
||||
Reduce this to 0 to avoid ever searching subcells.
|
||||
skip_subcells: If port labels are found at a given hierarcy level,
|
||||
@ -73,52 +77,51 @@ def pat2dev(
|
||||
to contain their own port info without interfering with supercells'
|
||||
port data.
|
||||
Default True.
|
||||
blacklist: If a cell name appears in the blacklist, do not ea
|
||||
|
||||
Returns:
|
||||
The updated `pattern`. Port labels are not removed.
|
||||
"""
|
||||
print(f'TODO pat2dev {name}')
|
||||
if pattern.ports:
|
||||
logger.warning(f'Pattern {name if name else pattern} already had ports, skipping pat2dev')
|
||||
return pattern
|
||||
|
||||
if not isinstance(library, Library):
|
||||
library = WrapROLibrary(library)
|
||||
|
||||
ports = {}
|
||||
annotated_cells = set()
|
||||
pat2dev_flat(layers, pattern, name)
|
||||
if (skip_subcells and pattern.ports) or max_depth == 0:
|
||||
return pattern
|
||||
|
||||
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
||||
if len(hierarchy) > max_depth:
|
||||
if max_depth >= 999_999:
|
||||
logger.warning(f'pat2dev reached max depth ({max_depth})')
|
||||
return pat
|
||||
# Load ports for all subpatterns
|
||||
for target in set(rr.target for rr in pat.refs):
|
||||
pp = pat2dev(
|
||||
layers=layers,
|
||||
library=library,
|
||||
pattern=library[target],
|
||||
name=target,
|
||||
max_depth=max_depth-1,
|
||||
skip_subcells=skip_subcells,
|
||||
blacklist=blacklist + {name},
|
||||
)
|
||||
found_ports |= bool(pp.ports)
|
||||
|
||||
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
|
||||
return pat
|
||||
for ref in pat.refs:
|
||||
aa = library.abstract(ref.target)
|
||||
if not aa.ports:
|
||||
continue
|
||||
|
||||
cell_name = hierarchy[-1]
|
||||
pat2dev_flat(pat, cell_name)
|
||||
aa.apply_ref_transform(ref)
|
||||
|
||||
if skip_subcells:
|
||||
annotated_cells.add(cell_name)
|
||||
|
||||
mirr_factor = numpy.array((1, -1)) ** transform[3]
|
||||
rot_matrix = rotation_matrix_2d(transform[2])
|
||||
for name, port in pat.ports.items():
|
||||
port.offset = transform[:2] + rot_matrix @ (port.offset * mirr_factor)
|
||||
port.rotation = port.rotation * mirr_factor[0] * mirr_factor[1] + transform[2]
|
||||
ports[name] = port
|
||||
|
||||
return pat
|
||||
|
||||
# update `ports`
|
||||
library.dfs(top=top, visit_before=find_ports_each, transform=True)
|
||||
|
||||
pattern = library[top]
|
||||
pattern.check_ports(other_names=ports.keys())
|
||||
pattern.ports.update(ports)
|
||||
pattern.check_ports(other_names=aa.ports.keys())
|
||||
pattern.ports.update(aa.ports)
|
||||
return pattern
|
||||
|
||||
|
||||
def pat2dev_flat(
|
||||
pattern: Pattern,
|
||||
layers: Sequence[layer_t],
|
||||
pattern: Pattern,
|
||||
cell_name: Optional[str] = None,
|
||||
) -> Pattern:
|
||||
"""
|
||||
@ -131,8 +134,8 @@ def pat2dev_flat(
|
||||
The pattern is assumed to be flat (have no `refs`) and have no pre-existing ports.
|
||||
|
||||
Args:
|
||||
pattern: Pattern object to scan for labels.
|
||||
layers: Search for labels on all the given layers.
|
||||
pattern: Pattern object to scan for labels.
|
||||
cell_name: optional, used for warning message only
|
||||
|
||||
Returns:
|
||||
|
@ -10,11 +10,11 @@ from ..utils import rotation_matrix_2d
|
||||
from ..error import BuildError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..ports import Port, PortList
|
||||
from ..ports import Port
|
||||
|
||||
|
||||
def ell(
|
||||
ports: Union[Mapping[str, 'Port'], 'PortList'],
|
||||
ports: Mapping[str, 'Port'],
|
||||
ccw: Optional[bool],
|
||||
bound_type: str,
|
||||
bound: Union[float, ArrayLike],
|
||||
@ -83,9 +83,6 @@ def ell(
|
||||
if not ports:
|
||||
raise BuildError('Empty port list passed to `ell()`')
|
||||
|
||||
if isinstance(ports, PortList):
|
||||
ports = PortList.ports
|
||||
|
||||
if ccw is None:
|
||||
if spacing is not None and not numpy.isclose(spacing, 0):
|
||||
raise BuildError('Spacing must be 0 or None when ccw=None')
|
||||
|
@ -6,11 +6,8 @@ Notes:
|
||||
* ezdxf sets creation time, write time, $VERSIONGUID, and $FINGERPRINTGUID
|
||||
to unique values, so byte-for-byte reproducibility is not achievable for now
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Mapping, TextIO
|
||||
import re
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Mapping, TextIO
|
||||
import io
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
@ -60,7 +57,7 @@ def write(
|
||||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
|
@ -19,8 +19,8 @@ Notes:
|
||||
* GDS creation/modification/access times are set to 1900-01-01 for reproducibility.
|
||||
* Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01)
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable
|
||||
from typing import BinaryIO, Mapping, cast
|
||||
from typing import List, Dict, Tuple, Callable, Union, Iterable, Mapping
|
||||
from typing import BinaryIO, cast, Optional, Any
|
||||
import io
|
||||
import mmap
|
||||
import logging
|
||||
@ -39,7 +39,7 @@ from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
|
||||
from ..shapes import Polygon, Path
|
||||
from ..repetition import Grid
|
||||
from ..utils import layer_t, normalize_mirror, annotations_t
|
||||
from ..library import LazyLibrary, WrapLibrary, MutableLibrary
|
||||
from ..library import LazyLibrary, WrapLibrary, MutableLibrary, Library
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -84,7 +84,7 @@ def write(
|
||||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.gdsii.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
@ -188,6 +188,7 @@ def read(
|
||||
raw_mode: bool = True,
|
||||
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
||||
"""
|
||||
# TODO check GDSII file for cycles!
|
||||
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are
|
||||
translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs
|
||||
are translated into Ref objects.
|
||||
@ -511,6 +512,7 @@ def load_library(
|
||||
stream: BinaryIO,
|
||||
*,
|
||||
full_load: bool = False,
|
||||
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None
|
||||
) -> Tuple[LazyLibrary, Dict[str, Any]]:
|
||||
"""
|
||||
Scan a GDSII stream to determine what structures are present, and create
|
||||
@ -526,6 +528,8 @@ def load_library(
|
||||
full_load: If True, force all structures to be read immediately rather
|
||||
than as-needed. Since data is read sequentially from the file, this
|
||||
will be faster than using the resulting library's `precache` method.
|
||||
postprocess: If given, this function is used to post-process each
|
||||
pattern *upon first load only*.
|
||||
|
||||
Returns:
|
||||
LazyLibrary object, allowing for deferred load of structures.
|
||||
@ -548,9 +552,14 @@ def load_library(
|
||||
for name_bytes, pos in structs.items():
|
||||
name = name_bytes.decode('ASCII')
|
||||
|
||||
def mkstruct(pos: int = pos) -> Pattern:
|
||||
def mkstruct(pos: int = pos, name: str = name) -> Pattern:
|
||||
logger.error(f'mkstruct {name} @ {pos:x}')
|
||||
stream.seek(pos)
|
||||
return read_elements(stream, raw_mode=True)
|
||||
pat = read_elements(stream, raw_mode=True)
|
||||
if postprocess is not None:
|
||||
pat = postprocess(lib, name, pat)
|
||||
logger.error(f'mkstruct post {name} @ {pos:x}')
|
||||
return pat
|
||||
|
||||
lib[name] = mkstruct
|
||||
|
||||
@ -562,6 +571,7 @@ def load_libraryfile(
|
||||
*,
|
||||
use_mmap: bool = True,
|
||||
full_load: bool = False,
|
||||
postprocess: Optional[Callable[[Library, str], Pattern]] = None
|
||||
) -> Tuple[LazyLibrary, Dict[str, Any]]:
|
||||
"""
|
||||
Wrapper for `load_library()` that takes a filename or path instead of a stream.
|
||||
@ -578,6 +588,7 @@ def load_libraryfile(
|
||||
is decompressed into a python `bytes` object in memory
|
||||
and reopened as an `io.BytesIO` stream.
|
||||
full_load: If `True`, immediately loads all data. See `load_library`.
|
||||
postprocess: Passed to `load_library`
|
||||
|
||||
Returns:
|
||||
LazyLibrary object, allowing for deferred load of structures.
|
||||
@ -599,7 +610,7 @@ def load_libraryfile(
|
||||
stream = mmap.mmap(base_stream.fileno(), 0, access=mmap.ACCESS_READ) # type: ignore
|
||||
else:
|
||||
stream = open(path, mode='rb')
|
||||
return load_library(stream, full_load=full_load)
|
||||
return load_library(stream, full_load=full_load, postprocess=postprocess)
|
||||
|
||||
|
||||
def check_valid_names(
|
||||
|
@ -78,7 +78,7 @@ def build(
|
||||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon`, `masque.shapes.Path`, or `masque.shapes.Circle`
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
def __repr__(self) -> str:
|
||||
return '<Library with keys\n' + pformat(list(self.keys())) + '>'
|
||||
|
||||
def dangling_references(
|
||||
def dangling_refs(
|
||||
self,
|
||||
tops: Union[None, str, Sequence[str]] = None,
|
||||
) -> Set[Optional[str]]:
|
||||
@ -304,11 +304,11 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
|
||||
def dfs(
|
||||
self: L,
|
||||
top: str,
|
||||
pattern: 'Pattern',
|
||||
visit_before: Optional[visitor_function_t] = None,
|
||||
visit_after: Optional[visitor_function_t] = None,
|
||||
*,
|
||||
hierarchy: Tuple[str, ...] = (),
|
||||
hierarchy: Tuple[Optional[str], ...] = (None,),
|
||||
transform: Union[ArrayLike, bool, None] = False,
|
||||
memo: Optional[Dict] = None,
|
||||
) -> L:
|
||||
@ -317,23 +317,23 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
Performs a depth-first traversal of a pattern and its referenced patterns.
|
||||
At each pattern in the tree, the following sequence is called:
|
||||
```
|
||||
hierarchy += (top,)
|
||||
current_pattern = visit_before(current_pattern, **vist_args)
|
||||
for sp in current_pattern.refs]
|
||||
self.dfs(sp.target, visit_before, visit_after,
|
||||
hierarchy, updated_transform, memo)
|
||||
hierarchy + (sp.target,), updated_transform, memo)
|
||||
current_pattern = visit_after(current_pattern, **visit_args)
|
||||
```
|
||||
where `visit_args` are
|
||||
`hierarchy`: (top_pattern, L1_pattern, L2_pattern, ..., parent_pattern, current_pattern)
|
||||
tuple of all parent-and-higher pattern names
|
||||
`hierarchy`: (top_pattern_or_None, L1_pattern, L2_pattern, ..., parent_pattern)
|
||||
tuple of all parent-and-higher pattern names. Top pattern name may be
|
||||
`None` if not provided in first call to .dfs()
|
||||
`transform`: numpy.ndarray containing cumulative
|
||||
[x_offset, y_offset, rotation (rad), mirror_x (0 or 1)]
|
||||
for the instance being visited
|
||||
`memo`: Arbitrary dict (not altered except by `visit_before()` and `visit_after()`)
|
||||
|
||||
Args:
|
||||
top: Name of the pattern to start at (root node of the tree).
|
||||
pattern: Pattern object to start at ("top"/root node of the tree).
|
||||
visit_before: Function to call before traversing refs.
|
||||
Should accept a `Pattern` and `**visit_args`, and return the (possibly modified)
|
||||
pattern. Default `None` (not called).
|
||||
@ -345,8 +345,8 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
`True` or `None` is interpreted as `[0, 0, 0, 0]`.
|
||||
memo: Arbitrary dict for use by `visit_*()` functions. Default `None` (empty dict).
|
||||
hierarchy: Tuple of patterns specifying the hierarchy above the current pattern.
|
||||
Appended to the start of the generated `visit_args['hierarchy']`.
|
||||
Default is an empty tuple.
|
||||
Default is (None,), which will be used as a placeholder for the top pattern's
|
||||
name if not overridden.
|
||||
|
||||
Returns:
|
||||
self
|
||||
@ -359,16 +359,12 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
elif transform is not False:
|
||||
transform = numpy.array(transform)
|
||||
|
||||
if top in hierarchy:
|
||||
raise LibraryError('.dfs() called on pattern with circular reference')
|
||||
original_pattern = pattern
|
||||
|
||||
hierarchy += (top,)
|
||||
|
||||
pat = self[top]
|
||||
if visit_before is not None:
|
||||
pat = visit_before(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pattern = visit_before(pattern, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
|
||||
for ref in pat.refs:
|
||||
for ref in pattern.refs:
|
||||
if transform is not False:
|
||||
sign = numpy.ones(2)
|
||||
if transform[3]:
|
||||
@ -376,32 +372,38 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
xy = numpy.dot(rotation_matrix_2d(transform[2]), ref.offset * sign)
|
||||
mirror_x, angle = normalize_mirror(ref.mirrored)
|
||||
angle += ref.rotation
|
||||
sp_transform = transform + (xy[0], xy[1], angle, mirror_x)
|
||||
sp_transform[3] %= 2
|
||||
ref_transform = transform + (xy[0], xy[1], angle, mirror_x)
|
||||
ref_transform[3] %= 2
|
||||
else:
|
||||
sp_transform = False
|
||||
ref_transform = False
|
||||
|
||||
if ref.target is None:
|
||||
continue
|
||||
if ref.target in hierarchy:
|
||||
raise LibraryError(f'.dfs() called on pattern with circular reference to "{ref.target}"')
|
||||
|
||||
self.dfs(
|
||||
top=ref.target,
|
||||
pattern=self[ref.target],
|
||||
visit_before=visit_before,
|
||||
visit_after=visit_after,
|
||||
transform=sp_transform,
|
||||
hierarchy=hierarchy + (ref.target,),
|
||||
transform=ref_transform,
|
||||
memo=memo,
|
||||
hierarchy=hierarchy,
|
||||
)
|
||||
|
||||
if visit_after is not None:
|
||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pattern = visit_after(pattern, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
|
||||
if self[top] is not pat:
|
||||
if isinstance(self, MutableLibrary):
|
||||
self._set(top, pat)
|
||||
else:
|
||||
if pattern is not original_pattern:
|
||||
name = hierarchy[-1]
|
||||
if not isintance(self, MutableLibrary):
|
||||
raise LibraryError('visit_* functions returned a new `Pattern` object'
|
||||
' but the library is immutable')
|
||||
if name is None:
|
||||
raise LibraryError('visit_* functions returned a new `Pattern` object'
|
||||
' but no top-level name was provided in `hierarchy`')
|
||||
|
||||
self.set_const(name, pattern)
|
||||
|
||||
return self
|
||||
|
||||
@ -424,7 +426,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@ -564,7 +566,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
||||
del pat.shapes[i]
|
||||
|
||||
for ll, pp in shape_pats.items():
|
||||
self._set(label2name(ll), pp)
|
||||
self.set_const(label2name(ll), pp)
|
||||
|
||||
return self
|
||||
|
||||
@ -599,7 +601,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
||||
continue
|
||||
|
||||
name = name_func(pat, shape)
|
||||
self._set(name, Pattern(shapes=[shape]))
|
||||
self.set_const(name, Pattern(shapes=[shape]))
|
||||
pat.ref(name, repetition=shape.repetition)
|
||||
shape.repetition = None
|
||||
pat.shapes = new_shapes
|
||||
@ -610,7 +612,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
||||
new_labels.append(label)
|
||||
continue
|
||||
name = name_func(pat, label)
|
||||
self._set(name, Pattern(labels=[label]))
|
||||
self.set_const(name, Pattern(labels=[label]))
|
||||
pat.ref(name, repetition=label.repetition)
|
||||
label.repetition = None
|
||||
pat.labels = new_labels
|
||||
@ -692,7 +694,7 @@ class WrapLibrary(MutableLibrary):
|
||||
def __delitem__(self, key: str) -> None:
|
||||
del self.mapping[key]
|
||||
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
self[key] = value
|
||||
|
||||
def _merge(self, other: Mapping[str, 'Pattern'], key: str) -> None:
|
||||
@ -744,7 +746,7 @@ class LazyLibrary(MutableLibrary):
|
||||
def __len__(self) -> int:
|
||||
return len(self.dict)
|
||||
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
self[key] = lambda: value
|
||||
|
||||
def _merge(self, other: Mapping[str, 'Pattern'], key: str) -> None:
|
||||
@ -753,7 +755,7 @@ class LazyLibrary(MutableLibrary):
|
||||
if key in other.cache:
|
||||
self.cache[key] = other.cache[key]
|
||||
else:
|
||||
self._set(key, other[key])
|
||||
self.set_const(key, other[key])
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<LazyLibrary with keys\n' + pformat(list(self.keys())) + '>'
|
||||
|
@ -30,9 +30,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
(via Ref). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
||||
"""
|
||||
__slots__ = (
|
||||
'shapes', 'labels', 'refs', 'ports',
|
||||
'shapes', 'labels', 'refs', '_ports',
|
||||
# inherited
|
||||
'_offset', '_annotations'
|
||||
'_offset', '_annotations',
|
||||
)
|
||||
|
||||
shapes: List[Shape]
|
||||
@ -49,9 +49,17 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
(i.e. multiple instances of the same object).
|
||||
"""
|
||||
|
||||
ports: Dict[str, Port]
|
||||
_ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to snap to other Pattern instances"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self._ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self._ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@ -331,7 +339,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
def translate_elements(self: P, offset: ArrayLike) -> P:
|
||||
"""
|
||||
Translates all shapes, label, and refs by the given offset.
|
||||
Translates all shapes, label, refs, and ports by the given offset.
|
||||
|
||||
Args:
|
||||
offset: (x, y) to translate by
|
||||
@ -339,7 +347,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
cast(Positionable, entry).translate(offset)
|
||||
return self
|
||||
|
||||
@ -360,7 +368,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
def scale_by(self: P, c: float) -> P:
|
||||
"""
|
||||
Scale this Pattern by the given value
|
||||
(all shapes and refs and their offsets are scaled)
|
||||
(all shapes and refs and their offsets are scaled,
|
||||
as are all label and port offsets)
|
||||
|
||||
Args:
|
||||
c: factor to scale by
|
||||
@ -407,7 +416,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
|
||||
def rotate_element_centers(self: P, rotation: float) -> P:
|
||||
"""
|
||||
Rotate the offsets of all shapes, labels, and refs around (0, 0)
|
||||
Rotate the offsets of all shapes, labels, refs, and ports around (0, 0)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
@ -415,14 +424,14 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
old_offset = cast(Positionable, entry).offset
|
||||
cast(Positionable, entry).offset = numpy.dot(rotation_matrix_2d(rotation), old_offset)
|
||||
return self
|
||||
|
||||
def rotate_elements(self: P, rotation: float) -> P:
|
||||
"""
|
||||
Rotate each shape and refs around its center (offset)
|
||||
Rotate each shape, ref, and port around its origin (offset)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
@ -430,54 +439,54 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs):
|
||||
for entry in chain(self.shapes, self.refs, self.ports.values()):
|
||||
cast(Rotatable, entry).rotate(rotation)
|
||||
return self
|
||||
|
||||
def mirror_element_centers(self: P, axis: int) -> P:
|
||||
def mirror_element_centers(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
cast(Positionable, entry).offset[axis - 1] *= -1
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
cast(Positionable, entry).offset[across_axis - 1] *= -1
|
||||
return self
|
||||
|
||||
def mirror_elements(self: P, axis: int) -> P:
|
||||
def mirror_elements(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror each shape and refs across an axis, relative to its
|
||||
offset
|
||||
Mirror each shape, ref, and pattern across an axis, relative
|
||||
to its offset
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs):
|
||||
cast(Mirrorable, entry).mirror(axis)
|
||||
for entry in chain(self.shapes, self.refs, self.ports.values()):
|
||||
cast(Mirrorable, entry).mirror(across_axis)
|
||||
return self
|
||||
|
||||
def mirror(self: P, axis: int) -> P:
|
||||
def mirror(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror the Pattern across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.mirror_elements(axis)
|
||||
self.mirror_element_centers(axis)
|
||||
self.mirror_elements(across_axis)
|
||||
self.mirror_element_centers(across_axis)
|
||||
return self
|
||||
|
||||
def copy(self: P) -> P:
|
||||
|
@ -4,7 +4,7 @@ import warnings
|
||||
import traceback
|
||||
import logging
|
||||
from collections import Counter
|
||||
from abc import ABCMeta
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy
|
||||
from numpy import pi
|
||||
@ -103,10 +103,18 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, met
|
||||
|
||||
|
||||
class PortList(metaclass=ABCMeta):
|
||||
__slots__ = () # For use with AutoSlots
|
||||
__slots__ = () # Allow subclasses to use __slots__
|
||||
|
||||
ports: Dict[str, Port]
|
||||
@property
|
||||
@abstractmethod
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
""" Uniquely-named ports which can be used to snap to other Device instances"""
|
||||
pass
|
||||
|
||||
@ports.setter
|
||||
@abstractmethod
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: str) -> Port:
|
||||
|
Loading…
Reference in New Issue
Block a user