Welcome to phidl’s documentation!

Documentation for PHIDL, an open-source Python GDS (GDSII) layout and CAD geometry creation tool.

New user? Start here:

Tutorials

Tutorials for PHIDL

Quick start

PHIDL allows you to create complex designs from simple shapes, and can output the result as GDSII files. The basic element of PHIDL is the Device, which is just a GDS cell with some additional functionality (for those unfamiliar with GDS designs, it can be thought of as a blank area to which you can add polygon shapes). The polygon shapes can also have Ports on them–these allow you to snap shapes together like Lego blocks. You can either hand-design your own polygon shapes, or there is a large library of pre-existing shapes you can use as well.

Brief introduction

This first section is an extremely short tutorial meant to give you an idea of what PHIDL can do. For a more detailed tutorial, please read the following “The basics of PHIDL” section and the other tutorials.

We’ll start with some boilerplate imports:

[1]:
from phidl import Device
from phidl import quickplot as qp # Rename "quickplot()" to the easier "qp()"
import phidl.geometry as pg

Then let’s create a blank Device (essentially an empty GDS cell with some special features)

[2]:
D = Device('mydevice')

Next let’s add a custom polygon using lists of x points and y points. You can also add polygons pair-wise like [(x1,y1), (x2,y2), (x3,y3), ... ]. We’ll also image the shape using the handy quickplot() function (imported here as qp())

[3]:
xpts = (0,10,10, 0)
ypts = (0, 0, 5, 3)
poly1 = D.add_polygon( [xpts, ypts], layer = 0)

qp(D) # quickplot it!
_images/tutorials_quickstart_7_0.png

You can also create new geometry using the built-in geometry library:

[4]:
T = pg.text('Hello!', layer = 1)
A = pg.arc(radius = 25, width = 5, theta = 90, layer = 3)

qp(T) # quickplot it!
qp(A) # quickplot it!
_images/tutorials_quickstart_9_0.png
_images/tutorials_quickstart_9_1.png

We can easily add these new geometries to D, which currently contains our custom polygon. (For more details about references see below, or the tutorial called “Understanding References”.)

[5]:
text1 = D.add_ref(T) # Add the text we created as a reference
arc1 = D.add_ref(A) # Add the arc we created

qp(D) # quickplot it!
_images/tutorials_quickstart_11_0.png

Now that the geometry has been added to D, we can move and rotate everything however we want:

[6]:
text1.movey(5)
text1.movex(-20)
arc1.rotate(-90)
arc1.move([10,22.5])
poly1.ymax = 0

qp(D) # quickplot it!
_images/tutorials_quickstart_13_0.png

We can also connect shapes together using their Ports, allowing us to snap shapes together like Legos. Let’s add another arc and snap it to the end of the first arc:

[7]:
arc2 = D.add_ref(A) # Add a second reference the arc we created earlier
arc2.connect(port = 1, destination = arc1.ports[2])

qp(D) # quickplot it!
_images/tutorials_quickstart_15_0.png

That’s it for the very basics! Keep reading for a more detailed explanation of each of these, or see the other tutorials for topics such as using Groups, creating smooth Paths, and more.

The basics of PHIDL

This is a longer tutorial meant to explain the basics of PHIDL in a little more depth. Further explanation can be found in the other tutorials as well.

PHIDL allows you to create complex designs from simple shapes, and can output the result as GDSII files. The basic element of PHIDL is the Device, which can be thought of as a blank area to which you can add polygon shapes. The polygon shapes can also have Ports on them–these allow you to snap shapes together like Lego blocks. You can either hand-design your own polygon shapes, or there is a large library of pre-existing shapes you can use as well.

Creating a custom shape

Let’s start by trying to make a rectangle shape with ports on either end.

[8]:
import numpy as np
from phidl import quickplot as qp
from phidl import Device
import phidl.geometry as pg


# First we create a blank device `R` (R can be thought of as a blank
# GDS cell with some special features). Note that when we
# make a Device, we usually assign it a variable name with a capital letter
R = Device('rect')

# Next, let's make a list of points representing the points of the rectangle
# for a given width and height
width = 10
height = 3
points =  [(0, 0), (width, 0), (width, height), (0, height)]

# Now we turn these points into a polygon shape using add_polygon()
R.add_polygon(points)

# Let's use the built-in "quickplot" function to display the polygon we put in D
qp(R)
_images/tutorials_quickstart_18_0.png

Next, let’s add Ports to the rectangle which will allow us to connect it to other shapes easily

[9]:
# Ports are defined by their width, midpoint, and the direction (orientation) they're facing
# They also must have a name -- this is usually a string or an integer
R.add_port(name = 'myport1', midpoint = [0,height/2], width = height, orientation = 180)
R.add_port(name = 'myport2', midpoint = [width,height/2], width = height, orientation = 0)

# The ports will show up when we quickplot() our shape
qp(R) # quickplot it!
_images/tutorials_quickstart_20_0.png

We can check to see that our Device has ports in it using the print command:

[10]:
print(R)
Device (name "rect" (uid 4), ports ['myport1', 'myport2'], aliases [], 1 polygons, 0 references)

Looks good!

Library & combining shapes

Since this Device is finished, let’s create a new (blank) Device and add several shapes to it. Specifically, we will add an arc from the built-in geometry library and two copies of our rectangle Device. We’ll then then connect the rectangles to both ends of the arc. The arc() function is contained in the phidl.geometry library which as you can see at the top of this example is imported with the name pg.

This process involves adding “references”. These references allow you to create a Device shape once, then reuse it many times in other Devices.

[11]:
# Create a new blank Device
E = Device('arc_with_rectangles')

# Also create an arc from the built-in "pg" library
A = pg.arc(width = 3)

# Add a "reference" of the arc to our blank Device
arc_ref = E.add_ref(A)

# Also add two references to our rectangle Device
rect_ref1 = E.add_ref(R)
rect_ref2 = E.add_ref(R)

# Move the shapes around a little
rect_ref1.move([-10,0])
rect_ref2.move([-5,10])

qp(E) # quickplot it!
_images/tutorials_quickstart_24_0.png

Now we can see we have added 3 shapes to our Device “E”: two references to our rectangle Device, and one reference to the arc Device. We can also see that all the references have Ports on them, shown as the labels “myport1”, “myport2”, “1” and “2”.

Next, let’s snap everything together like Lego blocks using the connect() command.

[12]:
# First, we recall that when we created the references above we saved
# each one its own variable: arc_ref, rect_ref1, and rect_ref2
# We'll use these variables to control/move the reference shapes.

# First, let's move the arc so that it connects to our first rectangle.
# In this command, we tell the arc reference 2 things: (1) what port
# on the arc we want to connect, and (2) where it should go
arc_ref.connect(port = 1, destination = rect_ref1.ports['myport2'])

qp(E) # quickplot it!
_images/tutorials_quickstart_26_0.png
[13]:
# Then we want to move the second rectangle reference so that
# it connects to port 2 of the arc
rect_ref2.connect('myport1', arc_ref.ports[2])

qp(E) # quickplot it!
_images/tutorials_quickstart_27_0.png

Looks great!

Going a level higher

Now we’ve made a (somewhat) complicated bend-shape from a few simple shapes. But say we’re not done yet – we actually want to combine together 3 of these bend-shapes to make an even-more complicated shape. We could recreate the geometry 3 times and manually connect all the pieces, but since we already put it together once it will be smarter to just reuse it multiple times.

We will start by abstracting this bend-shape. As shown in the quickplot, there are ports associated with each reference in our bend-shape Device E: “myport1”, “myport2”, “1”, and “2”. But when working with this bend-shape, all we really care about is the 2 ports at either end – “myport1” from rect_ref1 and “myport2” from rect_ref2. It would be simpler if we didn’t have to keep track of all of the other ports.

First, let’s look at something: let’s see if our bend-shape Device E has any ports in it:

[14]:
print(E)
Device (name "arc_with_rectangles" (uid 5), ports [], aliases [], 0 polygons, 3 references)

It has no ports apparently! Why is that, when we clearly see ports in the quickplots above?

The answer is that Device E itself doesn’t have ports – the references inside E do have ports, but we never actually added ports to E. Let’s fix that now, adding a port at either end, setting the names to the integers 1 and 2.

[15]:
# Rather than specifying the midpoint/width/orientation, we can instead
# copy ports directly from the references since they're already in the right place
E.add_port(name = 1, port = rect_ref1.ports['myport1'])
E.add_port(name = 2, port = rect_ref2.ports['myport2'])

qp(E) # quickplot it!
_images/tutorials_quickstart_31_0.png

If we look at the quickplot above, we can see that there are now red-colored ports on both ends. Ports that are colored red are owned by the Device, ports that are colored blue-green are owned by objects inside the Device. This is good! Now if we want to use this bend-shape, we can interact with its ports named 1 and 2.

Let’s go ahead and try to string 3 of these bend-shapes together:

[16]:
# Create a blank Device
D = Device('triple-bend')

# Add 3 references to our bend-shape Device `E`:
bend_ref1 = D.add_ref(E)  # Using the function add_ref()
bend_ref2 = D << E        # Using the << operator which is identical to add_ref()
bend_ref3 = D << E

# Let's mirror one of them so it turns right instead of left
bend_ref2.mirror()

# Connect each one in a series
bend_ref2.connect(1, bend_ref1.ports[2])
bend_ref3.connect(1, bend_ref2.ports[2])

# Add ports so we can use this shape at an even higher-level
D.add_port(name = 1, port = bend_ref1.ports[1])
D.add_port(name = 2, port = bend_ref3.ports[2])

qp(D) # quickplot it!
_images/tutorials_quickstart_33_0.png
Saving as a GDSII file

Saving the design as a GDS file is simple – just specify the Device you’d like to save and run the write_gds() function:

[17]:
D.write_gds('triple-bend.gds')
[17]:
'triple-bend.gds'

Some useful notes about writing GDS files:

  • The default unit is 1e-6 (micrometers aka microns), with a precision of 1e-9 (nanometer resolution)
  • PHIDL will automatically handle naming of all the GDS cells to avoid name-collisions.
  • Unless otherwise specified, the top-level GDS cell will be named “toplevel”

All of these parameters can be modified using the appropriate arguments of write_gds():

[18]:
D.write_gds(filename = 'triple-bend.gds', # Output GDS file name
            unit = 1e-6,                  # Base unit (1e-6 = microns)
            precision = 1e-9,             # Precision / resolution (1e-9 = nanometers)
            auto_rename = True,           # Automatically rename cells to avoid collisions
            max_cellname_length = 28,     # Max length of cell names
            cellname = 'toplevel'         # Name of output top-level cell
           )
[18]:
'triple-bend.gds'

Moving, rotating, mirroring

There are several actions we can take to move and rotate PHIDL objects. These actions include movement, rotation, and reflection. There are several types of PHIDL objects (Device, DeviceReference, Port, Polygon, CellArray, Label, and Group) but they all can be moved and manipulated in the same ways.

Basic movement and rotation

We’ll start by creating a blank Device and some shapes. We’ll add the shapes to the Device as references

[1]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

# Start with a blank Device
D = Device()

# Create some more shape Devices
T = pg.text('hello', size = 10, layer = 1)
E = pg.ellipse(radii = (10,5))
R = pg.rectangle(size = (10,3), layer = 2)

# Add the shapes to D as references
text = D << T
ellipse = D << E
rect1 = D << R
rect2 = D << R

qp(D) # quickplot it!
_images/tutorials_movement_2_0.png

Now let’s practice moving and rotating the objects:

[2]:
text.move([10,4]) # Translate by dx = 10, dy = 4
ellipse.move(origin = [1,1], destination = [2,2.5]) # Translate by dx = 1, dy = 1.5
rect1.move([1,1], [5,5], axis = 'y') # Translate by dx = 0, dy = 4 (motion only along y-axis)
rect2.movey(4) # Same as specifying axis='y' in the move() command
rect2.movex(4) # Same as specifying axis='x'' in the move() command
ellipse.movex(30,40) # Moves "from" x=30 "to" x=40 (i.e. translates by dx = 10)

rect1.rotate(45) # Rotate the first waveguide by 45 degrees around (0,0)
rect2.rotate(-30, center = [1,1]) # Rotate the second waveguide by -30 degrees around (1,1)

text.mirror(p1 = [1,1], p2 = [1,3]) # Reflects across the line formed by p1 and p2

qp(D) # quickplot it!
_images/tutorials_movement_4_0.png

Working with properties

Each Device and DeviceReference object has several properties which can be used to learn information about the object (for instance where it’s center coordinate is). Several of these properties can actually be used to move the geometry by assigning them new values.

Available properties are:

  • xmin / xmax: minimum and maximum x-values of all points within the object
  • ymin / ymax: minimum and maximum y-values of all points within the object
  • x: centerpoint between minimum and maximum x-values of all points within the object
  • y: centerpoint between minimum and maximum y-values of all points within the object
  • bbox: bounding box (see note below) in format ((xmin,ymin),(xmax,ymax))
  • center: center of bounding box
[3]:
print('bounding box:')
print(text.bbox) # Will print the bounding box of text in terms of [(xmin, ymin), (xmax, ymax)]
print('xsize and ysize:')
print(text.xsize) # Will print the width of text in the x dimension
print(text.ysize) # Will print the height of text in the y dimension
print('center:')
print(text.center) # Gives you the center coordinate of its bounding box
print('xmax')
print(ellipse.xmax) # Gives you the rightmost (+x) edge of the ellipse bounding box
bounding box:
[[-42.   4.]
 [ -9.  15.]]
xsize and ysize:
33.0
11.0
center:
[-25.5   9.5]
xmax
21.0

Let’s use these properties to manipulate our shapes to arrange them a little better

[4]:
# First let's center the ellipse
ellipse.center = [0,0] # Move the ellipse such that the bounding box center is at (0,0)

# Next, let's move the text to the left edge of the ellipse
text.y = ellipse.y # Move the text so that its y-center is equal to the y-center of the ellipse
text.xmax = ellipse.xmin # Moves the ellipse so its xmax == the ellipse's xmin

# Align the right edge of the rectangles with the x=0 axis
rect1.xmax = 0
rect2.xmax = 0

# Move the rectangles above and below the ellipse
rect1.ymin = ellipse.ymax + 5
rect2.ymax = ellipse.ymin - 5

qp(D)
_images/tutorials_movement_8_0.png

In addition to working with the properties of the references inside the Device, we can also manipulate the whole Device if we want. Let’s try mirroring the whole Device D:

[5]:
print(D.xmax) # Prints out '10.0'

D.mirror((0,1)) # Mirror across line made by (0,0) and (0,1)

qp(D)
10.0
_images/tutorials_movement_10_1.png
A note about bounding boxes

When we talk about bounding boxes, we mean it in the sense of the smallest enclosing box which contains all points of the geometry. So the bounding box for the device D looks like this:

[6]:
# The phidl.geometry library has a handy bounding-box function
# which takes a bounding box and creates a rectangle shape for it
device_bbox = D.bbox
D << pg.bbox(device_bbox, layer = 3)

qp(D)
_images/tutorials_movement_12_0.png

When we query the properties of D, they will be calculated with respect to this bounding-rectangle. For instance:

[7]:
print('Center of Device D:')
print(D.center)

print('X-max of Device D:')
print(D.xmax)
Center of Device D:
[16.5         0.79715597]
X-max of Device D:
43.0

Chaining commands

Many of the movement/manipulation functions return the object they manipulate. We can use this to chain multiple commands in a single line.

For instance these two expressions:

rect1.rotate(angle = 37)
rect1.move([10,20])

…are equivalent to this single-line expression

rect1.rotate(angle = 37).move([10,20])

Understanding references

In this tutorial we’ll investigate what “references” are. The GDSII specification allows the use of references, and similarly PHIDL uses them (with the add_ref() function). So what is a reference? Simply put: A reference does not contain any geometry. It only *points* to an existing geometry. That still might not be clear, so let’s use an example:

Say Alice has a ridiculously large polygon with 100 billion vertices in it that we’ll call BigPolygon. It’s huge, but she needs to use it in her design 250 times. Well, a single copy of BigPolygon takes up 100GB of memory just by itself, so she doesn’t want to make 250 copies of it. Since Alice is clever, she instead references the polygon 250 times. Each reference only uses a few bytes of memory – it only needs to know the memory address of BigPolygon and a few other things. In this way, she can keep one copy of BigPolygon and use it again and again wherever she needs to.

If that’s still not clear, let’s use the following examples.

Using references

Let’s start by making a blank geometry (Device) then adding a single polygon to it.

[2]:
import numpy as np
import phidl.geometry as pg
from phidl import Device
from phidl import quickplot as qp


# Create a blank Device
P = Device()

# Add a polygon
xpts = [0,0,5,6,9,12]
ypts = [0,1,1,2,2,0]
P.add_polygon([xpts,ypts])

# Quickplot the Device with the polygon in it
qp(P)
_images/tutorials_references_2_0.png

Now, pretend we’re in the same position as Alice: We want to reuse this polygon repeatedly but do not want to make multiple copies of it. To do so, we need to make a second blank Device, this time called D. In this new Device we’ll reference our Device P which contains our polygon.

[3]:
D = Device()             # Create a new blank Device
poly_ref = D.add_ref(P)  # Reference the Device "P" that has the polygon in it

qp(D) # Quickplot the reference-containing device "D"
_images/tutorials_references_4_0.png

OK, well that seemed to work, but it also seems thoroughly useless! It looks like we just made a copy of our polygon – but remember, we’ve didn’t actually make a second polygon, we just made a reference (aka pointer) to the original polygon. Let’s continue with the example by adding two more references to D:

[4]:
poly_ref2 = D.add_ref(P)  # Reference the Device "P" that has the polygon in it
poly_ref3 = D.add_ref(P)  # Reference the Device "P" that has the polygon in it

qp(D) # Quickplot the reference-containing device "D"
_images/tutorials_references_6_0.png

Now we have 3x polygons all on top of each other. Again, this would appear useless, except that we can manipulate each reference indepedently. Notice that when we called D.add_ref(P) above, we saved the result to a new variable each time (poly_ref, poly_ref2, and poly_ref3)? We can use those variables to reposition the references. Let’s try:

[5]:
poly_ref2.rotate(15) # Rotate the 2nd reference we made 15 degrees
poly_ref3.rotate(30) # Rotate the 3rd reference we made 30 degrees

qp(D) # Quickplot the reference-containing device "D"
_images/tutorials_references_8_0.png

Now we’re getting somewhere! We’ve only had to make the polygon once, but we’re able to reuse it as many times as we want.

Modifying the referenced geometry

Now, a question naturally follows this: What happens when you change the original geometry that the reference points to? In our case, our references in D all point to the Device P that with the original polygon. Let’s try adding a second polygon to P.

First we add the second polygon and make sure P looks like we expect:

[6]:
# Add a 2nd polygon to "P"
xpts = [14,14,16,16]
ypts = [0,2,2,0]
P.add_polygon([xpts,ypts], layer = 1)

qp(P) # Quickplot the "P" with its 2 polygons
_images/tutorials_references_10_0.png

That looks good. Now let’s find out what happened to D that contains the three references. Keep in mind that we have not modified D or executed any functions/operations on D – all we have done is modify P.

[7]:
qp(D)  # Quickplot the reference-containing device "D"
_images/tutorials_references_12_0.png

We have our answer! When we modify the original geometry, all of the references automatically reflect the modifications. This is very powerful, because we can use this to make very complicated designs from relatively simple elements in a computation- and memory-efficienct way.

Let’s try making references a level deeper by referencing D. Note here we use the << operator to add the references – this is just shorthand, and is exactly equivalent to using add_ref()

[8]:
X = Device()             # Create a new blank Device
d_ref1 = X.add_ref(D)  # Reference the Device "D" that 3 references in it
d_ref2 = X << D        # Use the "<<" operator to create a 2nd reference to D
d_ref3 = X << D        # Use the "<<" operator to create a 2nd reference to D

d_ref1.move([20,0])
d_ref2.move([40,0])

qp(X) # Quickplot the reference-containing device "D"
_images/tutorials_references_14_0.png

Arrays of references

Sometimes it’s convenient to make an array or grid of the same geometry. For that purpose, the GDSII spec allows you to define arrays of references. In PHIDL, these are added with the add_array() function. Note that by GDSII definition these arrays are on a fixed grid – PHIDL does however have more flexible arrangement options if desired, see for example grid() and packer().

Let’s make a new device and put a big array of our Device D in it:

[9]:
A = Device()           # Create a new blank Device
d_ref1 = A.add_array(D, columns = 6, rows = 3, spacing = [20,15])  # Reference the Device "D" that 3 references in it

qp(A) # Quickplot the reference-containing device "D"
_images/tutorials_references_16_0.png

Layers

Layers are an important component of GDSII design. Layers in a GDS file are defined using two integers (0-255): a layer and a datatype. In PHIDL, the layer can be specified…

  1. as a single number 0-255 representing the gds layer number (gds datatype will be set to 0)
  2. as a 2-element list [0,1] or tuple (0,1) representing the (layer, datatype)
  3. as a Layer / LayerSet object

To begin, let’s make a new blank device D and add some text-polygons to it using different methods of layer entry:

[2]:
import phidl.geometry as pg
from phidl import Device, Layer, LayerSet
from phidl import quickplot as qp


D = Device()

# Specify layer with a single integer 0-255 (gds datatype will be set to 0)
layer1 = 1

# Specify layer as 1, equivalent to layer = 2, datatype = 6
layer2 = (2,6)

# Specify layer as 2, equivalent to layer = 2, datatype = 0
layer3 = Layer(gds_layer = 3, gds_datatype = 5, color = 'gold')

D << pg.text('Layer (1,0)', layer = layer1)
D << pg.text('Layer (2,6)', layer = layer2).movey(-20)
D << pg.text('Layer (3,5)', layer = layer3).movey(-40)

qp(D)
_images/tutorials_layers_2_0.png

Multiple layers

Say we want to create the same ellipse on several different layers. We can do that by using a Python set of layers, which is created using the curly brackets {} (similar to a dict but without colons). So if we want to add it to four layers, say: - layer 1 / datatype 0 - layer 3 / datatype 5 - layer 3 / datatype 6 - layer 7 / datatype 1

[3]:
# Note each element of the set must be a valid layer input by itself
my_layers = {1, (3,5), (3,6), (7,8)}
D = pg.ellipse(layer = my_layers)

Note that although we specified four different layers, it did not produce four separate ellipse Devices–instead, it produced one Device with all four ellipse polygons inside that single Device.

LayerSet

PHIDL also has a convenience container for layers called a LayerSet, which lets you conveniently call each Layer object just by its name. You can also specify the layer color using an RGB triplet e.g (0.1, 0.4, 0.2), an HTML hex color (e.g. #a31df4), or a CSS3 color name (e.g. 'gold' or 'lightblue' see http://www.w3schools.com/colors/colors_names.asp ) The 'alpha' argument also lets you specify how transparent that layer should look when using quickplot() (has no effect on the written GDS file).

We construct a layerset by starting with a blank one, and then using the add_layer() function:

[4]:
ls = LayerSet() # Create a blank LayerSet

ls.add_layer(name = 'au', gds_layer = 4, gds_datatype = 0,
             description = 'Gold wiring', color = 'goldenrod')
ls.add_layer(name = 'nb', gds_layer = 5, gds_datatype = 0,
             description = 'Niobium liftoff', color = (0.4,0.5,0.7))
ls.add_layer('nb_etch', 6, 0, color = 'lightblue', alpha = 0.2)
ls.add_layer('silicon', 8, 2, color = 'green', alpha = 0.4)

Now that our layers are defined, we can call them from the LayerSet in the same way we would from a dictionary, where the name becomes the key:

[5]:
D = Device()

gold_layer = ls['au']

D.add_ref( pg.text('Gold layer', size = 10, layer = ls['au']) ).movey(0)
D.add_ref( pg.text('Niobium layer', size = 10, layer = ls['nb']) ).movey(-20)
D.add_ref( pg.text('Nb Etch layer', size = 10, layer = ls['nb_etch']) ).movey(-40)
D.add_ref( pg.text('Si layer', size = 10, layer = ls['silicon']) ).movey(-60)

qp(D)
_images/tutorials_layers_9_0.png

We can additionally use a LayerSet to add the same structure to several layers at once by passing the whole layerset to the layer argument. Note that since they all overlap, it will look like a single object (but rest assured there are multiple layers)

[6]:
T = pg.text('All layers', layer = ls)
qp(T)
_images/tutorials_layers_11_0.png

If we want to examine any single layer, we can call them by their names, for example

[7]:
print(ls['nb'])
Layer (name nb, GDS layer 5, GDS datatype 0, description Niobium liftoff, color #6680b2)

We can quickly preview our color scheme using the pg.preview_layerset() function as well.

[8]:
D = pg.preview_layerset(ls)
qp(D)
_images/tutorials_layers_15_0.png
Making KLayout .lyp files

We can even save the LayerSet as a KLayout .lyp file (“layer properties” file), useful for getting the color scheme in KLayout to match quickplot

[9]:
import phidl.utilities as pu
pu.write_lyp('my_layer_properties_file.lyp', layerset = ls)
Ignoring layers with None

In some cases, you may want to not use one of the layers for a given geometry. PHIDL makes that easy by letting you pass None to any function that accepts layers. For instance, say we have the following structure with three distinct layers

[10]:
def many_ellipses(layer1 = 1, layer2 = 2, layer3 = 3):
    D = Device()
    D << pg.ellipse(layer = layer1)
    D << pg.ellipse(layer = layer2).movex(15)
    D << pg.ellipse(layer = layer3).movex(30)

    return D

qp(many_ellipses(layer1 = 1, layer2 = 2, layer3 = 3))
_images/tutorials_layers_20_0.png

Now say we want to remove the middle layer entirely. We note that it’s defined by the layer layer2, so we can set that to None and it won’t be added at all:

[11]:
qp(many_ellipses(layer1 = 1, layer2 = None, layer3 = 3))
_images/tutorials_layers_22_0.png

Removing layers

We can remove the unwanted layers using the remove_layers() function. Let’s again start with our pg.preview_layerset() Device above:

[12]:
D = pg.preview_layerset(ls)
qp(D)
_images/tutorials_layers_24_0.png

Now let’s remove layers 6 and 8. This should leave us with just the gold (layer 4,0) and nb (5,0) layers.

[13]:
D.remove_layers(layers = [6,8])
qp(D)
_images/tutorials_layers_26_0.png

Whoops! Note that because we specified just an 8 as the layer we wanted to remove, PHIDL interpreted it as (8,0), so it didn’t remove the the silicon layer that is actually on (8,2). Let’s try again:

[14]:
D.remove_layers(layers = [(8,2)])
qp(D)
_images/tutorials_layers_28_0.png

That’s better.

If we later decide that we actually only want layer 4, we can do that too. To do so, we set “invert_selection” to True so that all layers except 4 are removed:

[15]:
D.remove_layers(layers = [4], invert_selection = True)
qp(D)
_images/tutorials_layers_30_0.png

Remapping (moving) layers

We can use the remap_layers() function to map layers arbitrarily. We’ll start again with our layerset_preview device:

[16]:
D = pg.preview_layerset(ls)
qp(D)
_images/tutorials_layers_32_0.png

Now say that we wanted to move shapes on the silicon layer (8,2) to layer 99, and nb_etch layer (6,0) to layer 77 but leave the other layers alone. We do this by construction a python dictionary and passing to remap_layers()

[17]:
D.remap_layers(layermap = {(8,2): 99,
                             6  : 77,
                           })
qp(D)
_images/tutorials_layers_34_0.png

Very good! We can see from the colors of the bottom polygons changing that the layer remapping worked.

Extracting / copying layers

Extracting layers

Say you want to access or copy all the polygons of specific layer(s) from an existing Device. You can do this using the pg.extract() function, which will copy layers out of one Device and put them into a new Device.

Let’s start with a Device with a few objects in it:

[24]:
D = Device()

D << pg.ellipse(layer = 1)
D << pg.rectangle(size = (10,10), layer = 2).movex(15)
D << pg.arc(width = 10, layer = 3).movex(25)
D << pg.circle(radius = 5, layer = 2).movex(50)

qp(D)
_images/tutorials_layers_37_0.png

Next, let’s use pg.extract() to create a new Device which only has the objects on layers 1 and 2:

[25]:
D_only_layers_1_and_2 = pg.extract(D, layers = [1,2])
qp(D_only_layers_1_and_2)
_images/tutorials_layers_39_0.png

Note that the Device created from this function is flattened

Copying layers to a new layer

Sometimes you want to copy a layer out of a Device AND remap that layer to a new layer in a single command. You can do that with the copy_layer() function. Let’s try this out by taking the above geometry:

[26]:
qp(D)
_images/tutorials_layers_42_0.png

…and let’s copy the arc (yellow / layer 3) arc to a new layer (blue / layer 1)

[31]:
D_copied = pg.copy_layer(D, layer = 3, new_layer = 1)
qp(D_copied)
_images/tutorials_layers_44_0.png

Grouping objects

The Group class is used to group items together. This allows you to then manipulate the whole Group as a single object. This is similar to the “selection” and “group” functionality in Inkscape/Adobe Illustrator, where you can highlight several objects (by e.g. shift-clicking) and move them together.

You can manipulate a Group like any other phidl object, such as:

  • Direct manipulation: move(), rotate(), and mirror()
  • Arrange with align() and distribute()
  • and the usual list of attributes (xmin, ymax, center, bbox, etc)

Creating and manipulating a Group

[1]:
import numpy as np
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

from phidl import Group

# Create a blank Device and add number shapes to it
D = Device()
t1 = D << pg.text('1')
t2 = D << pg.text('2')
t3 = D << pg.text('3')
t4 = D << pg.text('4')
t5 = D << pg.text('5')
t6 = D << pg.text('6')

# Spread out devices
D.distribute(direction = 'x', spacing = 3)

qp(D) # quickplot it!

_images/tutorials_group_2_0.png

Say we want to only move the even numbers. We can create a group of those numbers and move them them up in the Y direction a few units easily:

[2]:
even = Group([t2,t4,t6])
even.movey(5)

qp(D) # quickplot it!
_images/tutorials_group_4_0.png

Now let’s make a Group out of the odd numbers so we can rotate them by 90 degrees. We’re going to do make this Group in a slightly different way – simply by using the + operation.

[3]:
odd = t1 + t3 + t5 # Create the Group
odd.rotate(-90)    # Rotate the Group

qp(D) # quickplot it!
_images/tutorials_group_6_0.png

Any PHIDL object can be summed in this way to create a Group – this includes Device, DeviceReference, Port, Polygon, CellArray, and Label. Groups themselves can also be summed.

We can even add items to groups if we need to:

[4]:
one_to_five = t1 + t2    # Create the group
one_to_five.add([t3,t4]) # Add more elements with the "add" method
one_to_five += t5        # Equivalently, add more elements with the '+=' operator

We can also perform the usual manipulations of PHIDL objects like asking where the center is, xmin, ymax, etc. Here we move the entire one_to_five group (which now has all numbers except for 6) down so the top is aligned with the y==0 axis

[5]:
print(one_to_five.ymax)
one_to_five.ymax = 0

qp(D) # quickplot it!
15.0
_images/tutorials_group_10_1.png

Align and distribute in Groups

Lastly, we can also align() and distribute() a Group if we want to. Let’s start with some misaligned objects:

[6]:
# Create a blank Device and add number shapes to it
D = Device()
t1 = D << pg.text('1').move([-5,-5])
t2 = D << pg.text('2').move([10,-15])
t3 = D << pg.text('3').move([20, 5])
t4 = D << pg.text('4').move([30, -7])
t5 = D << pg.text('5').move([50, -13])
t6 = D << pg.text('6').move([60,6])

qp(D) # quickplot it!
_images/tutorials_group_12_0.png

By forming a Group out of all the objects and using the align() command, we can easily align them:

[7]:
all_numbers = (t1+t2+t3+t4+t5+t6)     # Make a Group of all the numbers
all_numbers.align(alignment = 'ymax') # Align the ymax of all the numbers

qp(D) # quickplot it!
_images/tutorials_group_14_0.png

It’s aligned now, but still not evenly distributed. Let’s fix that by using the distribute() command:

[8]:
all_numbers.distribute(direction = 'x', spacing = 1.2, separation = True)

qp(D) # quickplot it!
_images/tutorials_group_16_0.png

We can even change the order of distribution – it’s determined by the order the objects are added to the Group. If we want to reverse the order of the numbers, we simply need to make a new Group with the correct order:

[9]:
all_numbers_reverse = (t6 + t5 + t4 + t3 + t2 + t1) # Start with t6, then t5, ...
all_numbers_reverse.distribute(direction = 'x', spacing = 1.2, separation = True)

qp(D) # quickplot it!
_images/tutorials_group_18_0.png

Paths and waveguides

PHIDL includes an extremely efficient module for creating curves, particularly useful for creating waveguide structures such as those used in photonics. Creating a path device is simple:

  • Create a blank Path
  • Append points to the Path either using the built-in functions (arc(), straight(), euler(), etc) or by providing your own lists of points
  • Specify what you want the cross-section (CrossSection) to look like
  • Combine the Path and the CrossSection (will output a Device with the path polygons in it)

Path creation

The first step is to generate the list of points we want the path to follow. Let’s start out by creating a blank Path and using the built-in functions to make a few smooth turns.

[2]:
from phidl import Path, CrossSection, Device
import phidl.path as pp
import numpy as np

P = Path()
P.append( pp.arc(radius = 10, angle = 90) )   # Circular arc
P.append( pp.straight(length = 10) )          # Straight section
P.append( pp.euler(radius = 3, angle = -90) ) # Euler bend (aka "racetrack" curve)
P.append( pp.straight(length = 40) )
P.append( pp.arc(radius = 8, angle = -45) )
P.append( pp.straight(length = 10) )
P.append( pp.arc(radius = 8, angle = 45) )
P.append( pp.straight(length = 10) )

from phidl import quickplot as qp
qp(P)
_images/tutorials_waveguides_4_0.png

We can also modify our Path in the same ways as any other PHIDL object:

  • Manipulation with move(), rotate(), mirror(), etc
  • Accessing properties like xmin, y, center, bbox, etc
[3]:
P.movey(10)
P.xmin = 20
qp(P)
_images/tutorials_waveguides_6_0.png

We can also check the length of the curve with the length() method:

[4]:
P.length()
[4]:
107.69901058617913

Defining the cross-section

Now that we’ve got our path defined, the next step is to tell phidl what we want the cross-section of the path to look like and then extrude() it. There’s a few different ways of doing this.

Option 1: Fixed width cross-section

The simplest option is to just set the cross-section to be a constant width by passing a number to extrude() like so:

[5]:
waveguide_device = P.extrude(1.5, layer = 3)
qp(waveguide_device)
_images/tutorials_waveguides_10_0.png
Option 2: Linearly-varying width

A slightly more advanced version is to make the cross-section width vary linearly from start to finish by passing a 2-element list to extrude() like so:

[6]:
waveguide_device = P.extrude([0.7,3.7], layer = 4)
qp(waveguide_device)
_images/tutorials_waveguides_12_0.png
Option 3: Complex CrossSection objects

If we want a more complex cross-section we can create CrossSection object and add any number of cross-sectional elements to it. We can then combine the Path and the CrossSection using the extrude() function to generate our final geometry.

For instance, in some photonic applications it’s helpful to have a shallow etch that appears on either side of the waveguide (often called a “sleeve”). Additionally, it might be nice to have a Port on either end of the center section so we can snap other geometries to it:

[7]:
# Create a blank CrossSection
X = CrossSection()

# Add a a few "sections" to the cross-section
X.add(width = 1, offset = 0, layer = 0, ports = ('in','out'))
X.add(width = 3, offset = 2, layer = 2)
X.add(width = 3, offset = -2, layer = 2)

# Combine the Path and the CrossSection
waveguide_device = P.extrude(X)

# Quickplot the resulting Device
qp(waveguide_device)
_images/tutorials_waveguides_14_0.png

Assembling Paths quickly

You can pass append() lists of path segments. This makes it easy to combine paths very quickly. Below we show 3 examples using this functionality:

Example 1: Assemble a complex path by making a list of Paths and passing it to append()

[8]:
P = Path()

# Create the basic Path components
left_turn = pp.euler(radius = 4, angle = 90)
right_turn = pp.euler(radius = 4, angle = -90)
straight = pp.straight(length = 10)

# Assemble a complex path by making list of Paths and passing it to `append()`
P.append([
    straight,
    left_turn,
    straight,
    right_turn,
    straight,
    straight,
    right_turn,
    left_turn,
    straight,
])

qp(P)
_images/tutorials_waveguides_16_0.png

Example 2: Create an “S-turn” just by making a list of [left_turn, right_turn]

[9]:
P = Path()

# Create an "S-turn" just by making a list
s_turn = [left_turn, right_turn]

P.append(s_turn)

qp(P)
_images/tutorials_waveguides_18_0.png

Example 3: Repeat the S-turn 3 times by nesting our S-turn list in another list

[10]:
P = Path()

# Create an "S-turn" using a list
s_turn = [left_turn, right_turn]
# Repeat the S-turn 3 times by nesting our S-turn list 3x times in another list
triple_s_turn = [s_turn, s_turn, s_turn]

P.append(triple_s_turn)

qp(P)
_images/tutorials_waveguides_20_0.png

Note you can also use the Path() constructor to immediately contruct your Path:

[11]:
P = Path([straight, left_turn, straight, right_turn, straight])
qp(P)
_images/tutorials_waveguides_22_0.png

Waypoint-based smooth paths

We can also build smooth paths between waypoints using the smooth() function. Let’s say we wanted to route our path along the following points:

[12]:
points = np.array([(20,10), (40,10), (20,40), (50,40), (50,20), (70,20)])
plt.plot(points[:,0], points[:,1], '.-')
plt.axis('equal');
_images/tutorials_waveguides_24_0.png

We can use smooth() to generate a smoothed Path from these waypoints. You can specify the corner-rounding function with the corner_fun argument (typically using either euler() or arc()), and provide any additional parameters to the corner-rounding function. Note here we provide the the additional argument use_eff = False (which gets passed to pp.euler so that the minimum radius of curvature is 2).

[13]:
points = np.array([(20,10), (40,10), (20,40), (50,40), (50,20), (70,20)])

P = pp.smooth(
    points = points,
    radius = 2,
    corner_fun = pp.euler, # Alternatively, use pp.arc
    use_eff = False,
    )
qp(P)
_images/tutorials_waveguides_26_0.png

Sharp/angular paths

It’s also possible to make more traditional angular paths (e.g. electrical wires) in a few different ways.

Example 1: Using a simple list of points

[14]:
P = Path([(20,10), (30,10), (40,30), (50,30), (50,20), (70,20)])

qp(P)
_images/tutorials_waveguides_28_0.png

Example 2: Using the “turn and move” method, where you manipulate the end angle of the Path so that when you append points to it, they’re in the correct direction. Note: It is crucial that the number of points per straight section is set to 2 (``pp.straight(length, num_pts = 2)``) otherwise the extrusion algorithm will show defects.

[15]:
P = Path()
P.append( pp.straight(length = 10, num_pts = 2) )
P.end_angle += 90                    # "Turn" 90 deg (left)
P.append( pp.straight(length = 10, num_pts = 2) ) # "Walk" length of 10
P.end_angle += -135                  # "Turn" -135 degrees (right)
P.append( pp.straight(length = 15, num_pts = 2) ) # "Walk" length of 10
P.end_angle = 0                      # Force the direction to be 0 degrees
P.append( pp.straight(length = 10, num_pts = 2) ) # "Walk" length of 10
qp(P)
_images/tutorials_waveguides_30_0.png

As usual, these paths can be used with a CrossSection to create nice extrusions:

[16]:
X = CrossSection()

X.add(width = 1, offset = 0, layer = 3)
X.add(width = 1.5, offset = 2.5, layer = 4)
X.add(width = 1.5, offset = -2.5, layer = 7)
wiring_device = P.extrude(X)

qp(wiring_device)
_images/tutorials_waveguides_32_0.png

Custom curves

Now let’s have some fun and try to make a loop-de-loop structure with parallel waveguides and several Ports.

To create a new type of curve we simply make a function that produces an array of points. The best way to do that is to create a function which allows you to specify a large number of points along that curve – in the case below, the looploop() function outputs 1000 points along a looping path. Later, if we want reduce the number of points in our geometry we can trivially simplify the path.

[17]:
def looploop(num_pts = 1000):
    """ Simple limacon looping curve """
    t = np.linspace(-np.pi,0,num_pts)
    r = 20 + 25*np.sin(t)
    x = r*np.cos(t)
    y = r*np.sin(t)
    points = np.array((x,y)).T
    return points

# Create the path points
P = Path()
P.append( pp.arc(radius = 10, angle = 90) )
P.append( pp.straight())
P.append( pp.arc(radius = 5, angle = -90) )
P.append( looploop(num_pts = 1000) )
P.rotate(-45)

# Create the crosssection
X = CrossSection()
X.add(width = 0.5, offset = 2, layer = 0, ports = [None,None])
X.add(width = 0.5, offset = 4, layer = 1, ports = [None,'out2'])
X.add(width = 1.5, offset = 0, layer = 2, ports = ['in','out'])
X.add(width = 1, offset = 0, layer = 3)

D = P.extrude(X)
qp(D) # quickplot the resulting Device
_images/tutorials_waveguides_34_0.png

You can create Paths from any array of points! If we examine our path P we can see that all we’ve simply created a long list of points:

[18]:
import numpy as np
path_points = P.points       # Curve points are stored as a numpy array in P.points
print(np.shape(path_points)) # The shape of the array is Nx2
print(len(P))                # Equivalently, use len(P) to see how many points are inside
(1457, 2)
1457

Simplifying / reducing point usage

One of the chief concerns of generating smooth curves is that too many points are generated, inflating file sizes and making boolean operations computationally expensive. Fortunately, PHIDL has a fast implementation of the Ramer-Douglas–Peucker algorithm that lets you reduce the number of points in a curve without changing its shape. All that needs to be done is when you extrude() the device, you specify the simplify argument.

If we specify simplify = 1e-3, the number of points in the line drops from 12,000 to 4,000, and the remaining points form a line that is identical to within 1e-3 distance from the original (for the default 1 micron unit size, this corresponds to 1 nanometer resolution):

[19]:
# The remaining points form a identical line to within `1e-3` from the original
D = P.extrude(X, simplify = 1e-3)
qp(D) # quickplot the resulting Device
_images/tutorials_waveguides_38_0.png

Let’s say we need fewer points. We can increase the simplify tolerance by specifying simplify = 1e-1. This drops the number of points to ~400 points form a line that is identical to within 1e-1 distance from the original:

[20]:
D = P.extrude(X, simplify = 1e-1)
qp(D) # quickplot the resulting Device
_images/tutorials_waveguides_40_0.png

Taken to absurdity, what happens if we set simplify = 0.3? Once again, the ~200 remaining points form a line that is within 0.3 units from the original – but that line looks pretty bad.

[21]:
D = P.extrude(X, simplify = 0.3)
qp(D) # quickplot the resulting Device
_images/tutorials_waveguides_42_0.png

Curvature calculation

The Path class has a curvature() method that computes the curvature K of your smooth path (K = 1/(radius of curvature)). This can be helpful for verifying that your curves transition smoothly such as in track-transition curves (also known as “racetrack”, “Euler”, or “straight-to-bend” curves in the photonics world). Note this curvature is numerically computed so areas where the curvature jumps instantaneously (such as between an arc and a straight segment) will be slightly interpolated, and sudden changes in point density along the curve can cause discontinuities. It is recommended if using the curvature calculation with pp.straight to set num_pts = 100 or more to avoid bad approximations

[22]:
P = Path()
P.append([
    pp.straight(length = 10, num_pts = 100), # Curvature of 0
    # Euler straight-to-bend transition with min. bend radius of 3 (max curvature of 1/3)
    pp.euler(radius = 3, angle = 90, p = 0.5, use_eff = False),
    pp.straight(length = 10, num_pts = 100), # Curvature of 0
    pp.arc(radius = 10, angle = 90),         # Curvature of 1/10
    pp.arc(radius = 5, angle = -90),         # Curvature of -1/5
    pp.straight(length = 20, num_pts = 100), # Curvature of 0
    ])

s,K = P.curvature()
plt.plot(s,K,'.-')
plt.xlabel('Position along curve (arc length)')
plt.ylabel('Curvature');
_images/tutorials_waveguides_44_0.png

Transitioning between cross-sections

Often a critical element of building paths is being able to transition between cross-sections. You can use the transition() function to do exactly this: you simply feed it two CrossSections and it will output a new CrossSection that smoothly transitions between the two.

Let’s start off by creating two cross-sections we want to transition between. Note we give all the cross-sectional elements names by specifying the name argument in the add() function – this is important because the transition function will try to match names between the two input cross-sections, and any names not present in both inputs will be skipped.

[23]:
# Create our first CrossSection
X1 = CrossSection()
X1.add(width = 1.2, offset = 0, layer = 2, name = 'wg', ports = ('in1', 'out1'))
X1.add(width = 2.2, offset = 0, layer = 3, name = 'etch')
X1.add(width = 1.1, offset = 3, layer = 1, name = 'wg2')

# Create the second CrossSection that we want to transition to
X2 = CrossSection()
X2.add(width = 1, offset = 0, layer = 2, name = 'wg', ports = ('in2', 'out2'))
X2.add(width = 3.5, offset = 0, layer = 3, name = 'etch')
X2.add(width = 3, offset = 5, layer = 1, name = 'wg2')

# To show the cross-sections, let's create two Paths and
# create Devices by extruding them
P1 = pp.straight(length = 5)
P2 = pp.straight(length = 5)
WG1 = P1.extrude(X1)
WG2 = P2.extrude(X2)

# Place both cross-section Devices and quickplot them
D = Device()
wg1 = D << WG1
wg2 = D << WG2
wg2.movex(7.5)

qp(D)
_images/tutorials_waveguides_46_0.png

Now let’s create the transitional CrossSection by calling transition() with these two CrossSections as input. If we want the width to vary as a smooth sinusoid between the sections, we can set width_type to 'sine' (alternatively we could also use 'linear').

[24]:
# Create the transitional CrossSection
Xtrans = pp.transition(cross_section1 = X1,
                       cross_section2 = X2,
                       width_type = 'sine')
# Create a Path for the transitional CrossSection to follow
P3 = pp.straight(length = 15)
# Use the transitional CrossSection to create a Device
WG_trans = P3.extrude(Xtrans)

qp(WG_trans)
_images/tutorials_waveguides_48_0.png

Now that we have all of our components, let’s connect() everything and see what it looks like

[25]:
D = Device()
wg1 = D << WG1 # First cross-section Device
wg2 = D << WG2
wgt = D << WG_trans

wgt.connect('in2', wg1.ports['out1'])
wg2.connect('in2', wgt.ports['out1'])

qp(D)
_images/tutorials_waveguides_50_0.png

Note that since transition() outputs a CrossSection, we can make the transition follow an arbitrary path:

[26]:
# Transition along a curving Path
P4 = pp.euler(radius = 25, angle = 45, p = 0.5, use_eff = False)
WG_trans = P4.extrude(Xtrans)

D = Device()
wg1 = D << WG1 # First cross-section Device
wg2 = D << WG2
wgt = D << WG_trans

wgt.connect('in2', wg1.ports['out1'])
wg2.connect('in2', wgt.ports['out1'])

qp(D)
_images/tutorials_waveguides_52_0.png

Variable width / offset

In some instances, you may want to vary the width or offset of the path’s cross-section as it travels. This can be accomplished by giving the CrossSection arguments that are functions or lists. Let’s say we wanted a width that varies sinusoidally along the length of the Path. To do this, we need to make a width function that is parameterized from 0 to 1: for an example function my_width_fun(t) where the width at t==0 is the width at the beginning of the Path and the width at t==1 is the width at the end.

[27]:
def my_custom_width_fun(t):
    # Note: Custom width/offset functions MUST be vectorizable--you must be able
    # to call them with an array input like my_custom_width_fun([0, 0.1, 0.2, 0.3, 0.4])
    num_periods = 5
    w =  3 + np.cos(2*np.pi*t * num_periods)
    return w

# Create the Path
P = pp.straight(length = 40)

# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
X = CrossSection()
X.add(width = 3,                   offset = -6, layer = 0)
X.add(width = my_custom_width_fun, offset = 0,  layer = 0)

# Extrude the Path to create the Device
D = P.extrude(X)

qp(D)
_images/tutorials_waveguides_54_0.png

We can do the same thing with the offset argument:

[28]:
def my_custom_offset_fun(t):
    # Note: Custom width/offset functions MUST be vectorizable--you must be able
    # to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
    num_periods = 3
    w =  3 + np.cos(2*np.pi*t * num_periods)
    return w

# Create the Path
P = pp.straight(length = 40)

# Create two cross-sections: one fixed offset, one modulated by my_custom_offset_fun
X = CrossSection()
X.add(width = 1, offset = my_custom_offset_fun, layer = 0)
X.add(width = 1, offset = 0, layer = 0)

# Extrude the Path to create the Device
D = P.extrude(X)

qp(D)
_images/tutorials_waveguides_56_0.png

Offsetting a Path

Sometimes it’s convenient to start with a simple Path and offset the line it follows to suit your needs (without using a custom-offset CrossSection). Here, we start with two copies of simple straight Path and use the offset() function to directly modify each Path.

[29]:
def my_custom_offset_fun(t):
    # Note: Custom width/offset functions MUST be vectorizable--you must be able
    # to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
    num_periods = 1
    w =  2 + np.cos(2*np.pi*t * num_periods)
    return w


P1 = pp.straight(length = 40)
P2 = P1.copy() # Make a copy of the Path

P1.offset(offset = my_custom_offset_fun)
P2.offset(offset = my_custom_offset_fun)
P2.mirror((1,0)) # reflect across X-axis

qp([P1, P2])
_images/tutorials_waveguides_58_0.png

Modifying a CrossSection

In case you need to modify the CrossSection, it can be done simply by specifying a name argument for the cross-sectional element you want to modify later. Here is an example where we name one of thee cross-sectional elements 'myelement1' and 'myelement2':

[30]:
# Create the Path
P = pp.arc(radius = 10, angle = 45)

# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
X = CrossSection()
X.add(width = 1, offset = 0, layer = 0, ports = (1,2), name = 'myelement1')
X.add(width = 1, offset = 3, layer = 0, ports = (3,4), name = 'myelement2')

# Extrude the Path to create the Device
D = P.extrude(X)

qp(D)
_images/tutorials_waveguides_60_0.png

In case we want to change any of the CrossSection elements, we simply access the Python dictionary that specifies that element and modify the values

[31]:
# Copy our original CrossSection
Xcopy = X.copy()

# Modify
Xcopy['myelement2']['width'] = 2 # X['myelement2'] is a dictionary
Xcopy['myelement2']['layer'] = 1 # X['myelement2'] is a dictionary

# Extrude the Path to create the Device
D = P.extrude(Xcopy)

qp(D)
_images/tutorials_waveguides_62_0.png

Routing

Often when creating a design, you need to connect geometries together with wires or waveguides. To help with that, PHIDL has the phidl.routing (pr) module, which can flexibly and quickly create routes between ports.

Simple quadrilateral routes

In general, a route is a polygon used to connect two ports. Simple routes are easy to create using pr.route_quad(). This function returns a quadrilateral route that directly connects two ports, as shown in this example:

[2]:
from phidl import Device, quickplot as qp
import phidl.geometry as pg
import phidl.routing as pr

# Use pg.compass() to make 2 boxes with North/South/East/West ports
D = Device()
c1 = D << pg.compass()
c2 = D << pg.compass().move([10,5]).rotate(15)

# Connect the East port of one box to the West port of the other
R = pr.route_quad(c1.ports['E'], c2.ports['W'],
                  width1 = None, width2 = None,  # width = None means use Port width
                  layer = 2)
qp([R,D])
_images/tutorials_routing_3_0.png

Automatic manhattan routing

In many cases, we need to draw wires or waveguides between two objects, and we’d prefer not to have to hand-calculate all the points. In these instances, we can use the automatic routing functions pr.route_smooth() and pr.route_sharp().

These functions allow you to route along a Path, and come with built-in options that let you control the shape of the path and how to extrude it. If you don’t need detailed control over the path your route takes, you can let these functions create an automatic manhattan route by leaving the default path_type='manhattan'. Just make sure the ports you’re to routing between face parallel or orthogonal directions.

[3]:
from phidl import Device, quickplot as qp
import phidl.geometry as pg
import phidl.routing as pr

# Use pg.compass() to make 4 boxes with North/South/East/West ports
D = Device()
smooth1 = D << pg.compass([4,15])
smooth2 = D << pg.compass([15,4]).move([35,35])
sharp1 = D << pg.compass([4,15]).movex(50)
sharp2 = D << pg.compass([15,4]).move([35,35]).movex(50)

# Connect the South port of one box to the West port of the other
R1 = pr.route_smooth(smooth1.ports['S'], smooth2.ports['W'], radius=8, layer = 2)
R2 = pr.route_sharp(  sharp1.ports['S'],  sharp2.ports['W'], layer = 2)

qp([D, R1, R2])
_images/tutorials_routing_6_0.png
Customized widths / cross-sections

By default, route functions such as route_sharp() and route_smooth() will connect one port to another with polygonal paths that are as wide as the ports are. However, you can override this by setting the width parameter in the same way as extrude():

  • If set to a single number (e.g. width=1.7): makes a fixed-width extrusion
  • If set to a 2-element array (e.g. width=[1.8,2.5]): makes an extrusion whose width varies linearly from width[0] to width[1]
  • If set to a CrossSection: uses the CrossSection parameters for extrusion
[4]:
from phidl import CrossSection
import phidl.routing as pr

# Create input ports
port1 = D.add_port(name='smooth1', midpoint=(40,  0), width=5, orientation=180)
port2 = D.add_port(name='smooth2', midpoint=(0, -40), width=5, orientation=270)

# (left) Setting width to a constant
D1 = pr.route_smooth(port1, port2, width = 2, radius=10, layer = 0)

# (middle) Setting width to a 2-element list to linearly vary the width
D2 = pr.route_smooth(port1, port2, width = [7, 1.5], radius=10, layer = 1)

# (right) Setting width to a CrossSection
X = CrossSection()
X.add(width=1, layer=4)
X.add(width=2.5, offset =  3, layer = 5)
X.add(width=2.5, offset = -3, layer = 5)
D3 = pr.route_smooth(port1, port2, width = X, radius=10)

qp([D1, D2.movex(50), D3.movex(100)])
_images/tutorials_routing_8_0.png
Details of operation

The route_smooth() function works in three steps:

  1. It calculates a waypoint Path using a waypoint path function – such as pr.path_manhattan() – set by the path_type.
  2. It smooths out the waypoint Path using pp.smooth().
  3. It extrudes the Path to create the route geometry.

The route_sharp() function works similarly, but it omits step 2 to create sharp bends. The extra smoothing makes route_smooth() particularly useful for photonic / microwave waveguides, whereas route_sharp() is typically more useful for electrical wiring.

To illustrate how these functions work, let’s look at how you could manually implement a similar behaviour to route_smooth(path_type='manhattan'):

[5]:
from phidl import CrossSection
import phidl.path as pp

D = Device()
port1 = D.add_port(name=1, midpoint=(40,0), width=5, orientation=180)
port2 = D.add_port(name=2, midpoint=(0, -40), width=5, orientation=270)

# Step 1: Calculate waypoint path
route_path = pr.path_manhattan(port1, port2, radius=10)
# Step 2: Smooth waypoint path
smoothed_path = pp.smooth(route_path, radius=10, use_eff=True)
# Step 3: Extrude path
D.add_ref(smoothed_path.extrude(width=5, layer=0))

qp([route_path,D])
_images/tutorials_routing_10_0.png

We can even customize the bends produced by pr.route_smooth() using the smooth_options, which are passed to pp.smooth(), such as controlling the corner-smoothing function or changing the number of points that will be rendered:

[6]:
D = Device()
port1 = D.add_port(name='smooth1', midpoint=(40,0), width=3, orientation=180)
port2 = D.add_port(name='smooth2', midpoint=(0, -40), width=3, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, radius=10, smooth_options={'corner_fun': pp.arc, 'num_pts': 16}))
qp(D)
_images/tutorials_routing_12_0.png

Customizing route paths

Avoiding obstacles

Sometimes, automatic routes will run into obstacles in your layout, like this:

[7]:
import phidl.geometry as pg

D = Device()
Obstacle = D.add_ref(pg.rectangle(size=(30,15), layer=1)).move((10, -25))

port1 = D.add_port(name=1, midpoint=(40,0), width=5, orientation=180)
port2 = D.add_port(name=2, midpoint=(0, -40), width=5, orientation=270)

D.add_ref(pr.route_smooth(port1, port2, radius=10, path_type='manhattan'))
qp(D)
_images/tutorials_routing_14_0.png

Example 1: Custom ``J`` paths. To avoid the other device, we need to customize the path our route takse. Luckily, PHIDL provides several waypoint path functions to help us do that quickly. Each of these waypoint path functions has a name of the form pr.path_*** (e.g. pr.path_L()), and generates a particular path type with its own shape. All the available path types are described in detail below and in the Geometry Reference. In this case, we want to connect two orthogonal ports, but the ports are positioned such that we can’t connect them with a single 90-degree turn. A J-shaped path with four line segments and three turns is perfect for this problem. We can tell route_smooth to use pr.path_J() as its waypoint path function via the argument path_type='J'.

[8]:
D = Device()
Obstacle = D.add_ref(pg.rectangle(size=(30,15), layer=1))
Obstacle.move((10, -25))
port1 = D.add_port(name=1, midpoint=(40,0), width=5, orientation=180)
port2 = D.add_port(name=2, midpoint=(0, -40), width=5, orientation=270)

D.add_ref(pr.route_smooth(port1, port2, radius=10, path_type='J', length1=60, length2=20))
qp(D)

_images/tutorials_routing_16_0.png

For ease of use, the waypoint path functions are parameterized in terms of relative distances from ports. Above, we had to define the keyword arguments length1 and length2, which are passed to pr.route_J() for the waypoint path calculation. These arguments length1 and length2 define the lengths of the line segments that exit port1 and port2 respectively (i.e. the first and last sements in the path). Once those first and last segments are set, path_J() completes the waypoint path with two more 90-degree turns. Note that just knowing length1 and length2, along with the port positions and orientations, is enough to completely determine the waypoint path.

Example 2: Custom ``C`` paths. Now consider this routing problem:

[9]:
D = Device()
Obstacle = D.add_ref(pg.rectangle(size=(20,30),layer=1))
Obstacle.move((10, -25))
port1 = D.add_port(name=1, midpoint=(0,0), width=5, orientation=180)
port2 = D.add_port(name=2, midpoint=(40, -5), width=5, orientation=0)

D.add_ref(pr.route_sharp(port1, port2, path_type='manhattan'))
qp(D)
_images/tutorials_routing_19_0.png

In this case, we want a C path. C paths have three parameters we need to define: + length1 and length2, which are the lengths of the segments that exit port1 and port2 (similar to the J path), as well as + left1, which is the length of the segment that turns left from port1.

In this case, we would actually prefer that the path turns right after it comes out of port1, so that our route avoids the other device. To make that happen, we can just set left1<0:

[10]:
D = Device()
Obstacle = D.add_ref(pg.rectangle(size=(20,30),layer=1))
Obstacle.move((10, -25))
port1 = D.add_port(name=1, midpoint=(0,0), width=5, orientation=180)
port2 = D.add_port(name=2, midpoint=(40, -5), width=5, orientation=0)

D.add_ref(pr.route_sharp(port1, port2, path_type='C', length1=10, length2=10, left1=-10))
qp(D)
_images/tutorials_routing_21_0.png

Example 3: Custom ``manual`` paths. For even more complex route problems, we can use path_type='manual' to create a route along an arbitrary path. In the example below, we use a manual path to route our way out of a sticky situation:

[11]:
D = Device()
Obstacle = D.add_ref(pg.rectangle(size=(30,15),layer=1)).move((0, -20))
Obstacle2 = D.add_ref(pg.rectangle(size=(15,20), layer=1))
Obstacle2.xmax,Obstacle2.ymin = Obstacle.xmax, Obstacle.ymax

port1 = D.add_port(name=1, midpoint=(5,  0), width=5, orientation=0)
port2 = D.add_port(name=2, midpoint=(50, 0), width=5, orientation=270)

manual_path = [ port1.midpoint,
                (Obstacle2.xmin-5, port1.y),
                (Obstacle2.xmin-5, Obstacle2.ymax+5),
                (Obstacle2.xmax+5, Obstacle2.ymax+5),
                (Obstacle2.xmax+5, port2.y-5),
                (port2.x, port2.y-5),
                port2.midpoint ]

D.add_ref(pr.route_sharp(port1, port2, path_type='manual', manual_path=manual_path))
qp(D)
_images/tutorials_routing_23_0.png

Note that to manually route between two ports, the first and last points in the manual_path should be the midpoints of the ports.

List of routing path types

PHIDL provides the following waypoint path types for routing:

Path type Routing style Segments Useful for … Parameters
manhattan Manhattan 1-5 parallel or orthogonal ports. radius
straight Manhattan 1 ports that point directly at each other.
L Manhattan 2 orthogonal ports that can be connected with one turn.
U Manhattan 3 parallel ports that face each other or same direction. length1
J Manhattan 4 orthogonal ports that can’t be connected with just one turn. length1, length2
C Manhattan 5 parallel ports that face apart. length1, length2, left1
V Free 2 ports at odd angles that face a common intersection point.
Z Free 3 ports at odd angles. length1, length2
manual Free fully custom paths. manual_path

For more details on each path type, you can also look at the API Documentation or the Geometry Reference.

The path types can be classified by their routing style. Manhattan style routing uses only 90-degree turns, and thus requires that you route between ports that are orthogonal or parallel (note that the ports don’t neccearrily have to point horizontally or vertically, though). For routing between ports at odd angles, you can use path types with a free routing style instead.

Most path types are named after letters that they resemble to help you remember them. However, as you’ll see in the examples below, some of the more complicated paths can take a variety of shapes. One good way to identify which manhattan-style route type you need is to count the number of line segments and consult the above table.

[12]:
D = Device()

#straight path
port1 = D.add_port(name='S1', midpoint=(-50, 0), width=4, orientation=90)
port2 = D.add_port(name='S2', midpoint=(-50, 50), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='straight'))
#L path
port1 = D.add_port(name='L1', midpoint=(30,0), width=4, orientation=180)
port2 = D.add_port(name='L2', midpoint=(0, 50), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2,  path_type='L'))
#U path
port1 = D.add_port(name='U1', midpoint=(50, 50), width=2, orientation=270)
port2 = D.add_port(name='U2', midpoint=(80,50), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2,  radius=10, path_type='U', length1=50))
port1 = D.add_port(name='U3', midpoint=(50, 80), width=4, orientation=10)
port2 = D.add_port(name='U4', midpoint=(80, 130), width=4, orientation=190)
D.add_ref(pr.route_smooth(port1, port2,  path_type='U', length1=20))
#J path
port1 = D.add_port(name='J1', midpoint=(100, 25), width=4, orientation=270)
port2 = D.add_port(name='J2', midpoint=(130, 50), width=4,  orientation=180)
D.add_ref(pr.route_smooth(port1, port2,  path_type='J', length1=25, length2=10))
port1 = D.add_port(name='J3', midpoint=(115, 105), width=5, orientation=270)
port2 = D.add_port(name='J4', midpoint=(131, 130), width=5,  orientation=180)
D.add_ref(pr.route_smooth(port1, port2, path_type='J', length1=25, length2=30))
#C path
port1 = D.add_port(name='C1', midpoint=(180, 35), width=4, orientation=90)
port2 = D.add_port(name='C2', midpoint=(178, 15), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=15, left1=30, length2=15))
port1 = D.add_port(name='C3', midpoint=(150, 105), width=4, orientation=90)
port2 = D.add_port(name='C4', midpoint=(180, 105), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=25, left1=-15, length2=25))
port1 = D.add_port(name='C5', midpoint=(150, 170), width=4, orientation=0)
port2 = D.add_port(name='C6', midpoint=(175, 170), width=4, orientation=0)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=10, left1=10, length2=10, radius=4))
#V path
port1 = D.add_port(name='V1', midpoint=(200,50), width=5, orientation=284)
port2 = D.add_port(name='V2', midpoint=(230, 50), width=5, orientation=270-14)
D.add_ref(pr.route_smooth(port1, port2, path_type='V'))
#Z path
port1 = D.add_port(name='Z1', midpoint=(280,0), width=4, orientation=190)
port2 = D.add_port(name='Z2', midpoint=(250, 50), width=3, orientation=-10)
D.add_ref(pr.route_smooth(port1, port2, path_type='Z', length1=30, length2=40))


qp(D)
_images/tutorials_routing_27_0.png

The manhattan path type is bending-radius aware and can produce any route neccessary to connect two ports, as long as they are orthogonal or parallel.

[13]:
import numpy as np
set_quickplot_options(show_ports=False, show_subports=False)

D = Device()
pitch = 40
test_range=20
x_centers = np.arange(5)*pitch
y_centers = np.arange(3)*pitch
xoffset = np.linspace(-1*test_range, test_range, 5)
yoffset = np.linspace(-1*test_range, test_range, 3)
for xidx, x0 in enumerate(x_centers):
    for yidx, y0 in enumerate(y_centers):
        name = '{}{}'.format(xidx, yidx)
        port1 = D.add_port(name=name+'1', midpoint=(x0, y0), width=5, orientation=0)
        port2 = D.add_port(name=name+'2', midpoint=(x0+xoffset[xidx], y0+yoffset[yidx]),
                           width=5,  orientation=90)
        D.add_ref(pr.route_smooth(port1, port2, route_type='manhattan'))
qp(D)
_images/tutorials_routing_29_0.png

Simple XY wiring

Often one requires simple wiring between two existing objects with Ports. For this purpose, you can use the route_xy() function. It allows a simple string to specify the path the wiring will take. So the argument directions = 'yxy' means “go 1 part in the Y direction, 1 part in the X direction, then 1 more part in the X direction” with the understanding that 1 part X = (total X distance from port1 to port2)/(total number of ‘x’ in the directions)

As an example, say we have two objects with multiple ports, and we want to route multiple wires between them without overlapping

[14]:
from phidl import Device, quickplot as qp
import phidl.routing as pr
import phidl.geometry as pg

# Create boxes with multiple North/South ports
D = Device()
c1 = D.add_ref( pg.compass_multi(ports={'N':3}) )
c2 = D.add_ref( pg.compass_multi(ports={'S':3}) ).move([6,6])

qp(D)
_images/tutorials_routing_31_0.png

We can then route_xy() between them and use different directions arguments to prevent them from overlapping:

[15]:
D.add_ref( pr.route_xy(port1 = c1.ports['N1'], port2 = c2.ports['S1'],
    directions = 'yyyxy', width = 0.1, layer = 2) )
D.add_ref( pr.route_xy(port1 = c1.ports['N2'], port2 = c2.ports['S2'],
    directions = 'yyxy',  width = 0.2, layer = 2) )
D.add_ref( pr.route_xy(port1 = c1.ports['N3'], port2 = c2.ports['S3'],
    directions = 'yxy',   width = 0.4, layer = 2) )

qp(D)
_images/tutorials_routing_33_0.png
  • In the first case, when directions = 'yyyxy': The route traveled up 3/4 of the total Y distance, then the full x distance, then the remaining 1/4 of the y distance (there are 4 y characters and only 1 x character)
  • In the second case, when directions = 'yyxy': The route traveled up 2/3 of the total Y distance, then the full x distance, then the remaining 1/3 of the y distance
  • In the third case, when directions = 'yxy': The route traveled up 1/2 of the total Y distance, then the full x distance, then the remaining 1/2 of the y distance

Geometry library

Reference for the built-in geometry library in PHIDL. Most of the functions here are found in PHIDL’s phidl.geometry library, which is typically imported as pg. For instance, below you will see the phidl.geometry.arc() function called as pg.arc()

Basic shapes

Rectangle

To create a simple rectangle, there are two functions:

pg.rectangle() can create a basic rectangle:

[2]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.rectangle(size = (4.5, 2), layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_4_0.png

pg.bbox() can also create a rectangle based on a bounding box. This is useful if you want to create a rectangle which exactly surrounds a piece of existing geometry. For example, if we have an arc geometry and we want to define a box around it, we can use pg.bbox():

[3]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
arc = D << pg.arc(radius = 10, width = 0.5, theta = 85, layer = 1).rotate(25)
# Draw a rectangle around the arc we created by using the arc's bounding box
rect = D << pg.bbox(bbox = arc.bbox, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_6_0.png

Cross

The pg.cross() function creates a cross structure:

[4]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.cross(length = 10, width = 0.5, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_8_0.png

Ellipse

The pg.ellipse() function creates an ellipse by defining the major and minor radii:

[5]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.ellipse(radii = (10,5), angle_resolution = 2.5, layer = 0)
qp(D)
_images/geometry_reference_10_0.png

Circle

The pg.circle() function creates a circle:

[6]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.circle(radius = 10, angle_resolution = 2.5, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_12_0.png

Ring

The pg.ring() function creates a ring. The radius refers to the center radius of the ring structure (halfway between the inner and outer radius).

[7]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.ring(radius = 5, width = 0.5, angle_resolution = 2.5, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_14_0.png

Arc

The pg.arc() function creates an arc. The radius refers to the center radius of the arc (halfway between the inner and outer radius). The arc has two ports, 1 and 2, on either end, allowing you to easily connect it to other structures.

[8]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.arc(radius = 2.0, width = 0.5, theta = 45, start_angle = 0,
           angle_resolution = 2.5, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_16_0.png

Tapers

pg.taper()is defined by setting its length and its start and end length. It has two ports, 1 and 2, on either end, allowing you to easily connect it to other structures.

[9]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.taper(length = 10, width1 = 6, width2 = 4, port = None, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_18_0.png

pg.ramp() is a structure is similar to taper() except it is asymmetric. It also has two ports, 1 and 2, on either end.

[10]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.ramp(length = 10, width1 = 4, width2 = 8, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_20_0.png

Common compound shapes

The pg.L() function creates a “L” shape with ports on either end named 1 and 2.

[11]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.L(width = 7, size = (10,20) , layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_23_0.png

The pg.C() function creates a “C” shape with ports on either end named 1 and 2.

[12]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.C(width = 7, size = (10,20) , layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_25_0.png

Text

PHIDL has an implementation of the DEPLOF font with the majority of english ASCII characters represented.

[13]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.text(text = 'Hello world!\nMultiline text\nLeft-justified', size = 10,
            justify = 'left', layer = 0)
# `justify` should be either 'left', 'center', or 'right'
qp(D) # quickplot the geometry
_images/geometry_reference_27_0.png

Furthermore, you can also use any true-type font or open-type font that is installed on your system (including unicode fonts). For portability, fonts can also be referenced by path.

[14]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.text(text = 'System fonts are\nalso supported!\nWith ùήίςόdέ', size = 10,
            justify = 'center', layer = 1, font="Comic Sans MS")
# Alternatively, fonts can be loaded by file name
qp(D) # quickplot the geometry
_images/geometry_reference_29_0.png

Grid / gridsweep / packer / align / distribute

Grid

The pg.grid() function can take a list (or 2D array) of objects and arrange them along a grid. This is often useful for making parameter sweeps. If the separation argument is true, grid is arranged such that the elements are guaranteed not to touch, with a spacing distance between them. If separation is false, elements are spaced evenly along a grid. The align_x/align_y arguments specify intra-row/intra-column alignment. Theedge_x/edge_y arguments specify inter-row/inter-column alignment (unused if separation = True).

[15]:
import phidl.geometry as pg
from phidl import quickplot as qp

device_list = []
for width1 in [1, 6, 9]:
    for width2 in [1, 2, 4, 8]:
        D = pg.taper(length = 10, width1 = width1, width2 = width2, layer = 0)
        device_list.append(D)

G = pg.grid(device_list,
            spacing = (5,1),
            separation = True,
            shape = (3,4),
            align_x = 'x',
            align_y = 'y',
            edge_x = 'x',
            edge_y = 'ymax')

qp(G)
_images/geometry_reference_32_0.png

Gridsweep

The pg.gridsweep() function creates a parameter sweep of devices and places them on a grid, optionally labeling each device with its specific parameters. You can sweep multiple parameters simultaneously in the x and/or y direction, allowing you to make very complex parameter sweeps. See the grid documentation above for further explanation about the edge_x/edge_y/separation/spacing arguments

In the simplest example, we can just sweep one parameter each in the x and y directions. Here, let’s sweep the radius argument of pg.circle in the x-direction, and the layer argument in the y-direction:

[16]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.gridsweep(
    function = pg.circle,
    param_x = {'radius' : [10,20,30,40,50]},
    param_y = {'layer'  : [0,  5, 9]},
    param_defaults = {},
    param_override = {},
    spacing = (30,10),
    separation = True,
    align_x = 'x',
    align_y = 'y',
    edge_x = 'x',
    edge_y = 'ymax',
    label_layer = None)

qp(D)
_images/geometry_reference_34_0.png

In a more advanced sweep, we can vary multiple parameters. Here we’ll vary one parameter in the x-direction (the ellipse width), but two parameters in the y-direction (the ellipse height and the layer):

[17]:
import phidl.geometry as pg
from phidl import quickplot as qp

def custom_ellipse(width, height, layer):
    D = pg.ellipse(radii = (width, height), layer = layer)
    return D

D = pg.gridsweep(
    function = custom_ellipse,
    param_x = {'width' :  [40, 80, 120, 160]},
    param_y = {'height' : [10, 50],
               'layer' :  [1, 2, 3]  },
    spacing = (30,10))

qp(D)
_images/geometry_reference_36_0.png

The param_defaults and param_override arguments are also useful when you want to make multiple sets of sweeps and but want to have a baseline/default set of parameters to use. Here, we can make an array of litho_calipers() to perform alignment between multiple layers, and use the param_defaults

[18]:
import phidl.geometry as pg
from phidl import quickplot as qp

param_defaults = dict( notch_size = [2, 5],
                       notch_spacing = 2,
                       num_notches = 3,
                       offset_per_notch = 0.2,
                       row_spacing = 0
                     )

D = pg.gridsweep(
    function = pg.litho_calipers,
    param_x = {'layer1' : [0,1,2]},
    param_y = {'layer2'  : [0,1,2]},
    param_defaults = param_defaults,
    param_override = {},
    spacing = (25,10))

qp(D)
_images/geometry_reference_38_0.png

Later, if we want to change some of the default parameters, we can use the same param_defaults dictionary but override one (or more) of the parameters. Here, let’s keep everything the same but override the row_spacing parameter to put a little vertical space between the layers:

[19]:
D = pg.gridsweep(
    function = pg.litho_calipers,
    param_x = {'layer1' : [0,1,2]},
    param_y = {'layer2'  : [0,1,2]},
    param_defaults = param_defaults,
    param_override = {'row_spacing' : 3}, # <--- overriding the "row_spacing" parameter
    spacing = (25, 10))

qp(D)
_images/geometry_reference_40_0.png

Packer

The pg.packer() function is able to pack geometries together into rectangular bins. If a max_size is specified, the function will create as many bins as is necessary to pack all the geometries and then return a list of the filled-bin Devices.

Here we generate several random shapes then pack them together automatically. We allow the bin to be as large as needed to fit all the Devices by specifying max_size = (None, None). By setting aspect_ratio = (2,1), we specify the rectangular bin it tries to pack them into should be twice as wide as it is tall:

[20]:
import phidl.geometry as pg
from phidl import quickplot as qp
import numpy as np

# Create a lot of random objects
np.random.seed(3)
D_list = [pg.ellipse(radii = np.random.rand(2)*n+2, layer = 0) for n in range(50)]
D_list += [pg.rectangle(size = np.random.rand(2)*n+2, layer = 2) for n in range(50)]

# Also create a parameter sweep
D = pg.gridsweep(
    function = pg.circle,
    param_x = {'radius' : [6,8,10,15,20]},
    param_y = {'layer'  : [1, 5, 9]},
    spacing = (3,1))
D_list.append(D)

# Pack everything together
D_packed_list = pg.packer(
        D_list,                 # Must be a list or tuple of Devices
        spacing = 1.25,         # Minimum distance between adjacent shapes
        aspect_ratio = (2,1),   # (width, height) ratio of the rectangular bin
        max_size = (None,None), # Limits the size into which the shapes will be packed
        density = 1.05,         # Values closer to 1 pack tighter but takes longer
        sort_by_area = True,    # Pre-sorts the shapes by area
        verbose = False,
        )
D = D_packed_list[0] # Only one bin was created, so we plot that
qp(D) # quickplot the geometry
_images/geometry_reference_42_0.png

Say we need to pack many shapes into multiple 500x500 unit die. If we set max_size = (500,500) the shapes will be packed into as many 500x500 unit die as required to fit them all:

[21]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device
import numpy as np

np.random.seed(1)
D_list = [pg.ellipse(radii = np.random.rand(2)*n+2) for n in range(120)]
D_list += [pg.rectangle(size = np.random.rand(2)*n+2) for n in range(120)]

D_packed_list = pg.packer(
        D_list,                 # Must be a list or tuple of Devices
        spacing = 4,         # Minimum distance between adjacent shapes
        aspect_ratio = (1,1),   # Shape of the box
        max_size = (500,500),   # Limits the size into which the shapes will be packed
        density = 1.05,         # Values closer to 1 pack tighter but require more computation
        sort_by_area = True,    # Pre-sorts the shapes by area
        verbose = False,
        )

# Put all packed bins into a single device and spread them out with distribute()
F = Device()
[F.add_ref(D) for D in D_packed_list]
F.distribute(elements = 'all', direction = 'x', spacing = 100, separation = True)
qp(F)
_images/geometry_reference_44_0.png

Note that the packing problem is an NP-complete problem, so pg.packer() may be slow if there are more than a few hundred Devices to pack (in that case, try pre-packing a few dozen at a time then packing the resulting bins). Requires the rectpack python package.

Distribute

The distribute() function allows you to space out elements within a Device evenly in the x or y direction. It is meant to duplicate the distribute functionality present in Inkscape / Adobe Illustrator:

inkscape distribute

Say we start out with a few random-sized rectangles we want to space out:

[22]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
# Create different-sized rectangles and add them to D
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]

qp(D) # quickplot the geometry
_images/geometry_reference_49_0.png

Oftentimes, we want to guarantee some distance between the objects. By setting separation = True we move each object such that there is spacing distance between them:

[23]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
# Create different-sized rectangles and add them to D
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]
# Distribute all the rectangles in D along the x-direction with a separation of 5
D.distribute(elements = 'all',   # either 'all' or a list of objects
             direction = 'x',    # 'x' or 'y'
             spacing = 5,
             separation = True)

qp(D) # quickplot the geometry
_images/geometry_reference_51_0.png

Alternatively, we can spread them out on a fixed grid by setting separation = False. Here we align the left edge (edge = 'ymin') of each object along a grid spacing of 100:

[24]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]
D.distribute(elements = 'all', direction = 'x', spacing = 100, separation = False,
             edge = 'xmin')
# edge must be either 'xmin' (left), 'xmax' (right), or 'x' (x-center)
# or if direction = 'y' then
# edge must be either 'ymin' (bottom), 'ymax' (top), or 'y' (y-center)

qp(D) # quickplot the geometry
_images/geometry_reference_53_0.png

The alignment can be done along the right edge as well by setting edge = 'xmax', or along the x-center by setting edge = 'x' like in the following:

[25]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n-10,n*4))) for n in [0,2,3,1,2]]
D.distribute(elements = 'all', direction = 'x', spacing = 100, separation = False,
             edge = 'x')
# edge must be either 'xmin' (left), 'xmax' (right), or 'x' (x-center)
# or if direction = 'y' then
# edge must be either 'ymin' (bottom), 'ymax' (top), or 'y' (y-center)

qp(D) # quickplot the geometry
_images/geometry_reference_55_0.png

Align

The align() function allows you to elements within a Device horizontally or vertically. It is meant to duplicate the alignment functionality present in Inkscape / Adobe Illustrator:

inkscape align

Say we distribute() a few objects, but they’re all misaligned:

[26]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
# Create different-sized rectangles and add them to D then distribute them
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]
D.distribute(elements = 'all', direction = 'x', spacing = 5, separation = True)

qp(D) # quickplot the geometry
_images/geometry_reference_59_0.png

we can use the align() function to align their top edges (``alignment = ‘ymax’):

[27]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
# Create different-sized rectangles and add them to D then distribute them
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]
D.distribute(elements = 'all', direction = 'x', spacing = 5, separation = True)

# Align top edges
D.align(elements = 'all', alignment = 'ymax')

qp(D) # quickplot the geometry
_images/geometry_reference_61_0.png

or align their centers (``alignment = ‘y’):

[28]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D = Device()
# Create different-sized rectangles and add them to D then distribute them
[D.add_ref(pg.rectangle(size = [n*15+20,n*15+20]).move((n,n*4))) for n in [0,2,3,1,2]]
D.distribute(elements = 'all', direction = 'x', spacing = 5, separation = True)

# Align top edges
D.align(elements = 'all', alignment = 'y')

qp(D) # quickplot the geometry
_images/geometry_reference_63_0.png

other valid alignment options include 'xmin', 'x', 'xmax', 'ymin', 'y', and 'ymax'

Boolean / outline / offset / invert

There are several common boolean-type operations available in the geometry library. These include typical boolean operations (and/or/not/xor), offsetting (expanding/shrinking polygons), outlining, and inverting.

Boolean

The pg.boolean() function can perform AND/OR/NOT/XOR operations, and will return a new geometry with the result of that operation.

All shapes in a single device can be merged with pg.boolean(Device, None, opertion = 'or').

Speedup note: The num_divisions argument can be used to divide up the geometry into multiple rectangular regions and process each region sequentially (which is more computationally efficient). If you have a large geometry that takes a long time to process, try using num_divisions = [10,10] to optimize the operation.

[29]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

D1 = pg.circle(radius = 5, layer = 1).move([5,5])
D2 = pg.rectangle(size = [10, 10], layer = 2).move([-5,-5])
NOT = pg.boolean(A = D1, B = D2, operation = 'not', precision = 1e-6,
               num_divisions = [1,1], layer = 0)
AND = pg.boolean(A = D1, B = D2, operation = 'and', precision = 1e-6,
               num_divisions = [1,1], layer = 0)
OR  = pg.boolean(A = D1, B = D2, operation = 'or',  precision = 1e-6,
               num_divisions = [1,1], layer = 0)
XOR = pg.boolean(A = D1, B = D2, operation = 'xor', precision = 1e-6,
               num_divisions = [1,1], layer = 0)
# ‘A+B’ is equivalent to ‘or’. ‘A-B’ is equivalent to ‘not’.
# ‘B-A’ is equivalent to ‘not’ with the operands switched.

# Plot the originals and the result
D = Device()
D.add_ref(D1)
D.add_ref(D2)
D.add_ref(NOT).move([25, 10])  # top left
D.add_ref(AND).move([45, 10])  # top right
D.add_ref(OR).move([25, -10])  # bottom left
D.add_ref(XOR).move([45, -10]) # bottom right
qp(D) # quickplot the geometry
_images/geometry_reference_67_0.png

Offset

The pg.offset() function takes the polygons of the input geometry, combines them together, and expands/contracts them. The function returns polygons on a single layer – it does not respect layers.

Speedup note: The num_divisions argument can be used to divide up the geometry into multiple rectangular regions and process each region sequentially (which is more computationally efficient). If you have a large geometry that takes a long time to process, try using num_divisions = [10,10] to optimize the operation.

[30]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

# Create `T`, an ellipse and rectangle which will be offset (expanded / contracted)
T = Device()
e = T << pg.ellipse(radii = (10,5), layer = 1)
r = T << pg.rectangle(size = [15,5], layer = 2)
r.move([3,-2.5])

Texpanded = pg.offset(T, distance = 2, join_first = True, precision = 1e-6,
        num_divisions = [1,1], layer = 0)
Tshrink = pg.offset(T, distance = -1.5, join_first = True, precision = 1e-6,
        num_divisions = [1,1], layer = 0)

# Plot the original geometry, the expanded, and the shrunk versions
D = Device()
t1 = D.add_ref(T)
t2 = D.add_ref(Texpanded)
t3 = D.add_ref(Tshrink)
D.distribute([t1,t2,t3], direction = 'x', spacing = 5)
qp(D) # quickplot the geometry
_images/geometry_reference_69_0.png

Outline

The pg.outline() function takes the polygons of the input geometry then performs an offset and “not” boolean operation to create an outline. The function returns polygons on a single layer – it does not respect layers.

Speedup note: The num_divisions argument can be used to divide up the geometry into multiple rectangular regions and process each region sequentially (which is more computationally efficient). If you have a large geometry that takes a long time to process, try using num_divisions = [10,10] to optimize the operation.

[31]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

# Create a blank device and add two shapes
X = Device()
X.add_ref(pg.cross(length = 25, width = 1, layer = 1))
X.add_ref(pg.ellipse(radii = [10,5], layer = 2))

O = pg.outline(X, distance = 1.5, precision = 1e-6, layer = 0)

# Plot the original geometry and the result
D = Device()
D.add_ref(X)
D.add_ref(O).movex(30)
qp(D) # quickplot the geometry
_images/geometry_reference_71_0.png

The open_ports argument opens holes in the outlined geometry at each Port location. If not False, holes will be cut in the outline such that the Ports are not covered. If True, the holes will have the same width as the Ports. If a float, the holes will be widened by that value. If a float equal to the outline distance, the outline will be flush with the port (useful positive-tone processes).

[32]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

# Create a geometry with ports
D = pg.L(width = 7, size = (10,20), layer = 1)

# Outline the geometry and open a hole at each port
N = pg.outline(D, distance = 5, open_ports = False) # No holes
O = pg.outline(D, distance = 5, open_ports = True)  # Hole is the same width as the port
P = pg.outline(D, distance = 5, open_ports = 2.9)  # Change the hole size by entering a float
Q = pg.outline(D, distance = 5, open_ports = 5)     # Creates flush opening (open_ports > distance)

# Plot the original geometry and the results
(D+N+O+P+Q).distribute(spacing = 10)
qp([D,N,O,P,Q])
_images/geometry_reference_73_0.png

Invert

The pg.invert() function creates an inverted version of the input geometry. The function creates a rectangle around the geometry (with extra padding of distance border), then subtract all polygons from all layers from that rectangle, resulting in an inverted version of the geometry.

Speedup note: The num_divisions argument can be used to divide up the geometry into multiple rectangular regions and process each region sequentially (which is more computationally efficient). If you have a large geometry that takes a long time to process, try using num_divisions = [10,10] to optimize the operation.

[33]:
import phidl.geometry as pg
from phidl import quickplot as qp

E = pg.ellipse(radii = (10,5))
D = pg.invert(E, border = 0.5, precision = 1e-6, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_75_0.png

Union

The pg.union() function is a “join” function, and is functionally identical to the “OR” operation of pg.boolean(). The one difference is it’s able to perform this function layer-wise, so each layer can be individually combined.

[34]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = Device()
D << pg.ellipse(layer = 0)
D << pg.ellipse(layer = 0).rotate(15*1)
D << pg.ellipse(layer = 0).rotate(15*2)
D << pg.ellipse(layer = 0).rotate(15*3)
D << pg.ellipse(layer = 1).rotate(15*4)
D << pg.ellipse(layer = 1).rotate(15*5)

# We have two options to unioning - take all polygons, regardless of
# layer, and join them together (in this case on layer 4) like so:
D_joined = pg.union(D, by_layer = False, layer = 4)

# Or we can perform the union operate by-layer
D_joined_by_layer = pg.union(D, by_layer = True)

# Space out shapes
D.add_ref(D_joined).movex(25)
D.add_ref(D_joined_by_layer).movex(50)
qp(D) # quickplot the geometry
_images/geometry_reference_77_0.png

XOR / diff

The pg.xor_diff() function can be used to compare two geometries and identify where they are different. Specifically, it performs a layer-wise XOR operation. If two geometries are identical, the result will be an empty Device. If they are not identical, any areas not shared by the two geometries will remain.

[35]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

A = Device()
A.add_ref(pg.ellipse(radii = [10,5], layer = 1))
A.add_ref(pg.text('A')).move([3,0])
B = Device()
B.add_ref(pg.ellipse(radii = [11,4], layer = 1).movex(4))
B.add_ref(pg.text('B')).move([3.2,0])
X = pg.xor_diff(A = A, B = B, precision = 1e-6)

# Plot the original geometry and the result
# Upper left: A / Upper right: B
# Lower left: A and B / Lower right: A xor B "diff" comparison
D = Device()
D.add_ref(A).move([-15,25])
D.add_ref(B).move([15,25])
D.add_ref(A).movex(-15)
D.add_ref(B).movex(-15)
D.add_ref(X).movex(15)
qp(D) # quickplot the geometry
_images/geometry_reference_79_0.png

Lithography structures

Step-resolution

The pg.litho_steps() function creates lithographic test structure that is useful for measuring resolution of photoresist or electron-beam resists. It provides both positive-tone and negative-tone resolution tests.

[36]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.litho_steps(
        line_widths = [1,2,4,8,16],
        line_spacing = 10,
        height = 100,
        layer = 0
        )
qp(D) # quickplot the geometry
_images/geometry_reference_81_0.png

Lithographic star

The pg.litho_star() function makes a common lithographic test structure known as a “star” that is just several lines intersecting at a centerpoint.

[37]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.litho_star(
        num_lines = 20,
        line_width = 0.4,
        diameter = 20,
        layer = 0
        )
qp(D) # quickplot the geometry
_images/geometry_reference_84_0.png

Calipers (inter-layer alignment)

The pg.litho_calipers() function is used to detect offsets in multilayer fabrication. It creates a two sets of notches on different layers. When an fabrication error/offset occurs, it is easy to detect how much the offset is because both center-notches are no longer aligned.

[38]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.litho_calipers(
        notch_size = [1,5],
        notch_spacing = 2,
        num_notches = 7,
        offset_per_notch = 0.1,
        row_spacing = 0,
        layer1 = 1,
        layer2 = 2)
qp(D) # quickplot the geometry
_images/geometry_reference_87_0.png

Ruler

This ruler structure is useful for measuring distances on a wafer. It features customizable ruler markings

[39]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.litho_ruler(
    height = 2 ,
    width = 0.25,
    spacing = 1,
    scale = [3,1,1,1,1,2,1,1,1,1],
    num_marks = 21,
    layer = 0,
    )

qp(D)
_images/geometry_reference_89_0.png

Paths / waveguides

See the Path tutorial for more details – this is just an enumeration of the available built-in Path functions

Circular arc

[40]:
import phidl.path as pp

P = pp.arc(radius = 10, angle = 135, num_pts = 720)
qp(P)
_images/geometry_reference_92_0.png

Straight

[41]:
import phidl.path as pp

P = pp.straight(length = 5, num_pts = 100)
qp(P)
_images/geometry_reference_94_0.png

Euler curve

Also known as a straight-to-bend, clothoid, racetrack, or track transition, this Path tapers adiabatically from straight to curved. Often used to minimize losses in photonic waveguides. If p < 1.0, will create a “partial euler” curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394. If the use_eff argument is false, radius corresponds to minimum radius of curvature of the bend. If use_eff is true, radius corresponds to the “effective” radius of the bend– The curve will be scaled such that the endpoints match an arc with parameters radius and angle.

[42]:
import phidl.path as pp

P = pp.euler(radius = 3, angle = 90, p = 1.0, use_eff = False, num_pts = 720)
qp(P)
_images/geometry_reference_96_0.png

Smooth path from waypoints

[43]:
points = np.array([(20,10), (40,10), (20,40), (50,40), (50,20), (70,20)])

P = pp.smooth(
    points = points,
    radius = 2,
    corner_fun = pp.euler,
    use_eff = False,
    )
qp(P)
_images/geometry_reference_98_0.png

Delay spiral

[44]:
import phidl.path as pp

P = pp.spiral(num_turns = 5.5, gap = 1, inner_gap = 2, num_pts = 10000)
qp(P)
_images/geometry_reference_100_0.png

Routing

See the Routing tutorial for more details – this is just an enumeration of the available built-in routing functions and path types

Quadrilateral routes

The route_quad() function creates a direct connection between two ports using a quadrilateral polygon. You can adjust the width of the route at the two ports using the width1 and width2 arguments. If these arguments are set to None, the width of the port is used.

[45]:
from phidl import Device, quickplot as qp
import phidl.geometry as pg
import phidl.routing as pr

# Use pg.compass() to make 2 boxes with North/South/East/West ports
D = Device()
c1 = D << pg.compass()
c2 = D << pg.compass().move([10,5]).rotate(15)

# Connect the East port of one box to the West port of the other
R = pr.route_quad(c1.ports['E'], c2.ports['W'],
                  width1 = None, width2 = None,  # width = None means use Port width
                  layer = 2)
qp([R,D])
_images/geometry_reference_103_0.png

Smooth routes

The route_smooth() function creates route geometries with smooth bends between two ports by extruding a Path. The waypoints the route follows are controlled by the path_type waypoint function selection (see below for examples of all the path types) and the keyword arguments passed to the waypoint function, such as length1 below, which sets the length of the segment exiting port1. The smooth bends are controlled using the radius and smooth_options arguments, which route_smooth() passes to pp.smooth(). Finally the width parameter is passed to Path.extrude() unless width=None is selected, in which case the route tapers linearly between the ports.

[46]:
from phidl import Device, quickplot as qp
import phidl.geometry as pg
import phidl.routing as pr
import phidl.path as pp

# Use pg.compass() to make 2 boxes with North/South/East/West ports
D = Device()
box1 = D << pg.compass([2,15])
box2 = D << pg.compass([15,6]).move([35,35])

# Connect the South port of one box to the West port of the other
R = pr.route_smooth(
        port1 = box1.ports['S'],
        port2 = box2.ports['W'],
        radius = 8,
        width = None,
        path_type = 'manhattan',
        manual_path = None,
        smooth_options=  {'corner_fun': pp.euler, 'use_eff': True},
        layer = 2,
)

qp([D, R])
_images/geometry_reference_105_0.png

Sharp routes

The route_sharp() function creates route geometries with sharp bends between two ports by extruding a Path. The waypoints the route follows are controlled by the path_type waypoint function selection (see below for examples of all the path types) and the keyword arguments passed to the waypoint function, such as length1 below, which sets the length of the segment exiting port1. The width parameter is passed to Path.extrude() unless width=None is selected, in which case the route tapers linearly between the ports.

[47]:
from phidl import Device, quickplot as qp
import phidl.geometry as pg
import phidl.routing as pr
import phidl.path as pp

# Use pg.compass() to make 2 boxes with North/South/East/West ports
D = Device()
box1 = D << pg.compass([6,15])
box2 = D << pg.compass([15,6]).move([35,35])

# Connect the South port of one box to the East port of the other
R = pr.route_sharp(
        port1 = box1.ports['S'],
        port2 = box2.ports['E'],
        width = None,
        path_type = 'manhattan',
        manual_path = None,
        layer = 2,
)

qp([D, R])
_images/geometry_reference_107_0.png

Simple XY wiring

[48]:
from phidl import Device, CrossSection
from phidl import quickplot as qp
import phidl.routing as pr
import phidl.geometry as pg
from phidl import set_quickplot_options
set_quickplot_options(show_ports=True, show_subports=True)

# Create multi-port devices
D = Device()
c1 = D.add_ref( pg.compass_multi(ports={'N':3}) )
c2 = D.add_ref( pg.compass_multi(ports={'S':3}) ).move([6,6])

# Create simply XY wiring
D.add_ref( pr.route_xy(port1 = c1.ports['N1'], port2 = c2.ports['S1'],
    directions = 'yyyxy', width = 0.1, layer = 2) )
D.add_ref( pr.route_xy(port1 = c1.ports['N2'], port2 = c2.ports['S2'],
    directions = 'yyxy',  width = 0.2, layer = 2) )
D.add_ref( pr.route_xy(port1 = c1.ports['N3'], port2 = c2.ports['S3'],
    directions = 'yxy',   width = 0.4, layer = 2) )
qp(D)
_images/geometry_reference_109_0.png

Path types

PHIDL comes with several built-in waypoint path types to help you use route_smooth() and route_sharp(). These path types can be accessed using the path_type parameters in route_smooth() and route_sharp(), or directly via the waypoint path functions pr.path_***(), for instance path_manhattan() and path_U().

See the Routing tutorial for more details on the usage of the path types.

straight path

This simple path can be used when two ports directly face each other.

[49]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='S1', midpoint=(0, 0), width=5, orientation=0)
port2 = D.add_port(name='S2', midpoint=(30, 0), width=5, orientation=180)
D.add_ref(pr.route_smooth(port1, port2, path_type='straight'))
waypoint_path = pr.path_straight(port1, port2)

qp([D, waypoint_path])
_images/geometry_reference_113_0.png
L path

The L path is useful when two orthogonal ports can be connected with one turn.

[50]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='L1', midpoint=(20,0), width=4, orientation=180)
port2 = D.add_port(name='L2', midpoint=(0, 40), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2,  path_type='L'))
waypoint_path = pr.path_L(port1, port2)

qp([D, waypoint_path])
_images/geometry_reference_115_0.png
U path

The U path is useful when two parrallel ports face each other or the same direction. It requires one argument: length1, which sets the length of the line segment which exits port1.

[51]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='U1', midpoint=(0, 30), width=4, orientation=270)
port2 = D.add_port(name='U2', midpoint=(30, 30), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='U', length1=30))
waypoint_path = pr.path_U(port1, port2, length1=30)


port1 = D.add_port(name='U3', midpoint=(50, 0), width=4, orientation=0)
port2 = D.add_port(name='U4', midpoint=(80, 30), width=4, orientation=180)
D.add_ref(pr.route_smooth(port1, port2,  path_type='U', length1=20))
waypoint_path2 = pr.path_U(port1, port2, length1=20)

qp([D, waypoint_path, waypoint_path2])
_images/geometry_reference_117_0.png
J path

The J path is useful when two orthogonal ports cannot be directly connected with one turn (as in an L path). It requires two arguments: length1 and length2, the lengths of the line segments exiting port1 and port2, respectively.

[52]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='J1', midpoint=(0, 25), width=4, orientation=270)
port2 = D.add_port(name='J2', midpoint=(30, 50), width=4,  orientation=180)
D.add_ref(pr.route_smooth(port1, port2,  path_type='J', length1=25, length2=10))
waypoint_path = pr.path_J(port1, port2, length1=25, length2=10)

port1 = D.add_port(name='J3', midpoint=(70, 15), width=5, orientation=270)
port2 = D.add_port(name='J4', midpoint=(90, 50), width=5,  orientation=180)
D.add_ref(pr.route_smooth(port1, port2, path_type='J', length1=15, length2=30))
waypoint_path2 = pr.path_J(port1, port2, length1=15, length2=30)

qp([D, waypoint_path, waypoint_path2])
_images/geometry_reference_120_0.png
C path

The C path is useful for parrallel ports that face away from each other. It requires three arguments: length1, length2 and left1. length1 and length2 are the lengths of the line segments exiting port1 and port2, respectively. left1 is the length of the segment that turns left from port1. To turn right after exiting port1 instead, left1 can be set to a negative value.

[53]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='C1', midpoint=(0, 35), width=4, orientation=90)
port2 = D.add_port(name='C2', midpoint=(2, 15), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=15, left1=30, length2=15))
waypoint_path = pr.path_C(port1, port2, length1=15, left1=30, length2=15)

port1 = D.add_port(name='C3', midpoint=(30, 25), width=4, orientation=90)
port2 = D.add_port(name='C4', midpoint=(60, 25), width=4, orientation=270)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=25, left1=-15, length2=25))
waypoint_path2 = pr.path_C(port1, port2, length1=25, left1=-15, length2=25)

port1 = D.add_port(name='C5', midpoint=(80, 0), width=4, orientation=0)
port2 = D.add_port(name='C6', midpoint=(105, 0), width=4, orientation=0)
D.add_ref(pr.route_smooth(port1, port2, path_type='C', length1=10, left1=40, length2=10, radius=4))
waypoint_path3 = pr.path_C(port1, port2, length1=10, left1=40,length2=10)

qp([D, waypoint_path, waypoint_path2, waypoint_path3])
_images/geometry_reference_122_0.png
manhattan path

The manhattan path uses straight, L, J, or C paths to route between arbitrary parrallel or orthogonal ports. When using path_manhattan(), the radius argument is required to set the minimum line segment lengths. When accessing manhattan waypoint paths through route_smooth() or route_sharp(), this radius parameter is set automatically based on other route parameters.

[54]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='M1', midpoint=(0,0), width=4, orientation=180)
port2 = D.add_port(name='M2', midpoint=(-30, 30), width=4, orientation=90)
D.add_ref(pr.route_smooth(port1, port2, radius=10,  path_type='manhattan'))
waypoint_path = pr.path_manhattan(port1, port2, radius=10)

qp([D, waypoint_path])
_images/geometry_reference_124_0.png
V path

The V path is useful for ports at odd angles that face a common intersection point.

[55]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='V1', midpoint=(0,50), width=4, orientation=290)
port2 = D.add_port(name='V2', midpoint=(30, 50), width=4, orientation=250)
D.add_ref(pr.route_smooth(port1, port2, path_type='V'))
waypoint_path = pr.path_V(port1, port2)

qp([D, waypoint_path])
_images/geometry_reference_126_0.png
Z path

The Z path is useful for ports at odd angles. It requires two arguments: length1 and length2, the lengths of the line segments exiting port1 and port2, respectively.

[56]:
from phidl import Device, quickplot as qp
import phidl.routing as pr

D = Device()
port1 = D.add_port(name='Z1', midpoint=(40,0), width=4, orientation=190)
port2 = D.add_port(name='Z2', midpoint=(0, 40), width=4, orientation=-10)
D.add_ref(pr.route_smooth(port1, port2, path_type='Z', length1=30, length2=40))
waypoint_path = pr.path_Z(port1, port2, length1=30, length2=40)

port1 = D.add_port(name='Z3', midpoint=(50,0), width=4, orientation=0)
port2 = D.add_port(name='Z4', midpoint=(80, 40), width=4, orientation=180)
D.add_ref(pr.route_smooth(port1, port2, path_type='Z', length1=10, length2=10))
waypoint_path2 = pr.path_Z(port1, port2, length1=10, length2=10)

qp([D, waypoint_path, waypoint_path2])
_images/geometry_reference_128_0.png

Fill tool

In some cases it’s useful to be able to fill empty spaces of your layout with dummy geometries, for example to increase fabrication uniformity or make easy ground planes. PHIDL has a fill tool that uses scikit-image to provide such functionality.

Dummy fill

In this first example, we will create a simple photonic waveguide (e.g. an arc) and fill the empty areas around it with dummy rectangles on layer 4 and layer 3

[57]:
import phidl.geometry as pg
from phidl import Device, quickplot as qp

# Create "waveguide" design
D = Device()
waveguide1 = D << pg.arc(radius=10, width = 2, theta = 135, layer = 0)

# Create fill that avoids the waveguides
fill = pg.fill_rectangle(D = D,
        fill_size= (1.5,1.5),      # Basic cell size of the fill
        avoid_layers=[0],          # Layers that will be avoided
        margin= 2,                 # Keepout distance from shapes
        fill_layers= [4,3],        # Adds fill rectangles to layer 4 and 3
        fill_densities= [0.1,0.5], # Layer 4 filled 10%, layer 3 filled 50%
        # - Note that 10% of (1.5,1.5) area = sqrt(0.1)*(1.5,1.5) = (0.47,0.47)
        fill_inverted=[False,False], # Neither layer is inverted
        bbox= [(-15,-5),(20,18)] )
D << fill
qp(D)
_images/geometry_reference_131_0.png

Ground fill

For the ground fill, we will make several devices which we want to share a common ground. To achieve this, we will create a special “include” layer that will override the avoided layers and force the fill to overlap the grounding pad of our geometry.

To begin, we’ll define a structure with a blue contact pad on the top, and a red grounding pad on layer 50 on the bottom. Let’s then make 3 of them evenly spaced:

[58]:
import phidl.geometry as pg
from phidl import Device, quickplot as qp

def shape_with_ground_pad():
    D = Device()
    s = D << pg.snspd_expanded(layer = 0).rotate(-90)
    contact_pad = D << pg.compass(size = (10,10), layer = 1)
    ground_pad = D << pg.compass(size = (10,10), layer = 50)
    contact_pad.connect('S',s.ports[1])
    ground_pad.connect('N',s.ports[2])
    return D

def three_shapes_with_ground_pad():
    Structures = Device()
    s1 = Structures << shape_with_ground_pad()
    s2 = Structures << shape_with_ground_pad()
    s3 = Structures << shape_with_ground_pad()
    group = s1 + s2 + s3
    group.distribute(direction = 'x', spacing = 10)
    return Structures

qp(three_shapes_with_ground_pad())
_images/geometry_reference_133_0.png

Next, we’ll fill the empty areas (along with the “include” layer) with a solid ground plane

[59]:
Structures = three_shapes_with_ground_pad()
fill = pg.fill_rectangle(Structures,
        fill_size= (1.5,1.5),      # Basic cell size of the fill
        avoid_layers="all",        # Layers that will be avoided
        include_layers=50,
        margin= 3,
        fill_layers= [50],
        fill_densities= [1.0],
        fill_inverted=[False],
        bbox= [(-30,-30),(80,35)] )
Structures << fill
qp(Structures)
_images/geometry_reference_135_0.png

Sometimes, for purposes of fabrication uniformity (or for superconductors, flux trapping), it’s nice to punch “holes” in the ground plane. We can do that–while maintaining a contiguous plane–by changing the fill_inverted argument`:

[60]:
Structures = three_shapes_with_ground_pad()
fill = pg.fill_rectangle(Structures,
        fill_size= (3,3),      # Basic cell size of the fill
        avoid_layers="all",        # Layers that will be avoided
        include_layers=50,
        margin= 3,
        fill_layers= [50],
        fill_densities= [0.2],
        fill_inverted=[True],
        bbox= [(-30,-30),(80,35)] )
Structures << fill
qp(Structures)
_images/geometry_reference_137_0.png

Importing GDS files

pg.import_gds() allows you to easily import external GDSII files. It imports a single cell from the external GDS file and converts it into a PHIDL device.

[61]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.ellipse()
D.write_gds('myoutput.gds')
D = pg.import_gds(filename = 'myoutput.gds', cellname = None, flatten = False)
qp(D) # quickplot the geometry
_images/geometry_reference_140_0.png

LayerSet

The LayerSet class allows you to predefine a collection of layers and specify their properties including: gds layer/datatype, name, and color. It also comes with a handy preview function called pg.preview_layerset()

[62]:
import phidl.geometry as pg
from phidl import quickplot as qp

from phidl import LayerSet
lys = LayerSet()
lys.add_layer('p', color = 'lightblue', gds_layer = 1, gds_datatype = 0)
lys.add_layer('p+', color = 'blue', gds_layer = 2, gds_datatype = 0)
lys.add_layer('p++', color = 'darkblue', gds_layer = 3, gds_datatype = 0)
lys.add_layer('n', color = 'lightgreen', gds_layer = 4, gds_datatype = 0)
lys.add_layer('n+', color = 'green', gds_layer = 4, gds_datatype = 98)
lys.add_layer('n++', color = 'darkgreen', gds_layer = 5, gds_datatype = 99)
D = pg.preview_layerset(lys, size = 100, spacing = 100)
qp(D) # quickplot the geometry
_images/geometry_reference_142_0.png

Useful contact pads / connectors

These functions are common shapes with ports, often used to make contact pads

[64]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.compass(size = (4,2), layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_145_0.png
[65]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.compass_multi(size = (4,2), ports = {'N':3,'S':4}, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_146_0.png
[66]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.flagpole(size = (50,25), stub_size = (4,8), shape = 'p', taper_type = 'fillet', layer = 0)
# taper_type should be None, 'fillet', or 'straight'

qp(D) # quickplot the geometry
_images/geometry_reference_147_0.png
[67]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.straight(size = (4,2), layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_148_0.png
[68]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.connector(midpoint = (0,0), width = 1, orientation = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_149_0.png
[69]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.tee(size = (8,4), stub_size = (1,2), taper_type = 'fillet', layer = 0)
# taper_type should be None, 'fillet', or 'straight'

qp(D) # quickplot the geometry
_images/geometry_reference_150_0.png

Chip / die template

[70]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.basic_die(
              size = (10000, 5000), # Size of die
              street_width = 100,   # Width of corner marks for die-sawing
              street_length = 1000, # Length of corner marks for die-sawing
              die_name = 'chip99',  # Label text
              text_size = 500,      # Label text size
              text_location = 'SW', # Label text compass location e.g. 'S', 'SE', 'SW'
              layer = 0,
              draw_bbox = False,
              bbox_layer = 99,
              )
qp(D) # quickplot the geometry
_images/geometry_reference_152_0.png

Optimal superconducting curves

The following structures are meant to reduce “current crowding” in superconducting thin-film structures (such as superconducting nanowires). They are the result of conformal mapping equations derived in Clem, J. & Berggren, K. “Geometry-dependent critical currents in superconducting nanocircuits.” Phys. Rev. B 84, 1–27 (2011).

[71]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.optimal_hairpin(width = 0.2, pitch = 0.6, length = 10,
    turn_ratio = 4, num_pts = 50, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_154_0.png
[72]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.optimal_step(start_width = 10, end_width = 22, num_pts = 50, width_tol = 1e-3,
                 anticrowding_factor = 1.2, symmetric = False, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_155_0.png
[73]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.optimal_90deg(width = 100.0, num_pts = 15, length_adjust = 1, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_156_0.png
[74]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.snspd(wire_width = 0.2, wire_pitch = 0.6, size = (10,8),
        num_squares = None, turn_ratio = 4,
        terminals_same_side = False, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_157_0.png
[75]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.snspd_expanded(wire_width = 0.3, wire_pitch = 0.6, size = (10,8),
           num_squares = None, connector_width = 1, connector_symmetric = False,
            turn_ratio = 4, terminals_same_side = False, layer = 0)
qp(D) # quickplot the geometry
_images/geometry_reference_158_0.png
[76]:
import phidl.geometry as pg
from phidl import quickplot as qp

D = pg.snspd_candelabra(
    wire_width=0.52,
    wire_pitch=0.56,
    haxis=40,
    vaxis=20,
    equalize_path_lengths=False,
    xwing=False,
    layer=0,
)
qp(D) # quickplot the geometry
_images/geometry_reference_159_0.png

Copying and extracting geometry

[77]:
import phidl.geometry as pg
from phidl import quickplot as qp
from phidl import Device

E = Device()
E.add_ref(pg.ellipse(layer = {0,1}))
#X = pg.ellipse(layer = {0,1})
D = pg.extract(E, layers = [0,1])
qp(D) # quickplot the geometry
_images/geometry_reference_161_0.png
[78]:
import phidl.geometry as pg
from phidl import quickplot as qp

X = pg.ellipse()
D = pg.copy(X)
qp(D) # quickplot the geometry
_images/geometry_reference_162_0.png
[79]:
import phidl.geometry as pg
from phidl import quickplot as qp

X = pg.ellipse()
D = pg.deepcopy(X)
qp(D) # quickplot the geometry
_images/geometry_reference_163_0.png
[80]:
import phidl.geometry as pg
from phidl import quickplot as qp

X = Device()
X << pg.ellipse(layer = 0)
X << pg.ellipse(layer = 1)
D = pg.copy_layer(X, layer = 1, new_layer = 2)
qp(D) # quickplot the geometry
_images/geometry_reference_164_0.png

API Reference

Quickplot

quickplot

phidl.quickplot(items)

Takes a list of devices/references/polygons or single one of those, and plots them. Use set_quickplot_options() to modify the viewer behavior (e.g. displaying ports, creating new windows, etc)

Parameters:items (PHIDL object or list of PHIDL objects) – The item(s) which are to be plotted

Examples

>>> R = pg.rectangle()
>>> quickplot(R)
>>> R = pg.rectangle()
>>> E = pg.ellipse()
>>> quickplot([R, E])

set_quickplot_options

phidl.set_quickplot_options(show_ports=None, show_subports=None, label_aliases=None, new_window=None, blocking=None, zoom_factor=None, interactive_zoom=None)

Sets plotting options for quickplot()

Parameters:
  • show_ports (bool) – Sets whether ports are drawn
  • show_subports (bool) – Sets whether subports (ports that belong to references) are drawn
  • label_aliases (bool) – Sets whether aliases are labeled with a text name
  • new_window (bool) – If True, each call to quickplot() will generate a separate window
  • blocking (bool) – If True, calling quickplot() will pause execution of (“block”) the remainder of the python code until the quickplot() window is closed. If False, the window will be opened and code will continue to run.
  • zoom_factor (float) – Sets the scaling factor when zooming the quickplot window with the mousewheel/trackpad
  • interactive_zoom (bool) – Enables/disables the ability to use mousewheel/trackpad to zoom

Geometry Library

arc

phidl.geometry.arc(radius=10, width=0.5, theta=45, start_angle=0, angle_resolution=2.5, layer=0)

Creates an arc of arclength theta starting at angle start_angle.

Parameters:
  • radius (int or float) – Radius of the arc centerline.
  • width (int or float) – Width of the arc.
  • theta (int or float) – Total angle coverage of the arc.
  • start_angle (int or float) – Starting angle.
  • angle_resolution (int or float) – Resolution of the curve of the arc.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an arc polygon and two ports (1 and 2) on either end.

Notes

Theta = 0 is located along the positive x-axis relative to the center of the arc.

Ports are added to each end of the arc to facilitate connecting those ends to other geometries.

basic_die

phidl.geometry.basic_die(size=(10000, 10000), street_width=100, street_length=1000, die_name='chip99', text_size=100, text_location='SW', layer=0, draw_bbox=True, bbox_layer=99)

Creates a basic chip/die template, with 4 right angle corners marking the boundary of the chip/die and a label with the name of the die.

Parameters:
  • size (array_like[2] of int or float) – x, y dimensions of the die.
  • street_width (int or float) – Width of the corner marks for die-sawing.
  • street_length (int or float) – Length of the corner marks for die-sawing.
  • die_name (str) – Label text.
  • text_size (int or float) – Label text size.
  • text_location ({'NW', 'N', 'NE', 'SW', 'S', 'SE'}) – Label text compass location.
  • layer (int) – Specific layer(s) to put polygon geometry on.
  • draw_bbox (bool) – If true, drawns a bbox around the chip die geometry.
  • bbox_layer (int) – Layer on which bbox is placed if draw_bbox is true.
Returns:

D (Device) – A Device containing a basic die geometry.

bbox

phidl.geometry.bbox(bbox=[(-1, -1), (3, 4)], layer=0)

Creates a bounding box rectangle from coordinates, to allow creation of a rectangle bounding box directly form another shape.

Parameters:
  • bbox (list of tuples of int or float) – Coordinates of the box [(x1, y1), (x2, y2)].
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a single rectangle polygon.

Examples

>>> D = pg.bbox(anothershape.bbox)

boolean

phidl.geometry.boolean(A, B, operation, precision=0.0001, num_divisions=[1, 1], max_points=4000, layer=0)

Performs boolean operations between 2 Device/DeviceReference objects or lists of Devices/DeviceReferences.

Parameters:
  • A (Device(/Reference) or list of Device(/Reference) or Polygon) – Input Devices.
  • B (Device(/Reference) or list of Device(/Reference) or Polygon) – Input Devices.
  • operation ({'not', 'and', 'or', 'xor', 'A-B', 'B-A', 'A+B'}) – Boolean operation to perform.
  • precision (float) – Desired precision for rounding vertex coordinates.
  • num_divisions (array-like[2] of int) – The number of divisions with which the geometry is divided into multiple rectangular regions. This allows for each region to be processed sequentially, which is more computationally efficient.
  • max_points (int) – The maximum number of vertices within the resulting polygon.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a polygon(s) with the boolean operations between the 2 input Devices performed.

Notes

‘A+B’ is equivalent to ‘or’. ‘A-B’ is equivalent to ‘not’. ‘B-A’ is equivalent to ‘not’ with the operands switched.

C

phidl.geometry.C(width=1, size=(10, 20), layer=0)

Generates a ‘C’ geometry with ports on both ends.

Parameters:
  • width (int or float) – Thickness of the line forming the C.
  • size (tuple of int or float) – Lengths of the base + top edges and the height of the C, respectively.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a [-bracket-shaped polygon and two ports (1 and 2) on either end of the [ shape.

circle

phidl.geometry.circle(radius=10, angle_resolution=2.5, layer=0)

Generates a circle geometry.

Parameters:
  • radius (int or float) – Radius of the circle.
  • angle_resolution (int or float) – Resolution of the curve of the ring (# of degrees per point).
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a circle polygon.

compass

phidl.geometry.compass(size=(4, 2), layer=0)

Creates a rectangular contact pad with centered ports on edges of the rectangle (north, south, east, and west).

Parameters:
  • size (array_like[2]) – Dimensions of the rectangular contact pad.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a rectangular contact pad with centered ports.

compass_multi

phidl.geometry.compass_multi(size=(4, 2), ports={'N': 3, 'S': 4}, layer=0)

Creates a rectangular contact pad with multiple ports along the edges rectangle (north, south, east, and west).

Parameters:
  • size (array_like) – Dimensions of the rectangular contact pad.
  • ports (dict) – Number of ports on each edge of the rectangle.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a rectangular contact pad with multiple ports.

connector

phidl.geometry.connector(midpoint=(0, 0), width=1, orientation=0)

Creates a Device which has back-to-back ports.

Parameters:
  • midpoint (array-like) – Coordinates of Device midpoint.
  • width (int or float) – Width of connector on non-port axis.
  • orientation (int or float) – Orientation of the ports.
Returns:

D (Device) – A Device containing a back-to-back port geometry.

copy

phidl.geometry.copy(D)

Copies a Device.

Parameters:D (Device) – Device to be copied.
Returns:Device – Copied Device.

copy_layer

phidl.geometry.copy_layer(D, layer=1, new_layer=2)

Copies a layer within a Device to another layer in the same Device.

Parameters:
  • D (Device) – Device containing layer to be copied.
  • layer (int, array-like[2], or set) – Specific layer(s) to copy.
  • new_layer (int, array-like[2], or set) – Specific layer(s) to put copied layer on.
Returns:

Device – A Device containing the original and copied layers.

cross

phidl.geometry.cross(length=10, width=3, layer=0)

Generates a right-angle cross (+ shape, symmetric) from two rectangles of specified length and width.

Parameters:
  • length (int or float) – Length of the cross from one end to the other.
  • width (int or float) – Width of the arms of the cross.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a cross polygon.

deepcopy

phidl.geometry.deepcopy(D)

Deep copies a Device.

Parameters:D (Device) – Device to be deep copied.
Returns:Device – Deep copied Device.

ellipse

phidl.geometry.ellipse(radii=(10, 5), angle_resolution=2.5, layer=0)

Generates an ellipse geometry.

Parameters:
  • radii (tuple of int or float) – Semimajor (x) and semiminor (y) axis lengths of the ellipse.
  • angle_resolution (int or float) – Resolution of the curve of the ring (# of degrees per point).
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an ellipse polygon.

extract

phidl.geometry.extract(D, layers=[0, 1])

Extracts polygons from a given Device.

Parameters:
  • D (Device) – Device to extract polygons from.
  • layers (array-like[2] or set) – Specific layer(s) to extract polygon geometry from.
Returns:

Device – A Device containing the extracted polygons.

fill_rectangle

phidl.geometry.fill_rectangle(D, fill_size=(40, 10), avoid_layers='all', include_layers=None, margin=100, fill_layers=(0, 1, 3), fill_densities=(0.5, 0.25, 0.7), fill_inverted=None, bbox=None)

Creates a rectangular fill pattern and fills all empty areas in the input device D

Parameters:
  • D (Device) – Device to be filled
  • fill_size (array-like[2]) – Rectangular size of the fill element
  • avoid_layers ('all' or list of layers) – Layers to be avoided (not filled) in D
  • include_layers – Layers to be included (filled) in D, supercedes avoid_layers
  • margin (int or float) – Margin spacing around avoided areas – fill will not come within margin of the geometry in D
  • fill_layers (list of layers) – Defines the fill pattern layers
  • fill_densities (float between 0 and 1) – Defines the fill pattern density (1.0 == fully filled)
  • fill_inverted (bool) – Inverts the fill pattern
  • bbox (array-like[2][2]) – Limit the fill pattern to the area defined by this bounding box
Returns:

F (Device)

flagpole

phidl.geometry.flagpole(size=(4, 2), stub_size=(2, 1), shape='p', taper_type='straight', layer=0)

Creates a flagpole geometry of one of four configurations, all involving a vertical central column and a outward-pointing flag.

Parameters:
  • size (array-like) – (width, height) of the flag.
  • stub_size (array-like) – (width, height) of the pole stub.
  • shape ({'p', 'q', 'b', 'd'}) – Configuration of the flagpole, where the curved portion of the letters represents the flag and the straight portion the pole.
  • taper_type ({'straight', 'fillet', None}) – Type of taper between the bottom corner of the stub on the side of the flag and the corner of the flag closest to the stub.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a flagpole geometry.

geometry_to_ports

phidl.geometry.geometry_to_ports(device, layer=0)

Converts geometry representing ports over the whole Device hierarchy into Port objects.

Parameters:
  • device (Device) – Device containing geometry representing ports to convert.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on (the special port record layer)
Returns:

temp_device (Device) – A Device with all geometry representing ports converted to Ports and removed.

Notes

Does not mutate the device in the argument. Returns a new one lacking all port geometry (incl. labels)

grating

phidl.geometry.grating(*args, **kwargs)

Simple grating structure for photonics

Parameters:
  • num_periods (int) – Number of gratings.
  • period (int or float) – Distance between gratings.
  • fill_factor (int or float) – Thickness of the gratings.
  • width_grating (int or float) – Width of the gratings.
  • length_taper (int or float) – Length of the taper section.
  • width (int or float) – Width of the end of the taper section.
  • partial_etch (bool) – If True, makes an untapered, partially-etched grating.
Returns:

G (Device) – A Device containing a fiber grating geometry.

grid

phidl.geometry.grid(device_list, spacing=(5, 10), separation=True, shape=None, align_x='x', align_y='y', edge_x='x', edge_y='ymax')

Places the devices in the device_list (1D or 2D) on a grid.

Parameters:
  • device_list (array-like[N] of Device) – Devices to be placed onto a grid.
  • spacing (int, float, or array-like[2] of int or float) – Spacing between adjacent elements on the grid, can be a tuple for different distances in width and height (x,y).
  • separation (bool) – If True, guarantees elements are speparated with a fixed spacing between; if False, elements are spaced evenly along a grid.
  • shape (array-like[2]) – x, y shape of the grid (as if np.reshape() were run with shape[::-1]). If no shape is given and the list is 1D, the output is as if np.reshape were run with (-1, size of list).
  • align_x ({'x', 'xmin', 'xmax'}) – Which edge to perform the x (column) alignment along
  • align_y ({'y', 'ymin', 'ymax'}) – Which edge to perform the y (row) alignment along
  • edge_x ({'x', 'xmin', 'xmax'}) – Which edge to perform the x (column) distribution along (unused if separation == True)
  • edge_y ({'y', 'ymin', 'ymax'}) – Which edge to perform the y (row) distribution along (unused if separation == True)
Returns:

device_matrix (Device) – A Device containing all the Devices in device_list in a grid.

gridsweep

phidl.geometry.gridsweep(function, param_x={'width': [1, 5, 6, 7]}, param_y={'length': [1.1, 2, 70]}, param_defaults={}, param_override={}, spacing=(50, 100), separation=True, align_x='x', align_y='y', edge_x='x', edge_y='ymin', label_layer=255)

Creates a parameter sweep of devices and places them on a grid with labels for each device. For instance, given a function defined like myshape(width, height, offset, layer) and the following parameters:

param_x = {‘width’ : [4, 5, 6] }, param_y = {‘height’ : [7, 9],

‘layer’ : [1, 2, 3] },

param_defaults = {‘scale’ : 1}

gridsweep() produce a grid of devices with the following layout/parameters:
(w=4, h=7, s=1, l=1) (w=5, h=7, s=1, l=1) (w=6, h=7, s=1, l=1) (w=4, h=7, s=1, l=2) (w=5, h=7, s=1, l=2) (w=6, h=7, s=1, l=2) (w=4, h=7, s=1, l=3) (w=5, h=7, s=1, l=3) (w=6, h=7, s=1, l=3) (w=4, h=9, s=1, l=1) (w=5, h=9, s=1, l=1) (w=6, h=9, s=1, l=1) (w=4, h=9, s=1, l=2) (w=5, h=9, s=1, l=2) (w=6, h=9, s=1, l=2) (w=4, h=9, s=1, l=3) (w=5, h=9, s=1, l=3) (w=6, h=9, s=1, l=3)
Parameters:
  • function (function that produces a Device) – The function which will be used to create the individual devices in the grid. Must only return a single Device (e.g. any of the functions in pg.geometry)
  • param_x (dict) – A dictionary of one or more parameters to sweep in the x-direction
  • param_y (dict) – A dictionary of one or more parameters to sweep in the y-direction
  • param_defaults (dict) – Default parameters to pass to every device in the grid
  • param_override (dict) – Parameters that will override param_defaults, equivalent to changing param_defaults (useful )
  • spacing (int, float, or array-like[2] of int or float) – Spacing between adjacent elements on the grid, can be a tuple for different distances in width and height (x,y).
  • separation (bool) – If True, guarantees elements are separated with a fixed spacing between; if False, elements are spaced evenly along a grid.
  • shape (array-like[2]) – x, y shape of the grid (see np.reshape). If no shape is given and the list is 1D, the output is as if np.reshape were run with (1, -1).
  • align_x ({'x', 'xmin', 'xmax'}) – Which edge to perform the x (column) alignment along
  • align_y ({'y', 'ymin', 'ymax'}) – Which edge to perform the y (row) alignment along
  • edge_x ({'x', 'xmin', 'xmax'}) – Which edge to perform the x (column) distribution along (unused if separation == True)
  • edge_y ({'y', 'ymin', 'ymax'}) – Which edge to perform the y (row) distribution along (unused if separation == True)
  • label_layer (None or layer) – If not None, will place a label that describes the parameters on the device
Returns:

device_matrix (Device) – A Device containing all the Devices in device_list in a grid.

hecken_taper

phidl.geometry.hecken_taper(*args, **kwargs)

Creates a Hecken-tapered microstrip.

Parameters:
  • length (int or float) – Length of the microstrip.
  • B (int or float) – Controls the intensity of the taper.
  • dielectric_thickness (int or float) – Thickness of the substrate.
  • eps_r (int or float) – Dielectric constant of the substrate.
  • Lk_per_sq (float) – Kinetic inductance per square of the microstrip.
  • Z1 (int, float, or None) – Impedance of the left side region of the microstrip.
  • Z2 (int, float, or None) – Impedance of the right side region of the microstrip.
  • width1 (int, float, or None) – Width of the left side of the microstrip.
  • width2 (int, float, or None) – Width of the right side of the microstrip.
  • num_pts (int) – Number of points comprising the curve of the entire microstrip.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a Hecken-tapered microstrip.

import_gds

phidl.geometry.import_gds(filename, cellname=None, flatten=False)

Imports a GDS file and returns a Device with all the corresponding geometry

Parameters:
  • filename (str) – Path or name of file to be imported
  • cellname (str or None) – Name of the cell that will be returned as a Device. If None, will automatically select the topmost cell
  • flatten (bool) – Whether to flatten the imported geometry, removing all cell heirarchy
Returns:

Device – A PHIDL Device with all the geometry/labels/etc imported from the GDS file

inset

invert

phidl.geometry.invert(elements, border=10, precision=0.0001, num_divisions=[1, 1], max_points=4000, layer=0)

Creates an inverted version of the input shapes with an additional border around the edges.

Parameters:
  • elements (Device(/Reference), list of Device(/Reference), or Polygon) – A Device containing the polygons to invert.
  • border (int or float) – Size of the border around the inverted shape (border value is the distance from the edges of the boundary box defining the inverted shape to the border, and is applied to all 4 sides of the shape).
  • precision (float) – Desired precision for rounding vertex coordinates.
  • num_divisions (array-like[2] of int) – The number of divisions with which the geometry is divided into multiple rectangular regions. This allows for each region to be processed sequentially, which is more computationally efficient.
  • max_points (int) – The maximum number of vertices within the resulting polygon.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing the inverted version of the input shape(s) and the corresponding border(s).

L

phidl.geometry.L(width=1, size=(10, 20), layer=0)

Generates an ‘L’ geometry with ports on both ends.

Parameters:
  • width (int or float) – Thickness of the line forming the L.
  • size (tuple of int or float) – Lengths of the base and height of the L, respectively.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an L-shaped polygon and two ports (1 and 2) on either end of the L.

litho_calipers

phidl.geometry.litho_calipers(notch_size=[2, 5], notch_spacing=2, num_notches=11, offset_per_notch=0.1, row_spacing=0, layer1=1, layer2=2)

Creates a vernier caliper structure for lithography alignment tests. Vernier structure is made horizontally.

Parameters:
  • notch_size (array-like[2] of int or flaot) – x, y size of the notches.
  • notch_spacing (int or float) – Spacing between notches on the control structure.
  • num_notches (int) – Number of notches on one side of the structure (total number of notches is 2*num_notches + 1).
  • offset_per_notch (int or float) – The amount of horizontal offset to apply to the notch spacing per notch on the non-control structure.
  • row_spacing (int or float) – The amount of vertical space between the control and non-control structures.
  • layer1 (int, array-like[2], or set) – Specific layer(s) to put the control geometry on.
  • layer2 (int, array-like[2], or set) – Specific layer(s) to put the non-control geometry on.
Returns:

Device – A Device containing the caliper structures.

litho_star

phidl.geometry.litho_star(num_lines=20, line_width=2, diameter=200, layer=0)

Creates a circular-star shape from lines, used as a lithographic resolution test pattern.

Parameters:
  • num_lines (int) – Number of lines in the circular-star shape.
  • line_width (int or float) – Thickness of star spike lines.
  • diameter (int or float) – Diameter of the circular-star shape (total length of each star spike).
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a line-based circular-star shape.

litho_steps

phidl.geometry.litho_steps(line_widths=[1, 2, 4, 8, 16], line_spacing=10, height=100, layer=0)

Produces a positive + negative tone linewidth test, used for lithography resolution test patterning.

Parameters:
  • line_widths (array-like[N] of int or float) – Widths of the steps (positive side).
  • line_spacing (int or float) – Space between each step (negative side).
  • height (int or float) – Height of the steps.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing the lithographic linewidth resolution test geometry.

litho_ruler

phidl.geometry.litho_ruler(height=2, width=0.5, spacing=1.2, scale=[3, 1, 1, 1, 1, 2, 1, 1, 1, 1], num_marks=21, layer=0)

Creates a ruler structure for lithographic measurement with marks of varying scales to allow for easy reading by eye.

Parameters:
  • height (float) – Height of the ruling marks.
  • width (float) – Width of the ruling marks.
  • spacing (float) – Center-to-center spacing of the ruling marks
  • scale (array-like) – Height scale pattern of marks
  • num_marks (int) – Total number of marks to generate
  • num_marks – Total number of marks to generate
  • layer (int, array-like[2], or set) – Specific layer(s) to put the ruler geometry on.
Returns:

Device – A Device containing the ruler structure

meander_taper

phidl.geometry.meander_taper(*args, **kwargs)

Takes in an array of x-positions and a array of widths (corresponding to each x-position) and creates a meander. Typically used for creating meandered tapers

Parameters:
  • x_taper (array-like[N]) – The x-coordinates of the data points, must be increasing.
  • w_taper (array-like[N]) – The y-coordinates of the data points, same length as x_taper.
  • meander_length (int or float) – Length of each section of the meander
  • spacing_factor (int or float) – Multiplicative spacing factor between adjacent meanders
  • min_spacing (int or float) – Minimum spacing between adjacent meanders
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device)

offset

phidl.geometry.offset(elements, distance=0.1, join_first=True, precision=0.0001, num_divisions=[1, 1], join='miter', tolerance=2, max_points=4000, layer=0)

Shrinks or expands a polygon or set of polygons.

Parameters:
  • elements (Device(/Reference), list of Device(/Reference), or Polygon) – Polygons to offset or Device containing polygons to offset.
  • distance (int or float) – Distance to offset polygons. Positive values expand, negative shrink.
  • precision (float) – Desired precision for rounding vertex coordinates.
  • num_divisions (array-like[2] of int) – The number of divisions with which the geometry is divided into multiple rectangular regions. This allows for each region to be processed sequentially, which is more computationally efficient.
  • join ({'miter', 'bevel', 'round'}) – Type of join used to create the offset polygon.
  • tolerance (int or float) – For miter joints, this number must be at least 2 and it represents the maximal distance in multiples of offset between new vertices and their original position before beveling to avoid spikes at acute joints. For round joints, it indicates the curvature resolution in number of points per full circle.
  • max_points (int) – The maximum number of vertices within the resulting polygon.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a polygon(s) with the specified offset applied.

optimal_90deg

phidl.geometry.optimal_90deg(width=100.0, num_pts=15, length_adjust=1, layer=0)

Creates an optimally-rounded 90 degree bend that is sharp on the outer corner.

Parameters:
  • width (int or float) – Width of the ports on either side of the bend.
  • num_pts (int) – The number of points comprising the curved section of the bend.
  • length_adjust (int or float) – Adjusts the length of the non-curved portion of the bend.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded 90 degree bend.

Notes

Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1–27.

optimal_hairpin

phidl.geometry.optimal_hairpin(*args, **kwargs)

Creates an optimally-rounded hairpin geometry, with a 180 degree turn on the right end of the polygon connected to two prongs extending towards ports on the left end.

Parameters:
  • width (int or float) – Width of the hairpin leads.
  • pitch (int or float) – Distance between the two hairpin leads. Must be greater than width.
  • length (int or float) – Length of the hairpin from the connectors to the opposite end of the curve.
  • turn_ratio (int or float) – Specifies how much of the hairpin is dedicated to the 180 degree turn. A turn_ratio of 10 will result in 20% of the hairpin being comprised of the turn.
  • num_pts (int) – Number of points constituting the 180 degree turn.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded hairpin geometry.

Notes

Hairpin pitch must be greater than width.

Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1–27.

optimal_step

phidl.geometry.optimal_step(*args, **kwargs)

Creates an optimally-rounded step geometry.

Parameters:
  • start_width (int or float) – Width of the connector on the left end of the step.
  • end_width (int or float) – Width of the connector on the right end of the step.
  • num_pts (int) – The number of points comprising the entire step geometry.
  • width_tol (float) – Point at which to terminate the calculation of the optimal step
  • anticrowding_factor (int or float) – Factor to reduce current crowding by elongating the structure and reducing the curvature
  • symmetric (bool) – If True, adds a mirrored copy of the step across the x-axis to the geometry and adjusts the width of the ports.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded step.

Notes

Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1–27.

outline

phidl.geometry.outline(elements, distance=1, precision=0.0001, num_divisions=[1, 1], join='miter', tolerance=2, join_first=True, max_points=4000, open_ports=False, layer=0)

Creates an outline around all the polygons passed in the elements argument. elements may be a Device, Polygon, or list of Devices.

Parameters:
  • elements (Device(/Reference), list of Device(/Reference), or Polygon) – Polygons to outline or Device containing polygons to outline.
  • distance (int or float) – Distance to offset polygons. Positive values expand, negative shrink.
  • precision (float) – Desired precision for rounding vertex coordinates.
  • num_divisions (array-like[2] of int) – The number of divisions with which the geometry is divided into multiple rectangular regions. This allows for each region to be processed sequentially, which is more computationally efficient.
  • join ({'miter', 'bevel', 'round'}) – Type of join used to create the offset polygon.
  • tolerance (int or float) – For miter joints, this number must be at least 2 and it represents the maximal distance in multiples of offset between new vertices and their original position before beveling to avoid spikes at acute joints. For round joints, it indicates the curvature resolution in number of points per full circle.
  • join_first (bool) – Join all paths before offsetting to avoid unnecessary joins in adjacent polygon sides.
  • max_points (int) – The maximum number of vertices within the resulting polygon.
  • open_ports (bool or float) – If not False, holes will be cut in the outline such that the Ports are not covered. If True, the holes will have the same width as the Ports. If a float, the holes will be be widened by that value (useful for fully clearing the outline around the Ports for positive-tone processes
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.)
Returns:

D (Device) – A Device containing the outlined polygon(s).

packer

phidl.geometry.packer(D_list, spacing=10, aspect_ratio=(1, 1), max_size=(None, None), sort_by_area=True, density=1.1, precision=0.01, verbose=False)

Packs geometries together into rectangular bins.

Parameters:
  • D_list (array-like of Devices) – Input Devices to be packed.
  • spacing (int or float) – The minimum distance between adjacent shapes.
  • aspect_ratio (array-like) – The (width, height) ratio of the rectangular bin.
  • max_size (array-like) – Limits the size into which the shapes will be packed.
  • sort_by_area (bool) – If true, pre-sorts the shapes by area.
  • density (int or float) – Density of packing. Values closer to 1 pack tighter but require more computation.
  • precision (float) – Desired precision for rounding vertex coordinates.
  • verbose (bool) – Whether to display results of packing attempts
Returns:

D_packed_list (Device or list of Devices) – A Device or list of Devices containing all the packed rectangular bins generated.

Notes

If a max-size is specified, the function will create as many bins as necessary to pack all the geometries and then return a list of the filled-bin Devices.

polygon_ports

phidl.geometry.polygon_ports(xpts=[-1, -1, 0, 0], ypts=[0, 1, 1, 0], layer=0)

Creates a polygon with ports on all edges.

Parameters:
  • xpts (array-like) – x-coordinate values of the polygon vertices.
  • ypts (array-like) – y-coordinate values of the polygon vertices.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

P (Device) – A Device containing a polygon with ports on all edges.

ports_to_geometry

phidl.geometry.ports_to_geometry(device, layer=0)

Converts Port objects over the whole Device hierarchy to geometry and labels.

Parameters:
  • device (Device) – Device containing Port objects to convert.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on (the special port record layer).
Returns:

temp_device (Device) – A Device with all Ports converted to geometry/labels and removed.

preview_layerset

phidl.geometry.preview_layerset(ls, size=100, spacing=100)

Generates a preview Device with representations of all the layers, used for previewing LayerSet color schemes in quickplot or saved .gds files.

Parameters:
  • ls (LayerSet) – Set of layers to preview color schemes.
  • size (int or float) – Resizing factor for the preview Device.
  • spacing (int or float) – The space between each layer representation.
Returns:

D (Device) – A Device containing a representation of all the layers in the input LayerSet.

racetrack_gradual

phidl.geometry.racetrack_gradual(width=0.3, R=5, N=3, layer=0)

Creates a gradual racetrack bent geometry.

Parameters:
  • width (int or float) – Width of the track.
  • R (int or float) – Radius of the track at its most curved point.
  • N (int or float) – Radius of the track at its least curved point.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a gradual racetrack bent geometry.

ramp

phidl.geometry.ramp(length=10, width1=5, width2=8, layer=0)

Creates a ramp geometry.

Parameters:
  • length (int or float) – Length of the ramp section.
  • width1 (int or float) – Width of the start of the ramp section.
  • width2 (int, float, or None) – Width of the end of the ramp section (if width2 is None, width2 becomes the same as width1).
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a ramp geometry.

rectangle

phidl.geometry.rectangle(size=(4, 2), layer=0)

Generates a rectangle geometry.

Parameters:
  • size (tuple of int or float) – Width and height of rectangle.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a single rectangle polygon.

ring

phidl.geometry.ring(radius=10, width=0.5, angle_resolution=2.5, layer=0)

Generates a ring geometry.

Parameters:
  • radius (int or float) – Radius of the ring centerline
  • width (int or float) – Width of the ring.
  • angle_resolution (int or float) – Resolution of the curve of the ring (# of degrees per point).
  • layer – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a ring polygon.

Notes

The ring is formed by taking the radius out to the specified value, and then constructing the thickness by dividing the width in half and adding that value to either side of the radius.

The angle_resolution alters the precision of the curve of the ring. Larger values yield lower resolution.

snspd

phidl.geometry.snspd(*args, **kwargs)

Creates an optimally-rounded SNSPD.

Parameters:
  • width (int or float) – Width of the wire.
  • pitch (int or float) – Distance between two adjacent wires. Must be greater than width.
  • size (None or array-like[2] of int or float) – (width, height) of the rectangle formed by the outer boundary of the SNSPD. Must be none if num_squares is specified.
  • num_squares (int or None) – Total number of squares inside the SNSPD length. Must be none if size is specified.
  • turn_ratio (int or float) – Specifies how much of the SNSPD width is dedicated to the 180 degree turn. A turn_ratio of 10 will result in 20% of the width being comprised of the turn.
  • terminals_same_side (bool) – If True, both ports will be located on the same side of the SNSPD.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded SNSPD.

snspd_expanded

phidl.geometry.snspd_expanded(wire_width=0.2, wire_pitch=0.6, size=(10, 8), num_squares=None, connector_width=1, connector_symmetric=False, turn_ratio=4, terminals_same_side=False, layer=0)

Creates an optimally-rounded SNSPD with wires coming out of it that expand.

Parameters:
  • width (int or float) – Width of the wire.
  • pitch (int or float) – Distance between two adjacent wires. Must be greater than width.
  • size (None or array-like[2] of int or float) – (width, height) of the rectangle formed by the outer boundary of the SNSPD. Must be none if num_squares is specified.
  • num_squares (int or None) – Total number of squares inside the SNSPD length. Must be none if size is specified.
  • connector_width (int or float) – Width of the connectors.
  • connector_symmetric (bool) – If True, mirrors the connectors across their flat edge and adds them to the connector geometry.
  • turn_ratio (int or float) – Specifies how much of the SNSPD width is dedicated to the 180 degree turn. A turn_ratio of 10 will result in 20% of the width being comprised of the turn.
  • terminals_same_side (bool) – If True, both ports will be located on the same side of the SNSPD.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded SNSPD with expanding input/ output wires.

snspd_candelabra

phidl.geometry.snspd_candelabra(wire_width=0.52, wire_pitch=0.56, haxis=90, vaxis=50, equalize_path_lengths=False, xwing=False, layer=0)

Creates an optimally-rounded SNSPD with low current crowding and arbtitrarily-high fill factor as described by Reddy et. al., APL Photonics 7, 051302 (2022) https://doi.org/10.1063/5.0088007

Parameters:
  • wire_width (int or float) – Width of the wire.
  • wire_pitch (int or float) – Distance between two adjacent wires. Must be greater than width.
  • haxis (int or float) – Length of horizontal diagonal of the rhomboidal active area. The parameter haxis is prioritized over vaxis.
  • vaxis (int or float) – Length of vertical diagonal of the rhomboidal active area.
  • equalize_path_lengths (bool) – If True, adds wire segments to hairpin bends to equalize path lengths from center to center for all parallel wires in active area.
  • xwing (bool) – If True, replaces 90-degree bends with 135-degree bends.
  • layer (int) – Specific layer to put polygon geometry on.
Returns:

D (Device) – A Device containing an optimally-rounded SNSPD with minimized current crowding for any fill factor.

straight

phidl.geometry.straight(size=(4, 2), layer=0)

Generates a rectangular wire geometry with ports on the length edges.

Parameters:
  • size (tuple of int or float) – The length and width of the rectangle.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a rectangle polygon and two ports (1 and 2) on either end.

Notes

Ports are included on both sides of the length edge (i.e. size[0]) of the geometry.

taper

phidl.geometry.taper(length=10, width1=5, width2=None, port=None, layer=0)

Creates a tapered trapezoid/rectangle geometry.

Parameters:
  • length (int or float) – Length of the shape.
  • width1 (int, float, or None) – Width of end 1 of the taper section (width is equal to the port width if Port is not None and width1 is None).
  • width2 (int, float, or None) – Width of end 2 of the taper section (width is equal to the port width if Port is not None and width2 is None).
  • port (Port or None) – Port with which to match the width of the taper ends.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a taper geometry.

tee

phidl.geometry.tee(size=(4, 2), stub_size=(2, 1), taper_type=None, layer=0)

Creates a T-shaped geometry.

Parameters:
  • size (array-like) – (width, height) of the horizontal top part of the T shape.
  • stub_size (array-like) – (width, height) of the vertical stub part of the T shape.
  • taper_type ({'straight', 'fillet', None}) – If specified, the type of taper between the bottom corners of the stub and the bottom corners of the T shape.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a T-shaped geometry.

test_comb

phidl.geometry.test_comb(pad_size=(200, 200), wire_width=1, wire_gap=3, comb_layer=0, overlap_zigzag_layer=1, comb_pad_layer=2, comb_gnd_layer=3, overlap_pad_layer=4)

Overlap comb test structure for checking whether two layers are electrically isolated

Parameters:
  • pad_size (array-like) – x and y dimensions of the comb pads.
  • wire_width (int or float) – Width of the test comb teeth.
  • wire_gap (int or float) – Size of the gap between comb teeth.
  • comb_layer (int, array-like[2], or set) – Specific layer(s) to put comb geometry on.
  • overlap_zigzag_layer (int, array-like[2], or set) – Specific layer(s) to put overlap zigzag geometry on.
  • comb_pad_layer (int, array-like[2], or set) – Specific layer(s) to put comb pads on.
  • comb_gnd_layer (int, array-like[2], or set) – Specific layer(s) to put the comb ground on.
  • overlap_pad_layer (int, array-like[2], or set) – Specific layer(s) to put overlap pads on.
Returns:

CI (Device) – A Device containing a test comb geometry.

Notes

Call comb_insulation_test_structure() with any of the parameters shown below which you’d like to change. You only need to supply the parameters which you intend on changing You can alternatively call it with no parameters and it will take all the default values shown below.

Ex::
comb_insulation_test_structure(pad_size=(175,175), wire_width=2, wire_gap=5)
  • or -::
    comb_insulation_test_structure()

test_ic

phidl.geometry.test_ic(wire_widths=[0.25, 0.5, 1, 2, 4], wire_widths_wide=[0.75, 1.5, 3, 4, 6], pad_size=(200, 200), pad_gap=75, wire_layer=0, pad_layer=1, gnd_layer=1)

Critical current test structure for superconducting wires.

Parameters:
  • wire_widths (array-like[N]) – The widths of the thinnest parts of the test wires.
  • wire_widths_wide (array-like[N]) – The widths of the thickest parts of the test wires.
  • pad_size (array-like[2] of int or float) – (width, height) of the pads.
  • pad_gap (int or float) – Distance between the pads and the ground plane.
  • wire_layer (int) – Specific layer(s) to put the wires on.
  • pad_layer (int) – Specific layer(s) to put the pads on.
  • gnd_layer (int or None) – Specific layer(s) to put the ground plane on.
Returns:

Device – A Device containing the critical-current test structure.

test_res

phidl.geometry.test_res(pad_size=[50, 50], num_squares=1000, width=1, res_layer=0, pad_layer=1, gnd_layer=2)

Creates an efficient resonator structure for a wafer layout.

Parameters:
  • pad_size (array-like[2] of int or float) – (width, height) of the two matched impedance pads in microns.
  • num_squares (int or float) – Number of squares comprising the resonator wire.
  • width (int or float) – The width of the squares in microns.
  • res_layer (int) – Specific layer(s) to put the resonator structure on.
  • pad_layer (int or None) – Specific layer(s) to put the pads on.
  • gnd_layer (int or None) – Specific layer(s) to put the ground plane on.
Returns:

P (Device) – A Device containing an efficient resonator structure.

test_via

phidl.geometry.test_via(num_vias=100, wire_width=10, via_width=15, via_spacing=40, pad_size=(300, 300), min_pad_spacing=0, pad_layer=0, wiring1_layer=1, wiring2_layer=2, via_layer=3)

Via chain test structure

Parameters:
  • num_vias (int) – The total number of requested vias (must be an even number).
  • wire_width (int or float) – The width of the wires.
  • via_width (int or float) – Diameter of the vias.
  • via_spacing (int or float) – Distance between vias.
  • pad_size (array-like[2]) – (width, height) of the pads.
  • min_pad_spacing (int or float) – Defines the minimum distance between the two pads.
  • pad_layer (int) – Specific layer to put the pads on.
  • wiring1_layer (int) – Specific layer to put the top wiring on.
  • wiring2_layer (int) – Specific layer to put the bottom wiring on.
  • via_layer (int) – Specific layer to put the vias on.
Returns:

  • VR (Device) – A Device containing the test via structures.
  • Usage
  • —–
  • Call via_route_test_structure() by indicating the number of vias you want
  • drawn. You can also change the other parameters however if you do not
  • specifiy a value for a parameter it will just use the default value
  • Ex:: – via_route_test_structure(num_vias=54)
  • - or -:: – via_route_test_structure(num_vias=12, pad_size=(100,100),wire_width=8)
  • ex (via_route(54, min_pad_spacing=300))

text

phidl.geometry.text(text='abcd', size=10, justify='left', layer=0, font='DEPLOF')

Creates geometries of text

Parameters:
  • text (str) – Text string to be written.
  • size (int or float) – Size of the text
  • justify ({'left', 'right', 'center'}) – Justification of the text.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
  • font (str) – Font face to use. Default DEPLOF does not require additional libraries, otherwise freetype will be used to load fonts. Font can be given either by name (e.g. “Times New Roman”), or by file path. OTF or TTF fonts are supported.
Returns:

t (Device) – A Device containing the text geometry.

turn

phidl.geometry.turn(port, radius=10, angle=270, angle_resolution=2.5, layer=0)

Starting from a port, creates an arc which connects to the specified port on one end.

Parameters:
  • port (Port) – Port to anchor arc to.
  • radius (int or float) – Radius of the arc centerline.
  • angle (int or float) – Total angle coverage of the arc.
  • angle_resolution (int or float) – Resolution of the curve of the arc.
  • layer (int, array-like[2], or set) – Specific layer(s) to put the polygon geometry on.
Returns:

D (Device) – A Device containing an arc polygon and two ports (1 and 2) on either end.

Notes

Angle = 0 is located along the positive x-axis relative to the center of the arc. Ports are added to each end of the arc to facilitate connecting those ends to other geometries. Port 2 is aligned to connect to the specified port.

union

phidl.geometry.union(D, by_layer=False, precision=0.0001, join_first=True, max_points=4000, layer=0)

Performs the union of all polygons within a Device.

Parameters:
  • D (Device(/Reference)) – A Device containing polygons to perform a union on.
  • by_Layer (bool) – If true, performs the union operation layer-wise so each layer can be individually combined.
  • precision (float) – Desired precision for rounding vertex coordinates.
  • join_first (bool) – Join all paths before offsetting to avoid unnecessary joins in adjacent polygon sides.
  • max_points (int) – The maximum number of vertices within the resulting polygon.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

U (Device) – A Device containing the union of the polygons within the input Device.

xor_diff

phidl.geometry.xor_diff(A, B, precision=0.0001)

Given two Devices A and B, performs the layer-by-layer XOR difference between A and B and returns polygons representing the differences between A and B.

Parameters:
  • A (Device(/Reference) or list of Device(/Reference)) – A Device containing a polygon(s).
  • B (Device(/Reference) or list of Device(/Reference)) – A Device containing a polygon(s).
  • precision (float) – Desired precision for rounding vertex coordinates.
Returns:

D (Device) – A Device containing a polygon(s) defined by the XOR difference result between A and B.

ytron_round

phidl.geometry.ytron_round(rho=1, arm_lengths=(500, 300), source_length=500, arm_widths=(200, 200), theta=2.5, theta_resolution=10, layer=0)

Ytron structure for superconducting nanowires

McCaughan, A. N., Abebe, N. S., Zhao, Q.-Y. & Berggren, K. K. Using Geometry To Sense Current. Nano Lett. 16, 7626–7631 (2016). http://dx.doi.org/10.1021/acs.nanolett.6b03593

Parameters:
  • rho (int or float) – Radius of curvature of ytron intersection point
  • arm_lengths (array-like[2] of int or float) – Lengths of the left and right arms of the yTron, respectively.
  • source_length (int or float) – Length of the source of the yTron.
  • arm_widths (array-like[2] of int or float) – Widths of the left and right arms of the yTron, respectively.
  • theta (int or float) – Angle between the two yTron arms.
  • theta_resolution (int or float) – Angle resolution for curvature of ytron intersection point
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
Returns:

D (Device) – A Device containing a yTron geometry.

device_lru_cache

class phidl.geometry.device_lru_cache(fn)

Bases: object

Least-recently-used (LRU) cache for Devices

Path Library

arc

phidl.path.arc(radius=10, angle=90, num_pts=720)

Create a circular arc Path

Parameters:
  • radius (int or float) – Radius of arc
  • angle (int or float) – Total angle of arc
  • num_pts (int) – Number of points used per 360 degrees
Returns:

Path – A Path object with the specified arc

euler

phidl.path.euler(radius=3, angle=90, p=1.0, use_eff=False, num_pts=720)

Create an Euler bend (also known as “racetrack” or “clothoid” curves) that adiabatically transitions from straight to curved. By default, radius corresponds to the minimum radius of curvature of the bend. However, if use_eff is set to True, radius corresponds to the effective radius of curvature (making the curve a drop-in replacement for an arc). If p < 1.0, will create a “partial euler” curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394

Parameters:
  • radius (int or float) – Minimum radius of curvature
  • angle (int or float) – Total angle of curve
  • p (float) – Proportion of curve that is an Euler curve
  • use_eff (bool) – If False: radius corresponds to minimum radius of curvature of the bend If True: The curve will be scaled such that the endpoints match an arc with parameters radius and angle
  • num_pts (int) – Number of points used per 360 degrees
Returns:

Path – A Path object with the specified Euler curve

smooth

phidl.path.smooth(points=[(20, 0), (40, 0), (80, 40), (80, 10), (100, 10)], radius=4, corner_fun=<function euler>, **kwargs)

Create a smooth path from a series of waypoints. Corners will be rounded using corner_fun and any additional key word arguments (for example, use_eff = True when corner_fun = pp.euler)

Parameters:
  • points (array-like[N][2] or Path) – List of waypoints for the path to follow
  • radius (int or float) – Radius of curvature, this argument will be passed to corner_fun
  • corner_fun (function) – The function that controls how the corners are rounded. Typically either arc() or euler()
  • **kwargs (dict) – Extra keyword arguments that will be passed to corner_fun
Returns:

Path – A Path object with the specified smoothed path.

straight

phidl.path.straight(length=5, num_pts=100)

Creates a straight Path

Parameters:
  • length (int or float) – Total length of straight path
  • num_pts (int) – Number of points along Path
Returns:

Path – A Path object with the specified straight section

spiral

phidl.path.spiral(num_turns=5, gap=1, inner_gap=2, num_pts=10000)

Creates a spiral geometry consisting of two oddly-symmetric semi-circular arcs in the centre and two Archimedean (involute) spiral arms extending outward from the ends of both arcs.

Parameters:
  • num_turns (int or float) – The number of turns in the spiral. Must be greater than 1. A full spiral rotation counts as 1 turn, and the center arcs will together always be 0.5 turn.
  • gap (int or float) – The distance between any point on one arm of the spiral and a point with the same angular coordinate on an adjacent arm.
  • inner_gap (int or float) – The inner size of the spiral, equal to twice the chord length of the centre arcs.
  • num_pts (int) – The number of points in the entire spiral. The actual number of points will be slightly different than the specified value, as they are dynamically allocated using the path lengths of the spiral.
Returns:

Path – A Path object forming a spiral

Notes

num_turns usage (x is any whole number):
  • num_turns = x.0: Output arm will be extended 0.5 turn to be on

the same side as the input. - num_turns < x.5: Input arm will be extended by the fractional amount. - num_turns = x.5: Both arms will be the same length and the input and output will be on opposite sides. - num_turns > x.5: Output arm will be extended by the fractional amount.

Routing Library

route_sharp

phidl.routing.route_sharp(port1, port2, width=None, path_type='manhattan', manual_path=None, layer=nan, **kwargs)

Convenience function that routes a path between ports and immediately extrudes the path to create polygons. Has several waypoint path type options. Equivalent to e.g.

>>> P = pr.path_manhattan(port1, port2, radius)
>>> D = P.extrude(width)
Parameters:
  • port2 (port1,) – Ports to route between.
  • width (None, int, float, array-like[2], or CrossSection) –

    If None, the route linearly tapers between the widths the ports If set to a single number (e.g. width=1.7): makes a fixed-width route If set to a 2-element array (e.g. width=[1.8,2.5]): makes a route

    whose width varies linearly from width[0] to width[1]

    If set to a CrossSection: uses the CrossSection parameters for the route

  • path_type ({'manhattan', 'L', 'U', 'J', 'C', 'V', 'Z', 'straight', 'manual'}) –
    Method of waypoint path creation. Should be one of
    • ’manhattan’ - automatic manhattan routing
      (see path_manhattan() ).
    • ’L’ - L-shaped path for orthogonal ports that can be directly
      connected (see path_L() ).
    • ’U’ - U-shaped path for parrallel or facing ports
      (see path_U() ).
    • ’J’ - J-shaped path for orthogonal ports that cannot be
      directly connected (see path_J() ).
    • ’C’ - C-shaped path for ports that face away from each
      other (see path_C() ).
    • ’Z’ - Z-shaped path with three segments for ports at any
      angles (see path_Z() ).
    • ’V’ - V-shaped path with two segments for ports at any
      angles (see path_V() ).
    • ’straight’ - straight path for ports that face each other
      see path_straight() ).
    • ’manual’ - use an explicit waypoint path provided
      in manual_path.
  • manual_path (array-like[N][2] or Path) – Waypoint path for creating a manual route
  • layer (int or array-like[2]) – Layer to put route on. layer=0 is used by default.
  • **kwargs – Keyword arguments passed to the waypoint path function.
Returns:

D (Device) – A Device containing the route and two ports (1 and 2) on either end.

route_smooth

phidl.routing.route_smooth(port1, port2, radius=5, width=None, path_type='manhattan', manual_path=None, smooth_options={'corner_fun': <function euler>, 'use_eff': True}, layer=nan, **kwargs)

Convenience function that routes a path between ports using pp.smooth(), then immediately extrudes the path to create polygons. Has several waypoint path type options. Equivalent to e.g.

>>> pts = pr.path_manhattan(port1, port2, radius)
>>> P = pp.smooth(pts, radius)
>>> D = P.extrude(width)
Parameters:
  • port2 (port1,) – Ports to route between.
  • radius (int or float) – Bend radius passed to pp.smooth
  • width (None, int, float, array-like[2], or CrossSection) –

    If None, the route linearly tapers between the widths the ports If set to a single number (e.g. width=1.7): makes a fixed-width route If set to a 2-element array (e.g. width=[1.8,2.5]): makes a route

    whose width varies linearly from width[0] to width[1]

    If set to a CrossSection: uses the CrossSection parameters for the route

  • path_type ({'manhattan', 'L', 'U', 'J', 'C', 'V', 'Z', 'straight', 'manual'}) –
    Method of waypoint path creation. Should be one of
    • ’manhattan’ - automatic manhattan routing
      (see path_manhattan() ).
    • ’L’ - L-shaped path for orthogonal ports that can be directly
      connected (see path_L() ).
    • ’U’ - U-shaped path for parrallel or facing ports
      (see path_U() ).
    • ’J’ - J-shaped path for orthogonal ports that cannot be
      directly connected (see path_J() ).
    • ’C’ - C-shaped path for ports that face away from each
      other (see path_C() ).
    • ’Z’ - Z-shaped path with three segments for ports at any
      angles (see path_Z() ).
    • ’V’ - V-shaped path with two segments for ports at any
      angles (see path_V() ).
    • ’straight’ - straight path for ports that face each other
      see path_straight() ).
    • ’manual’ - use an explicit waypoint path provided
      in manual_path.
  • manual_path (array-like[N][2] or Path) – Waypoint path for creating a manual route
  • smooth_options (dict) – Keyword arguments passed to pp.smooth
  • layer (int or array-like[2]) – Layer to put route on. layer=0 is used by default.
  • **kwargs – Keyword arguments passed to the waypoint path function.
Returns:

D (Device) – A Device containing the route and two ports (1 and 2) on either end.

route_quad

phidl.routing.route_quad(port1, port2, width1=None, width2=None, layer=0)

Routes a basic quadrilateral polygon directly between two ports.

Parameters:
  • port2 (port1,) – Ports to route between.
  • width2 (width1,) – Width of quadrilateral at ports. If None, uses port widths.
  • layer (int or array-like[2]) – Layer to put the route on.
Returns:

D (Device) – A Device containing the route and two ports (1 and 2) on either end.

route_xy

phidl.routing.route_xy(port1, port2, directions='xxyx', width=None, layer=nan)

Routes a path in x and y directions (manhattan) from one point (or Port) to another. The directions string determines the order of the x/y steps. Example: directions = ‘xyx’ will travel

1/2 the distance in x from p1 to p2 The whole distance in y from p1 to p2 1/2 of the distance in x from p1 to p2
Parameters:
  • port2 (port1,) – Points to route between.
  • directions (string of {'x','y'} characters) – Directions the Path will be routed along
Returns:

D (Device) – A Device containing the route and two ports (1 and 2) on either end.

path_manhattan

phidl.routing.path_manhattan(port1, port2, radius)

Return waypoint path between port1 and port2 using manhattan routing. Routing is performed using straight, L, U, J, or C waypoint path as needed. Ports must face orthogonal or parallel directions.

Parameters:
  • port2 (port1,) – Ports to route between.
  • radius (float or int) – Bend radius for 90 degree bend.
Returns:

points (Path) – Waypoints for the route path to follow.

path_straight

phidl.routing.path_straight(port1, port2)

Return waypoint path between port1 and port2 in a straight line. Useful when ports point directly at each other.

Parameters:port2 (port1,) – Ports to route between.
Returns:points (array[2][2]) – Waypoints for the route path to follow.

path_L

phidl.routing.path_L(port1, port2)

Return waypoint path between port1 and port2 in an L shape. Useful when orthogonal ports can be directly connected with one turn.

Parameters:port2 (port1,) – Ports to route between.
Returns:points (Path) – Waypoints for the route path to follow.

path_U

phidl.routing.path_U(port1, port2, length1=200)

Return waypoint path between port1 and port2 in a U shape. Useful when ports face the same direction or toward each other.

Parameters:
  • port2 (port1,) – Ports to route between.
  • length1 (int or float) – Length of segment exiting port1. Should be larger than bend radius.
Returns:

points (Path) – Waypoints for the route path to follow.

path_J

phidl.routing.path_J(port1, port2, length1=200, length2=200)

Return waypoint path between port1 and port2 in a J shape. Useful when orthogonal ports cannot be connected directly with an L shape.

Parameters:
  • port2 (port1,) – Ports to route between.
  • length1 (int or float) – Length of segment exiting port1. Should be larger than bend radius.
  • length2 (int or float) – Length of segment exiting port2. Should be larger than bend radius.
Returns:

points (Path) – Waypoints for the route path to follow.

path_C

phidl.routing.path_C(port1, port2, length1=100, left1=100, length2=100)

Return waypoint path between port1 and port2 in a C shape. Useful when ports are parrallel and face away from each other.

Parameters:
  • port2 (port1,) – Ports to route between.
  • length1 (int or float) – Length of route segment coming out of port1. Should be at larger than bend radius.
  • left1 (int or float) – Length of route segment that turns left (or right if negative) from port1. Should be larger than twice the bend radius.
  • length2 (int or float) – Length of route segment coming out of port2. Should be larger than bend radius.
Returns:

points (Path) – Waypoints for the route path to follow.

path_V

phidl.routing.path_V(port1, port2)

Return waypoint path between port1 and port2 in a V shape. Useful when ports point to a single connecting point

Parameters:port2 (port1,) – Ports to route between.
Returns:points (Path) – Waypoints for the route path to follow.

path_Z

phidl.routing.path_Z(port1, port2, length1=100, length2=100)

Return waypoint path between port1 and port2 in a Z shape. Ports can have any relative orientation.

Parameters:
  • port2 (port1,) – Ports to route between.
  • length1 (int or float) – Length of route segment coming out of port1.
  • length2 (int or float) – Length of route segment coming out of port2.
Returns:

points (Path) – Waypoints for the route path to follow.

Layout Classes

CellArray

class phidl.device_layout.CellArray(device, columns, rows, spacing, origin=(0, 0), rotation=0, magnification=None, x_reflection=False)

Bases: gdspy.library.CellArray, phidl.device_layout._GeometryHelper

Multiple references to an existing cell in an array format.

Parameters:
  • device (Device) – The referenced Device.
  • columns (int) – Number of columns in the array.
  • rows (int) – Number of rows in the array.
  • spacing (array-like[2] of int or float) – Distances between adjacent columns and adjacent rows.
  • origin (array-like[2] of int or float) – Position where the cell is inserted.
  • rotation (int or float) – Angle of rotation of the reference (in degrees).
  • magnification (int or float) – Magnification factor for the reference.
  • x_reflection (bool) – If True, the reference is reflected parallel to the x direction before being rotated.
area(by_spec=False)

Calculate the total area of the cell array with the magnification factor included.

Parameters:by_spec (bool) – If True, the return value is a dictionary with the areas of each individual pair (layer, datatype).
Returns:out (number, dictionary) – Area of this cell.
get_bounding_box()

Calculate the bounding box for this reference.

Returns:out (Numpy array[2, 2] or None) – Bounding box of this cell [[x_min, y_min], [x_max, y_max]], or None if the cell is empty.
get_labels(depth=None, set_transform=False)

Return the list of labels created by this reference.

Parameters:
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve labels from.
  • set_transform (bool) – If True, labels will include the transformations from the reference.
Returns:

out (list of Label) – List containing the labels in this cell and its references.

get_paths(depth=None)

Return the list of paths created by this reference.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve paths from.
Returns:out (list of FlexPath or RobustPath) – List containing the paths in this cell and its references.
get_polygons(by_spec=False, depth=None)

Return the list of polygons created by this reference.

Parameters:
  • by_spec (bool or tuple) – If True, the return value is a dictionary with the polygons of each individual pair (layer, datatype). If set to a tuple of (layer, datatype), only polygons with that specification are returned.
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be name of the referenced cell.
Returns:

out (list of array-like[N][2] or dictionary) – List containing the coordinates of the vertices of each polygon, or dictionary with the list of polygons (if by_spec is True).

Note

Instances of FlexPath and RobustPath are also included in the result by computing their polygonal boundary.

get_polygonsets(depth=None)

Return the list of polygons created by this reference.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons from.
Returns:out (list of PolygonSet) – List containing the polygons in this cell and its references.
mirror(p1=(0, 1), p2=(0, 0))

Mirrors a CellArray across the line formed between the two specified points.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves the CellArray from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this CellArray.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of the move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
rotate(angle=45, center=(0, 0))

Rotates all elements in the CellArray around the specified centerpoint.

Parameters:
  • angle (int or float) – Angle to rotate the CellArray in degrees.
  • center (array-like[2], Port, or None) – Midpoint of the CellArray.
to_gds(outfile, multiplier)

Convert this object to a GDSII element.

Parameters:
  • outfile (open file) – Output to write the GDSII.
  • multiplier (number) – A number that multiplies all dimensions written in the GDSII element.
to_svg(outfile, scaling, precision)

Write an SVG fragment representation of this object.

Parameters:
  • outfile (open file) – Output to write the SVG representation.
  • scaling (number) – Scaling factor for the geometry.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.
translate(dx, dy)

Translate this reference.

Parameters:
  • dx (number) – Distance to move in the x-direction.
  • dy (number) – Distance to move in the y-direction.
Returns:

out (CellArray) – This object.

bbox

Returns the bounding box of the CellArray.

center

Returns the center of the bounding box.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

CrossSection

class phidl.device_layout.CrossSection

Bases: object

The CrossSection object for extruding a Path. To be used in combination with a Path to create a Device.

Parameters:path (array-like[N][2], Path, or list of Paths) – Points or Paths to append() initially
add(width=1, offset=0, layer=0, ports=(None, None), name=None)

Adds a cross-sectional element to the CrossSection. If ports are specified, when creating a Device with the extrude() command there be have Ports at the ends.

Parameters:
  • width (float) – Width of the segment
  • offset (float) – Offset of the segment (positive values = right hand side)
  • layer (int, tuple of int, or set of int) – The polygon layer to put the segment on
  • ports (array-like[2] of str, int, or None) – If not None, specifies the names for the ports at the ends of the cross-sectional element
  • name (str, int, or None) – Name of the cross-sectional element for later access
copy()

Creates a copy of the CrosSection.

Returns:CrossSection – A copy of the CrossSection
extrude(path, simplify=None)

Combines the 1D CrossSection with a 1D Path to form 2D polygons.

Parameters:
Returns:

Device – A Device with polygons added that correspond to the extrusion of the Path with the CrossSection

Device

class phidl.device_layout.Device(*args, **kwargs)

Bases: gdspy.library.Cell, phidl.device_layout._GeometryHelper

The basic object that holds polygons, labels, and ports in PHIDL

absorb(reference)

Flattens and absorbs polygons from an underlying DeviceReference into the Device, destroying the reference in the process but keeping the polygon geometry.

Parameters:reference (DeviceReference) – DeviceReference to be absorbed into the Device.
add(element)

Add a new element or list of elements to this cell.

Parameters:element (PolygonSet, CellReference, CellArray or iterable) – The element or iterable of elements to be inserted in this cell.
Returns:out (Cell) – This cell.
add_array(device, columns=2, rows=2, spacing=(100, 100), alias=None)

Creates a CellArray reference to a Device.

Parameters:
  • device (Device) – The referenced Device.
  • columns (int) – Number of columns in the array.
  • rows (int) – Number of rows in the array.
  • spacing (array-like[2] of int or float) – Distances between adjacent columns and adjacent rows.
  • alias (str or None) – Alias of the referenced Device.
Returns:

a (CellArray) – A CellArray containing references to the input Device.

add_label(text='hello', position=(0, 0), magnification=None, rotation=None, anchor='o', layer=255)

Adds a Label to the Device.

Parameters:
  • text (str) – Label text.
  • position (array-like[2]) – x-, y-coordinates of the Label location.
  • magnification (int, float, or None) – Magnification factor for the Label text.
  • rotation (int, float, or None) – Angle rotation of the Label text.
  • anchor ({'n', 'e', 's', 'w', 'o', 'ne', 'nw', ..}) – Position of the anchor relative to the text.
  • layer (int, array-like[2], or set) – Specific layer(s) to put Label on.
add_polygon(points, layer=nan)

Adds a Polygon to the Device.

Parameters:
  • points (array-like[N][2]) – Coordinates of the vertices of the Polygon.
  • layer (int, array-like[2], or set) – Specific layer(s) to put polygon geometry on.
add_port(name=None, midpoint=(0, 0), width=1, orientation=45, port=None)

Adds a Port to the Device.

Parameters:
  • name (str) – Name of the Port object.
  • midpoint (array-like[2] of int or float) – Midpoint of the Port location.
  • width (int or float) – Width of the Port.
  • orientation (int or float) – Orientation (rotation) of the Port.
  • port (Port or None) – A Port if the added Port is a copy of an existing Port.

Notes

Can be called to copy an existing port like add_port(port = existing_port) or to create a new port add_port(myname, mymidpoint, mywidth, myorientation). Can also be called to copy an existing port with a new name like add_port(port = existing_port, name = new_name)

add_ref(device, alias=None)

Takes a Device and adds it as a DeviceReference to the current Device.

Parameters:
  • device (Device) – Device to be added as a DeviceReference.
  • alias (str) – Alias of the Device.
Returns:

d (DeviceReference) – A DeviceReference that is added to the current Device.

align(elements='all', alignment='ymax')

Align elements in the Device

Parameters:
  • elements (array-like of PHIDL objects, or 'all') – Elements in the Device to align.
  • alignment ({'x', 'y', 'xmin', 'xmax', 'ymin', 'ymax'}) – Which edge to align along (e.g. ‘ymax’ will move the elements such that all of their topmost points are aligned)
area(by_spec=False)

Calculate the total area of the elements on this cell, including cell references and arrays.

Parameters:by_spec (bool) – If True, the return value is a dictionary with the areas of each individual pair (layer, datatype).
Returns:out (number, dictionary) – Area of this cell.
copy(name, deep_copy=False, translation=None, rotation=None, scale=None, x_reflection=False)

Create a copy of this cell.

Parameters:
  • name (string) – The name of the cell.
  • deep_copy (bool) – If False, the new cell will contain only references to the existing elements. If True, copies of all elements are also created. If any transformation is performed, this argument is automatically set to True.
  • translation (Numpy array[2]) – Amount to translate the cell contents.
  • rotation (number) – Rotation angle (in radians).
  • scale (number) – Scaling factor.
  • x_reflection (bool) – Reflect the geometry accros the x axis.
Returns:

out (Cell) – The new copy of this cell.

distribute(elements='all', direction='x', spacing=100, separation=True, edge='center')

Distributes the specified elements in the Device.

Parameters:
  • elements (array-like of PHIDL objects or 'all') – Elements to distribute.
  • direction ({'x', 'y'}) – Direction of distribution; either a line in the x-direction or y-direction.
  • spacing (int or float) – Distance between elements.
  • separation (bool) – If True, guarantees elements are speparated with a fixed spacing between; if False, elements are spaced evenly along a grid.
  • edge ({'x', 'xmin', 'xmax', 'y', 'ymin', 'ymax'}) – Which edge to perform the distribution along (unused if separation == True)
flatten(single_layer=None)

Flattens the heirarchy of the Device such that there are no longer any references to other Devices. All polygons and labels from underlying references are copied and placed in the top-level Device. If single_layer is specified, all polygons are moved to that layer.

Parameters:single_layer (None, int, tuple of int, or set of int) – If not None, all polygons are moved to the specified
get_bounding_box()

Calculate the bounding box for this cell.

Returns:out (Numpy array[2, 2] or None) – Bounding box of this cell [[x_min, y_min], [x_max, y_max]], or None if the cell is empty.
get_datatypes()

Return the set of datatypes in this cell.

Returns:out (set) – Set of the datatypes used in this cell.
get_dependencies(recursive=False)

Return a set of the cells included in this cell as references.

Parameters:recursive (bool) – If True returns cascading dependencies.
Returns:out (set of Cell) – Set of the cells referenced by this cell.
get_info()

Gathers the .info dictionaries from every sub-Device and returns them in a list.

Parameters:depth (int or None) – If not None, defines from how many reference levels to retrieve Ports from.
Returns:list of dictionaries – List of the “.info” property dictionaries from all sub-Devices
get_labels(depth=None, set_transform=False)

Return a list with a copy of the labels in this cell.

Parameters:
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve labels from.
  • set_transform (bool) – If True, labels will include the transformations from the references they are from.
Returns:

out (list of Label) – List containing the labels in this cell and its references.

get_layers()

Return the set of layers in this cell.

Returns:out (set) – Set of the layers used in this cell.
get_paths(depth=None)

Return a list with a copy of the paths in this cell.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve paths from.
Returns:out (list of FlexPath or RobustPath) – List containing the paths in this cell and its references.
get_polygons(by_spec=False, depth=None)

Return a list of polygons in this cell.

Parameters:
  • by_spec (bool or tuple) – If True, the return value is a dictionary with the polygons of each individual pair (layer, datatype), which are used as keys. If set to a tuple of (layer, datatype), only polygons with that specification are returned.
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be the name of this cell.
Returns:

out (list of array-like[N][2] or dictionary) – List containing the coordinates of the vertices of each polygon, or dictionary with with the list of polygons (if by_spec is True).

Note

Instances of FlexPath and RobustPath are also included in the result by computing their polygonal boundary.

get_polygonsets(depth=None)

Return a list with a copy of the polygons in this cell.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons from.
Returns:out (list of PolygonSet) – List containing the polygons in this cell and its references.
get_ports(depth=None)

Returns copies of all the ports of the Device, rotated and translated so that they’re in their top-level position. The Ports returned are copies of the originals, but each copy has the same uid as the original so that they can be traced back to the original if needed.

Parameters:depth (int or None) – If not None, defines from how many reference levels to retrieve Ports from.
Returns:port_list (list of Port) – List of all Ports in the Device.
get_svg_classes()

Return the set of classes for the SVG representation of this cell.

Returns:out0, out1 (sets of 2-tuples) – Sets of (layer, datatype) and (layer, texttype) used in this cell.
get_texttypes()

Return the set of texttypes in this cell.

Returns:out (set) – Set of the texttypes used in this cell.
hash_geometry(precision=0.0001)

Computes an SHA1 hash of the geometry in the Device. For each layer, each polygon is individually hashed and then the polygon hashes are sorted, to ensure the hash stays constant regardless of the ordering the polygons. Similarly, the layers are sorted by (layer, datatype)

Parameters:precision (float) – Roudning precision for the the objects in the Device. For instance, a precision of 1e-2 will round a point at (0.124, 1.748) to (0.12, 1.75)
Returns:str – Hash result in the form of an SHA1 hex digest string

Notes

Algorithm:

hash(
    hash(First layer information: [layer1, datatype1]),
    hash(Polygon 1 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
    hash(Polygon 2 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
    hash(Polygon 3 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
    hash(Second layer information: [layer2, datatype2]),
    hash(Polygon 1 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
    hash(Polygon 2 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3)] ),
)
mirror(p1=(0, 1), p2=(0, 0))

Mirrors a Device across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves elements of the Device from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this Device.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of the move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
remap_layers(layermap={}, include_labels=True)

Moves all polygons in the Device from one layer to another according to the layermap argument.

Parameters:
  • layermap (dict) – Dictionary of values in format {layer_from : layer_to}
  • include_labels (bool) – Selects whether to move Labels along with polygons
remove(items)

Removes items from a Device, which can include Ports, PolygonSets, CellReferences, and Labels.

Parameters:items (array-like[N]) – Items to be removed from the Device.
remove_labels(test)

Remove labels from this cell.

The function or callable test is called for each label in the cell. If its return value evaluates to True, the corresponding label is removed from the cell.

Parameters:test (callable) – Test function to query whether a label should be removed. The function is called with the label as the only argument.
Returns:out (Cell) – This cell.

Examples

Remove labels in layer 1:

>>> cell.remove_labels(lambda lbl: lbl.layer == 1)
remove_layers(layers=(), include_labels=True, invert_selection=False)

Removes layers from a Device.

Parameters:
  • layers (int, array-like[2], or set) – Specific layer(s) to remove.
  • include_labels (bool) – If True, keeps the labels corresponding to the input layers.
  • invert_selection (bool) – If True, removes all layers except those specified.
remove_paths(test)

Remove paths from this cell.

The function or callable test is called for each FlexPath or RobustPath in the cell. If its return value evaluates to True, the corresponding label is removed from the cell.

Parameters:test (callable) – Test function to query whether a path should be removed. The function is called with the path as the only argument.
Returns:out (Cell) – This cell.
remove_polygons(test)

Remove polygons from this cell.

The function or callable test is called for each polygon in the cell. If its return value evaluates to True, the corresponding polygon is removed from the cell.

Parameters:test (callable) – Test function to query whether a polygon should be removed. The function is called with arguments: (points, layer, datatype)
Returns:out (Cell) – This cell.

Examples

Remove polygons in layer 1:

>>> cell.remove_polygons(lambda pts, layer, datatype:
...                      layer == 1)

Remove polygons with negative x coordinates:

>>> cell.remove_polygons(lambda pts, layer, datatype:
...                      any(pts[:, 0] < 0))
rotate(angle=45, center=(0, 0))

Rotates all Polygons in the Device around the specified center point.

Parameters:
  • angle (int or float) – Angle to rotate the Device in degrees.
  • center (array-like[2] or None) – Midpoint of the Device.
to_gds(outfile, multiplier, timestamp=None)

Convert this cell to a GDSII structure.

Parameters:
  • outfile (open file) – Output to write the GDSII.
  • multiplier (number) – A number that multiplies all dimensions written in the GDSII structure.
  • timestamp (datetime object) – Sets the GDSII timestamp. If None, the current time is used.
to_svg(outfile, scaling, precision, attributes)

Write an SVG fragment representation of this object.

Parameters:
  • outfile (open file) – Output to write the SVG representation.
  • scaling (number) – Scaling factor for the geometry.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.
  • attributes (string) – Additional attributes to set for the cell group.
write_gds(filename, unit=1e-06, precision=1e-09, auto_rename=True, max_cellname_length=28, cellname='toplevel')

Writes a Device to a GDS file.

Parameters:
  • filename (str or file) – The GDS file to write to.
  • unit (int or float) – Unit size for the objects in the library (in meters).
  • precision (float) – Precision for the dimensions of the objects in the library (in meters).
  • auto_rename (bool) – If True, fixes any duplicate cell names.
  • max_cellname_length (int or None) – If given, and if auto_rename is True, enforces a limit on the length of the fixed duplicate cellnames.
  • cellname (str) – Name of the top-level cell in the saved GDS
write_svg(outfile, scaling=10, style=None, fontstyle=None, background='#222', pad='5%', precision=None)

Export this cell to an SVG file.

The dimensions actually written on the GDSII file will be the dimensions of the objects created times the ratio unit/precision. For example, if a circle with radius 1.5 is created and we set GdsLibrary.unit to 1.0e-6 (1 um) and GdsLibrary.precision to 1.0e-9` (1 nm), the radius of the circle will be 1.5 um and the GDSII file will contain the dimension 1500 nm.

Parameters:
  • outfile (file, string or Path) – The file (or path) where the GDSII stream will be written. It must be opened for writing operations in binary format.
  • scaling (number) – Scaling factor for the geometry.
  • style (dict or None) – Dictionary indexed by (layer, datatype) tuples. Entries must be dictionaries with CSS key-value pairs for the presentation attributes of the geometry in that layer and datatype.
  • fontstyle (dict or None) – Dictionary indexed by (layer, texttype) tuples. Entries must be dictionaries with CSS key-value pairs for the presentation attributes of the labels in that layer and texttype.
  • background (string or None) – String specifying the background color. If None, no background is inserted.
  • pad (number or string) – Background margin around the cell bounding box. It can be specified as a percentage of the width or height, whichever is the largest.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.

Examples

>>> cell = gdspy.Cell('MAIN')
>>> cell.add(gdspy.Rectangle((0, 0), (10, 10), layer=1))
>>> # Define fill and stroke for layer 1 and datatype 0
>>> mystyle = {(1, 0): {'fill': '#CC00FF',
                        'stroke': 'black'}}
>>> cell.write_svg('main.svg', style=mystyle)
bbox

Returns the bounding box of the Device.

center

Returns the center of the bounding box.

layers

Returns a set of the Layers in the Device.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

DeviceReference

class phidl.device_layout.DeviceReference(device, origin=(0, 0), rotation=0, magnification=None, x_reflection=False)

Bases: gdspy.library.CellReference, phidl.device_layout._GeometryHelper

Simple reference to an existing Device.

Parameters:
  • device (Device) – The referenced Device.
  • origin (array-like[2] of int or float) – Position where the Device is inserted.
  • rotation (int or float) – Angle of rotation of the reference (in degrees)
  • magnification (int or float) – Magnification factor for the reference.
  • x_reflection (bool) – If True, the reference is reflected parallel to the x-direction before being rotated.
area(by_spec=False)

Calculate the total area of the referenced cell with the magnification factor included.

Parameters:by_spec (bool) – If True, the return value is a dictionary with the areas of each individual pair (layer, datatype).
Returns:out (number, dictionary) – Area of this cell.
connect(port, destination, overlap=0)

Moves and rotates this object such that the the Port specified by port is connected (aligned and adjacent) with the Port specified by destination

Parameters:
  • port (str or Port) –
  • destination (array-like[2]) –
  • overlap (int or float) –
get_bounding_box()

Calculate the bounding box for this reference.

Returns:out (Numpy array[2, 2] or None) – Bounding box of this cell [[x_min, y_min], [x_max, y_max]], or None if the cell is empty.
get_labels(depth=None, set_transform=False)

Return the list of labels created by this reference.

Parameters:
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve labels from.
  • set_transform (bool) – If True, labels will include the transformations from the reference.
Returns:

out (list of Label) – List containing the labels in this cell and its references.

get_paths(depth=None)

Return the list of paths created by this reference.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve paths from.
Returns:out (list of FlexPath or RobustPath) – List containing the paths in this cell and its references.
get_polygons(by_spec=False, depth=None)

Return the list of polygons created by this reference.

Parameters:
  • by_spec (bool or tuple) – If True, the return value is a dictionary with the polygons of each individual pair (layer, datatype). If set to a tuple of (layer, datatype), only polygons with that specification are returned.
  • depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be the name of the referenced cell.
Returns:

out (list of array-like[N][2] or dictionary) – List containing the coordinates of the vertices of each polygon, or dictionary with the list of polygons (if by_spec is True).

Note

Instances of FlexPath and RobustPath are also included in the result by computing their polygonal boundary.

get_polygonsets(depth=None)

Return the list of polygons created by this reference.

Parameters:depth (integer or None) – If not None, defines from how many reference levels to retrieve polygons from.
Returns:out (list of PolygonSet) – List containing the polygons in this cell and its references.
mirror(p1=(0, 1), p2=(0, 0))

Mirrors a DeviceReference across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves the DeviceReference from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this DeviceReference.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
rotate(angle=45, center=(0, 0))

Rotates all Polygons in the DeviceReference around the specified centerpoint.

Parameters:
  • angle (int or float) – Angle to rotate the DeviceReference in degrees.
  • center (array-like[2] or None) – Midpoint of the DeviceReference.
to_gds(outfile, multiplier)

Convert this object to a GDSII element.

Parameters:
  • outfile (open file) – Output to write the GDSII.
  • multiplier (number) – A number that multiplies all dimensions written in the GDSII element.
to_svg(outfile, scaling, precision)

Write an SVG fragment representation of this object.

Parameters:
  • outfile (open file) – Output to write the SVG representation.
  • scaling (number) – Scaling factor for the geometry.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.
translate(dx, dy)

Translate this reference.

Parameters:
  • dx (number) – Distance to move in the x-direction.
  • dy (number) – Distance to move in the y-direction.
Returns:

out (CellReference) – This object.

bbox

Returns the bounding box of the DeviceReference.

center

Returns the center of the bounding box.

info

Returns information about the properties of the reference’s parent.

ports

This property allows you to access myref.ports, and receive a copy of the ports dict which is correctly rotated and translated.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

Group

class phidl.device_layout.Group(*args)

Bases: phidl.device_layout._GeometryHelper

Groups objects together so they can be manipulated as though they were a single object (move/rotate/mirror).

add(element)

Adds an element to the Group.

Parameters:element (Device, DeviceReference, Port, Polygon, CellArray, Label, or Group) – Element to add.
align(alignment='ymax')

Aligns the elements in the Group.

Parameters:alignment ({'x', 'y', 'xmin', 'xmax', 'ymin', 'ymax'}) – Which edge to align along (e.g. ‘ymax’ will align move the elements such that all of their topmost points are aligned)
distribute(direction='x', spacing=100, separation=True, edge='center')

Distributes the elements in the Group.

Parameters:
  • direction ({'x', 'y'}) – Direction of distribution; either a line in the x-direction or y-direction.
  • spacing (int or float) – Distance between elements.
  • separation (bool) – If True, guarantees elements are speparated with a fixed spacing between; if False, elements are spaced evenly along a grid.
  • edge ({'x', 'xmin', 'xmax', 'y', 'ymin', 'ymax'}) – Which edge to perform the distribution along (unused if separation == True)
mirror(p1=(0, 1), p2=(0, 0))

Mirrors a Group across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves the Group from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this Group.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of the move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
rotate(angle=45, center=(0, 0))

Rotates all elements in a Group around the specified centerpoint.

Parameters:
  • angle (int or float) – Angle to rotate the Group in degrees.
  • center (array-like[2] or None) – Midpoint of the Group.
bbox

Returns the bounding boxes of the Group.

center

Returns the center of the bounding box.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

Label

class phidl.device_layout.Label(*args, **kwargs)

Bases: gdspy.label.Label, phidl.device_layout._GeometryHelper

Text that can be used to label parts of the geometry or display messages. The text does not create additional geometry, it’s meant for display and labeling purposes only.

mirror(p1=(0, 1), p2=(0, 0))

Mirrors a Label across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves the Label from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this Label.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of the move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
rotate(angle=45, center=(0, 0))

Rotates Label around the specified centerpoint.

Parameters:
  • angle (int or float) – Angle to rotate the Label in degrees.
  • center (array-like[2] or None) – Midpoint of the Label.
to_gds(outfile, multiplier)

Convert this label to a GDSII structure.

Parameters:
  • outfile (open file) – Output to write the GDSII.
  • multiplier (number) – A number that multiplies all dimensions written in the GDSII structure.
to_svg(outfile, scaling, precision)

Write an SVG fragment representation of this object.

Parameters:
  • outfile (open file) – Output to write the SVG representation.
  • scaling (number) – Scaling factor for the geometry.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.
translate(dx, dy)

Translate this label.

Parameters:
  • dx (number) – Distance to move in the x-direction
  • dy (number) – Distance to move in the y-direction
Returns:

out (Label) – This object.

Examples

>>> text = gdspy.Label((0, 0), (10, 20))
>>> text = text.translate(2, 0)
>>> myCell.add(text)
bbox

Returns the bounding box of the Label.

center

Returns the center of the bounding box.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

Layer

class phidl.device_layout.Layer(gds_layer=0, gds_datatype=0, name='unnamed', description=None, inverted=False, color=None, alpha=0.6, dither=None)

Bases: object

Layer object.

Parameters:
  • gds_layer (int) – GDSII Layer number.
  • gds_datatype (int) – GDSII datatype.
  • name (str) – Name of the Layer.
  • color (str) – Hex code of color for the Layer.
  • alpha (int or float) – Alpha parameter (opacity) for the Layer.
  • dither (str) – KLayout dither parameter (texture) for the Layer (only used in phidl.utilities.write_lyp)

LayerSet

class phidl.device_layout.LayerSet

Bases: object

Set of layer objects.

add_layer(name='unnamed', gds_layer=0, gds_datatype=0, description=None, color=None, inverted=False, alpha=0.6, dither=None)

Adds a layer to an existing LayerSet object.

Parameters:
  • name (str) – Name of the Layer.
  • gds_layer (int) – GDSII Layer number.
  • gds_datatype (int) – GDSII datatype.
  • description (str) – Layer description.
  • color (str) – Hex code of color for the Layer.
  • inverted (bool) – If true, inverts the Layer.
  • alpha (int or float) – Alpha parameter (opacity) for the Layer, value must be between 0.0 and 1.0.
  • dither (str) – KLayout dither style (only used in phidl.utilities.write_lyp() )

Path

class phidl.device_layout.Path(path=None)

Bases: phidl.device_layout._GeometryHelper

The Path object for making smooth Paths. To be used in combination with a CrossSection to create a Device.

Parameters:path (array-like[N][2], Path, or list of Paths) – Points or Paths to append() initially
append(path)

Attaches the input path to the end of this object. The input path will be automatically rotated and translated such that it continues smoothly from the previous segment.

Parameters:path (Path, array-like[N][2], or list of Paths) – The input path that will be appended
copy()

Creates a copy of the Path.

Returns:Path – A copy of the Path
curvature()

Calculates the curvature of the Path. Note this curvature is numerically computed so areas where the curvature jumps instantaneously (such as between an arc and a straight segment) will be slightly interpolated, and sudden changes in point density along the curve can cause discontinuities.

Returns:
  • s (array-like[N]) – The arc-length of the Path
  • K (array-like[N]) – The curvature of the Path
extrude(width, layer=nan, simplify=None)

Combines the 1D Path with a 1D cross-section to form 2D polygons.

Parameters:
  • width (int, float, array-like[2], or CrossSection) –

    If set to a single number (e.g. width=1.7): makes a constant-width extrusion If set to a 2-element array (e.g. width=[1.8,2.5]): makes an extrusion

    whose width varies linearly from width[0] to width[1]

    If set to a CrossSection: uses the CrossSection parameters for extrusion

  • layer (int, tuple of int, or set of int) – The layer to put the extruded polygons on. layer=0 is used by default.
  • simplify (float) – Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed. Also known as epsilon here https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
Returns:

Device – A Device with polygons added that correspond to the extrusion of the Path

hash_geometry(precision=0.0001)

Computes an SHA1 hash of the points in the Path and the start_angle and end_angle

Parameters:precision (float) – Roudning precision for the the objects in the Device. For instance, a precision of 1e-2 will round a point at (0.124, 1.748) to (0.12, 1.75)
Returns:str – Hash result in the form of an SHA1 hex digest string

Notes

Algorithm:

hash(
    hash(First layer information: [layer1, datatype1]),
    hash(Polygon 1 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
    hash(Polygon 2 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
    hash(Polygon 3 on layer 1 points: [(x1,y1),(x2,y2),(x3,y3)] ),
    hash(Second layer information: [layer2, datatype2]),
    hash(Polygon 1 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3),(x4,y4)] ),
    hash(Polygon 2 on layer 2 points: [(x1,y1),(x2,y2),(x3,y3)] ),
)
length()

Computes the cumulative length (arc length) of the path.

Returns:float – The length of the Path
mirror(p1=(0, 1), p2=(0, 0))

Mirrors the Path across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves the Path from the origin point to the destination. Both origin and destination can be 1x2 array-like or a Port.

Parameters:
  • origin (array-like[2], Port) – Origin point of the move.
  • destination (array-like[2], Port) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
offset(offset=0)

Offsets the Path so that it follows the Path centerline plus an offset. The offset can either be a fixed value, or a function of the form my_offset(t) where t goes from 0->1

Parameters:offset (int or float, callable) – Magnitude of the offset
rotate(angle=45, center=(0, 0))

Rotates all Polygons in the Device around the specified center point. If no center point specified will rotate around (0,0).

Parameters:
  • angle (int or float) – Angle to rotate the Device in degrees.
  • center (array-like[2] or None) – Midpoint of the Device.
bbox

Returns the bounding box of the Path.

center

Returns the center of the bounding box.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

Polygon

class phidl.device_layout.Polygon(points, gds_layer, gds_datatype, parent)

Bases: gdspy.polygon.Polygon, phidl.device_layout._GeometryHelper

Polygonal geometric object.

Parameters:
  • points (array-like[N][2]) – Coordinates of the vertices of the Polygon.
  • gds_layer (int) – GDSII layer of the Polygon.
  • gds_datatype (int) – GDSII datatype of the Polygon.
  • parent
area(by_spec=False)

Calculate the total area of this polygon set.

Parameters:by_spec (bool) – If True, the return value is a dictionary with {(layer, datatype): area}.
Returns:out (number, dictionary) – Area of this object.
fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

Round the corners of these polygons and fractures them into polygons with less vertices if necessary.

Parameters:
  • radius (number, array-like) – Radius of the corners. If number: all corners filleted by that amount. If array: specify fillet radii on a per-polygon basis (length must be equal to the number of polygons in this PolygonSet). Each element in the array can be a number (all corners filleted by the same amount) or another array of numbers, one per polygon vertex. Alternatively, the array can be flattened to have one radius per PolygonSet vertex.
  • points_per_2pi (integer) – Number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number corresponding to the angle encompassed by that corner with respect to 2 pi.
  • max_points (integer) – Maximal number of points in each resulting polygon (at least 5, otherwise the resulting polygon is not fractured).
  • precision (float) – Desired precision for rounding vertice coordinates in case of fracturing.
Returns:

out (PolygonSet) – This object.

fracture(max_points=199, precision=0.001)

Slice these polygons in the horizontal and vertical directions so that each resulting piece has at most max_points. This operation occurs in place.

Parameters:
  • max_points (integer) – Maximal number of points in each resulting polygon (at least 5 for the fracture to occur).
  • precision (float) – Desired precision for rounding vertice coordinates.
Returns:

out (PolygonSet) – This object.

get_bounding_box()

Calculate the bounding box of the polygons.

Returns:out (Numpy array[2, 2] or None) – Bounding box of this polygon in the form [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.
mirror(p1=(0, 1), p2=(0, 0))

Mirrors a Polygon across the line formed between the two specified points. points may be input as either single points [1,2] or array-like[N][2], and will return in kind.

Parameters:
  • p1 (array-like[N][2]) – First point of the line.
  • p2 (array-like[N][2]) – Second point of the line.
move(origin=(0, 0), destination=None, axis=None)

Moves elements of the Device from the origin point to the destination. Both origin and destination can be 1x2 array-like, Port, or a key corresponding to one of the Ports in this device.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
  • axis ({'x', 'y'}) – Direction of move.
movex(origin=0, destination=None)

Moves an object by a specified x-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, key, or None) – Destination point of the move.
movey(origin=0, destination=None)

Moves an object by a specified y-distance.

Parameters:
  • origin (array-like[2], Port, or key) – Origin point of the move.
  • destination (array-like[2], Port, or key) – Destination point of the move.
rotate(angle=45, center=(0, 0))

Rotates a Polygon by the specified angle.

Parameters:
  • angle (int or float) – Angle to rotate the Polygon in degrees.
  • center (array-like[2] or None) – Midpoint of the Polygon.
scale(scalex, scaley=None, center=(0, 0))

Scale this object.

Parameters:
  • scalex (number) – Scaling factor along the first axis.
  • scaley (number or None) – Scaling factor along the second axis. If None, same as scalex.
  • center (array-like[2]) – Center point for the scaling operation.
Returns:

out (PolygonSet) – This object.

simplify(tolerance=0.001)

Removes points from the polygon but does not change the polygon shape by more than tolerance from the original. Uses the Ramer-Douglas-Peucker algorithm.

Parameters:tolerance (float) – Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed. Also known as epsilon here https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
to_gds(outfile, multiplier)

Convert this object to a series of GDSII elements.

Parameters:
  • outfile (open file) – Output to write the GDSII.
  • multiplier (number) – A number that multiplies all dimensions written in the GDSII elements.
to_svg(outfile, scaling, precision)

Write an SVG fragment representation of this object.

Parameters:
  • outfile (open file) – Output to write the SVG representation.
  • scaling (number) – Scaling factor for the geometry.
  • precision (positive integer or None) – Maximal number of digits for coordinates after scaling.
translate(dx, dy)

Translate this polygon.

Parameters:
  • dx (number) – Distance to move in the x-direction.
  • dy (number) – Distance to move in the y-direction.
Returns:

out (PolygonSet) – This object.

bbox

Returns the bounding box of the Polygon.

center

Returns the center of the bounding box.

size

Returns the (x, y) size of the bounding box.

x

Returns the x-coordinate of the center of the bounding box.

xmax

Returns the maximum x-value of the bounding box.

xmin

Returns the minimum x-value of the bounding box.

xsize

Returns the horizontal size of the bounding box.

y

Returns the y-coordinate of the center of the bounding box.

ymax

Returns the maximum y-value of the bounding box.

ymin

Returns the minimum y-value of the bounding box.

ysize

Returns the vertical size of the bounding box.

Port

class phidl.device_layout.Port(name=None, midpoint=(0, 0), width=1, orientation=0, parent=None)

Bases: object

Port object that can be used to easily snap together other geometric objects

Parameters:
  • name (str) – Name of the Port object.
  • midpoint (array-like[2] of int or float) – Midpoint of the Port location.
  • width (int or float) – Width of the Port.
  • orientation (int or float) – Orientation (rotation) of the Port.
  • parent
rotate(angle=45, center=None)

Rotates a Port around the specified center point, if no centerpoint specified will rotate around (0,0).

Parameters:angle (int or float) – Angle to rotate the Port in degrees. center : array-like[2] or None Midpoint of the Port.
center

Returns the midpoint of the Port.

endpoints

Returns the endpoints of the Port.

normal

Returns a vector normal to the Port

Returns:array-like[2] – Vector normal to the Port
x

Returns the x-coordinate of the Port midpoint.

y

Returns the y-coordinate of the Port midpoint.

make_device

phidl.device_layout.make_device(fun, config=None, **kwargs)

Makes a Device from a function.

Parameters:
  • fun (str) – Name of the function to make the Device with.
  • config (dict or None) – A dictionary containing arguments for the given function.
Returns:

D (Device) – A Device constructed from the specified function.

reset

phidl.device_layout.reset()

Resets the built-in Layer dictionary (controls the coloring in quickplot() ), and sets the Device universal ID (uid) to zero.

Citation

If you found PHIDL useful, please consider citing it in (just one!) of your publications – we appreciate it greatly. (BibTeX cite)

  • McCaughan, A. N., et. al. PHIDL: Python-based layout and geometry creation for nanolithography. J. Vac. Sci. Technol. B 39, 062601 (2021). http://dx.doi.org/10.1116/6.0001203