diff --git a/examples/tutorial/pather.py b/examples/tutorial/pather.py index 101fbb5..d4831bb 100644 --- a/examples/tutorial/pather.py +++ b/examples/tutorial/pather.py @@ -1,10 +1,10 @@ """ -Manual wire routing tutorial: Pather and BasicTool +Manual wire routing tutorial: Pather and AutoTool """ from collections.abc import Callable from numpy import pi from masque import Pather, RenderPather, Library, Pattern, Port, layer_t, map_layers -from masque.builder.tools import BasicTool, PathTool +from masque.builder.tools import AutoTool, PathTool from masque.file.gdsii import writefile from basic_shapes import GDS_OPTS @@ -110,28 +110,24 @@ def map_layer(layer: layer_t) -> layer_t: return layer_mapping.get(layer, layer) -# -# Now we can start building up our library (collection of static cells) and pathing tools. -# -# If any of the operations below are confusing, you can cross-reference against the `RenderPather` -# tutorial, which handles some things more explicitly (e.g. via placement) and simplifies others -# (e.g. geometry definition). -# -def main() -> None: +def prepare_tools() -> tuple[Library, Tool, Tool]: + """ + Create some basic library elements and tools for drawing M1 and M2 + """ # Build some patterns (static cells) using the above functions and store them in a library library = Library() library['pad'] = make_pad() library['m1_bend'] = make_bend(layer='M1', ptype='m1wire', width=M1_WIDTH) library['m2_bend'] = make_bend(layer='M2', ptype='m2wire', width=M2_WIDTH) library['v1_via'] = make_via( - layer_top='M2', - layer_via='V1', - layer_bot='M1', - width_top=M2_WIDTH, - width_via=V1_WIDTH, - width_bot=M1_WIDTH, - ptype_bot='m1wire', - ptype_top='m2wire', + layer_top = 'M2', + layer_via = 'V1', + layer_bot = 'M1', + width_top = M2_WIDTH, + width_via = V1_WIDTH, + width_bot = M1_WIDTH, + ptype_bot = 'm1wire', + ptype_top = 'm2wire', ) # @@ -140,53 +136,79 @@ def main() -> None: # M2_tool will route on M2, using wires with M2_WIDTH # Both tools are able to automatically transition from the other wire type (with a via) # - # Note that while we use BasicTool for this tutorial, you can define your own `Tool` + # Note that while we use AutoTool for this tutorial, you can define your own `Tool` # with arbitrary logic inside -- e.g. with single-use bends, complex transition rules, # transmission line geometry, or other features. # - M1_tool = BasicTool( - straight = ( - # First, we need a function which takes in a length and spits out an M1 wire - lambda length: make_straight_wire(layer='M1', ptype='m1wire', width=M1_WIDTH, length=length), - 'input', # When we get a pattern from make_straight_wire, use the port named 'input' as the input - 'output', # and use the port named 'output' as the output - ), - bend = ( - library.abstract('m1_bend'), # When we need a bend, we'll reference the pattern we generated earlier - 'input', # To orient it clockwise, use the port named 'input' as the input - 'output', # and 'output' as the output - ), + M1_tool = AutoTool( + # First, we need a function which takes in a length and spits out an M1 wire + straights = [ + AutoTool.Straight( + ptype = 'm1wire', + fn = lambda length: make_straight_wire(layer='M1', ptype='m1wire', width=M1_WIDTH, length=length), + in_port_name = 'input', # When we get a pattern from make_straight_wire, use the port named 'input' as the input + out_port_name = 'output', # and use the port named 'output' as the output + ), + ], + bends = [ + AutoTool.Bend( + abstract = library.abstract('m1_bend'), # When we need a bend, we'll reference the pattern we generated earlier + in_port_name = 'input', + out_port_name = 'output', + clockwise = True, + ), + ], transitions = { # We can automate transitions for different (normally incompatible) port types - 'm2wire': ( # For example, when we're attaching to a port with type 'm2wire' + ('m2wire', 'm1wire'): AutoTool.Transition( # For example, when we're attaching to a port with type 'm2wire' library.abstract('v1_via'), # we can place a V1 via 'top', # using the port named 'top' as the input (i.e. the M2 side of the via) 'bottom', # and using the port named 'bottom' as the output ), }, + sbends = [], default_out_ptype = 'm1wire', # Unless otherwise requested, we'll default to trying to stay on M1 ) - M2_tool = BasicTool( - straight = ( + M2_tool = AutoTool( + straights = [ # Again, we use make_straight_wire, but this time we set parameters for M2 - lambda length: make_straight_wire(layer='M2', ptype='m2wire', width=M2_WIDTH, length=length), - 'input', - 'output', - ), - bend = ( - library.abstract('m2_bend'), # and we use an M2 bend - 'input', - 'output', - ), + AutoTool.Straight( + ptype = 'm2wire', + fn = lambda length: make_straight_wire(layer='M2', ptype='m2wire', width=M2_WIDTH, length=length), + in_port_name = 'input', + out_port_name = 'output', + ), + ], + bends = [ + # and we use an M2 bend + AutoTool.Bend( + abstract = library.abstract('m2_bend'), + in_port_name = 'input', + out_port_name = 'output', + ), + ], transitions = { - 'm1wire': ( + ('m1wire', 'm2wire'): AutoTool.Transition( library.abstract('v1_via'), # We still use the same via, 'bottom', # but the input port is now 'bottom' 'top', # and the output port is now 'top' ), }, + sbends = [], default_out_ptype = 'm2wire', # We default to trying to stay on M2 ) + return library, M1_tool, M2_tool + + +# +# Now we can start building up our library (collection of static cells) and pathing tools. +# +# If any of the operations below are confusing, you can cross-reference against the `RenderPather` +# tutorial, which handles some things more explicitly (e.g. via placement) and simplifies others +# (e.g. geometry definition). +# +def main() -> None: + library, M1_tool, M2_tool = prepare_tools() # # Create a new pather which writes to `library` and uses `M2_tool` as its default tool. @@ -272,7 +294,7 @@ def main() -> None: pather.path_to('GND', None, -50_000) # Save the pather's pattern into our library - library['Pather_and_BasicTool'] = pather.pattern + library['Pather_and_AutoTool'] = pather.pattern # Convert from text-based layers to numeric layers for GDS, and output the file library.map_layers(map_layer) diff --git a/examples/tutorial/pcgen.py b/examples/tutorial/pcgen.py index 023079c..14d9994 100644 --- a/examples/tutorial/pcgen.py +++ b/examples/tutorial/pcgen.py @@ -2,7 +2,7 @@ Routines for creating normalized 2D lattices and common photonic crystal cavity designs. """ -from collection.abc import Sequence +from collections.abc import Sequence import numpy from numpy.typing import ArrayLike, NDArray @@ -198,11 +198,11 @@ def ln_defect( """ if defect_length % 2 != 1: raise Exception('defect_length must be odd!') - p = triangular_lattice([2 * d + 1 for d in mirror_dims]) + pp = triangular_lattice([2 * dd + 1 for dd in mirror_dims]) half_length = numpy.floor(defect_length / 2) hole_nums = numpy.arange(-half_length, half_length + 1) - holes_to_keep = numpy.in1d(p[:, 0], hole_nums, invert=True) - return p[numpy.logical_or(holes_to_keep, p[:, 1] != 0), ] + holes_to_keep = numpy.isin(pp[:, 0], hole_nums, invert=True) + return pp[numpy.logical_or(holes_to_keep, pp[:, 1] != 0), :] def ln_shift_defect( @@ -248,7 +248,7 @@ def ln_shift_defect( for sign in (-1, 1): x_val = sign * (x_removed + ind + 1) which = numpy.logical_and(xyr[:, 0] == x_val, xyr[:, 1] == 0) - xyr[which, ] = (x_val + numpy.sign(x_val) * shifts_a[ind], 0, shifts_r[ind]) + xyr[which, :] = (x_val + numpy.sign(x_val) * shifts_a[ind], 0, shifts_r[ind]) return xyr @@ -309,7 +309,7 @@ def l3_shift_perturbed_defect( # which holes should be perturbed? (xs[[3, 7]], ys[1]) and (xs[[2, 6]], ys[2]) perturbed_holes = ((xs[a], ys[b]) for a, b in ((3, 1), (7, 1), (2, 2), (6, 2))) - for row in xyr: - if numpy.fabs(row) in perturbed_holes: - row[2] = perturbed_radius + for xy in perturbed_holes: + which = (numpy.fabs(xyr[:, :2]) == xy).all(axis=1) + xyr[which, 2] = perturbed_radius return xyr diff --git a/examples/tutorial/port_pather.py b/examples/tutorial/port_pather.py new file mode 100644 index 0000000..f6f2a76 --- /dev/null +++ b/examples/tutorial/port_pather.py @@ -0,0 +1,86 @@ +""" +PortPather tutorial: Using .at() syntax for fluent port manipulation +""" +from numpy import pi +from masque import Pather, Library, Pattern, Port +from masque.builder.tools import AutoTool +from masque.file.gdsii import writefile + +from basic_shapes import GDS_OPTS +# Reuse helper functions and constants from the basic pather tutorial +from pather import ( + M1_WIDTH, V1_WIDTH, M2_WIDTH, + make_pad, make_via, make_bend, make_straight_wire, map_layer + +) + +def main() -> None: + # Reuse the same patterns (pads, bends, vias) and tools as in pather.py + library, M1_tool, M2_tool = prepare_tools() + library['pad'] = make_pad() + library['m1_bend'] = make_bend(layer='M1', ptype='m1wire', width=M1_WIDTH) + library['m2_bend'] = make_bend(layer='M2', ptype='m2wire', width=M2_WIDTH) + library['v1_via'] = make_via( + layer_top='M2', layer_via='V1', layer_bot='M1', + width_top=M2_WIDTH, width_via=V1_WIDTH, width_bot=M1_WIDTH, + ptype_bot='m1wire', ptype_top='m2wire', + ) + + # Create a RenderPather and place some initial pads (same as Pather tutorial) + rpather = RenderPather(library, tools=M2_tool) + + rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'}) + rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'}) + rpather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3)) + rpather.pattern.label(layer='M2', string='GND', offset=(18e3, 60e3)) + + # + # Routing with .at() + # + # The .at(port_name) method returns a PortPather object which wraps the Pather + # and remembers the selected port(s). This allows method chaining. + + # Then we can route just like in the other pather tutorials: + (rpather.at('VCC') + .path(ccw=False, length=6_000) + .path_to(ccw=None, x=0) + ) + + rpather.at('GND').path(0, 5_000).path_to(None, x=rpather['VCC'].x) + + + # We're using AutoTool so we could retool directly to M1_ptool like in the Pather + # tutorial, but let's manually plug to demonstrate what it looks like: + (rpather.at('GND') + .plug('v1_via', 'top') + .retool(M1_tool) # this only retools the 'GND' port + ) + + # We can also pass multiple ports to .at(), and then use .mpath() on them: + (rpather.at(['GND', 'VCC']) + .mpath(ccw=True, xmax=-10_000, spacing=5_000) + .retool(M1_tool) # this retools both ports + .mpath(ccw=True, emax=50_000, spacing=1_200) # ...causing an automatic via on VCC here + .mpath(ccw=False, emin=1_000, spacing=1_200) + .mpath(ccw=False, emin=2_000, spacing=4_500) + ) + + # Now we can finish up the equivalent to the other tutorials.. + rpather.at('VCC').retool(M2_tool) + rpather.at(['GND', 'VCC']).mpath(None, xmin=-28_000) + + rpather.at('VCC').path_to(None, x=-50_000, out_ptype='m1wire') + with pather.toolctx(M2_tool, keys=['GND']): + pather.at('GND').path_to(None, x=-40_000) + pather.at('GND').path_to(None, x=-50_000) + + # + # Save result + # + library['PortPather_Tutorial'] = pather.pattern + library.map_layers(map_layer) + writefile(library, 'port_pather.gds', **GDS_OPTS) + + +if __name__ == '__main__': + main() diff --git a/examples/tutorial/renderpather.py b/examples/tutorial/renderpather.py index cb002f3..3707fc6 100644 --- a/examples/tutorial/renderpather.py +++ b/examples/tutorial/renderpather.py @@ -25,14 +25,14 @@ def main() -> None: library = Library() library['pad'] = make_pad() library['v1_via'] = make_via( - layer_top='M2', - layer_via='V1', - layer_bot='M1', - width_top=M2_WIDTH, - width_via=V1_WIDTH, - width_bot=M1_WIDTH, - ptype_bot='m1wire', - ptype_top='m2wire', + layer_top = 'M2', + layer_via = 'V1', + layer_bot = 'M1', + width_top = M2_WIDTH, + width_via = V1_WIDTH, + width_bot = M1_WIDTH, + ptype_bot = 'm1wire', + ptype_top = 'm2wire', ) # `PathTool` is more limited than `BasicTool`. It only generates one type of shape @@ -42,7 +42,7 @@ def main() -> None: M2_ptool = PathTool(layer='M2', width=M2_WIDTH, ptype='m2wire') rpather = RenderPather(tools=M2_ptool, library=library) - # As in the pather tutorial, we make soem pads and labels... + # As in the pather tutorial, we make some pads and labels... rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'}) rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'}) rpather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3)) @@ -52,7 +52,7 @@ def main() -> None: rpather.path('VCC', ccw=False, length=6_000) rpather.path_to('VCC', ccw=None, x=0) rpather.path('GND', 0, 5_000) - rpather.path_to('GND', None, x=rpather['VCC'].offset[0]) + rpather.path_to('GND', None, x=rpather['VCC'].x) # `PathTool` doesn't know how to transition betwen metal layers, so we have to # `plug` the via into the GND wire ourselves. @@ -76,13 +76,14 @@ def main() -> None: # just ask it to transition to an 'm1wire' port at the end of the final VCC segment. # Instead, we have to calculate the via size ourselves, and adjust the final position # to account for it. - via_size = abs( - library['v1_via'].ports['top'].offset[0] - - library['v1_via'].ports['bottom'].offset[0] - ) + v1pat = library['v1_via'] + via_size = abs(v1pat.ports['top'].x - v1pat.ports['bottom'].x) + # alternatively, via_size = v1pat.ports['top'].measure_travel(v1pat.ports['bottom'])[0][0] + # would take into account the port orientations if we didn't already know they're along x rpather.path_to('VCC', None, -50_000 + via_size) rpather.plug('v1_via', {'VCC': 'top'}) + # Render the path we defined rpather.render() library['RenderPather_and_PathTool'] = rpather.pattern