Components#

gdsfactory provides a generic customizable components library in gf.components

Basic shapes#

Rectangle#

To create a simple rectangle, there are two functions:

gf.components.rectangle() can create a basic rectangle:

[1]:
import gdsfactory as gf

gf.CONF.plotter = "matplotlib"  # This notebook rendered with 'holoviews' exceeds the 100MB limit for github pages

r1 = gf.components.rectangle(size=(4.5, 2), layer=(1, 0))
r1
2022-06-28 17:00:45.275 | INFO     | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.11.4
../_images/notebooks_04_components__3_1.png
[1]:
rectangle_layer1__0_size4p5__2: uid 0, ports ['e1', 'e2', 'e3', 'e4'], aliases [], 0 polygons, 1 references

gf.components.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 gf.components.bbox():

[2]:
c = gf.Component()
arc = c << gf.components.bend_circular(radius=10, width=0.5, angle=90, layer=(1, 0))
arc.rotate(90)
# Draw a rectangle around the arc we created by using the arc's bounding box
rect = c << gf.components.bbox(bbox=arc.bbox, layer=(0, 0))
c
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1054: UserWarning: Component 'Unnamed_ff0bfae4' contains 1 Unnamed cells
  warnings.warn(
../_images/notebooks_04_components__5_1.png
[2]:
Unnamed_ff0bfae4: uid 2, ports [], aliases [], 0 polygons, 2 references

Cross#

The gf.components.cross() function creates a cross structure:

[3]:
gf.components.cross(length=10, width=0.5, layer=(1, 0))
../_images/notebooks_04_components__7_0.png
[3]:
cross_6c558a55: uid 6, ports [], aliases [], 0 polygons, 2 references

Ellipse#

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

[4]:
gf.components.ellipse(radii=(10, 5), angle_resolution=2.5, layer=(1, 0))
../_images/notebooks_04_components__9_0.png
[4]:
ellipse_layer1__0_radii10__5: uid 9, ports [], aliases [], 1 polygons, 0 references

Circle#

The gf.components.circle() function creates a circle:

[5]:
gf.components.circle(radius=10, angle_resolution=2.5, layer=(1, 0))
../_images/notebooks_04_components__11_0.png
[5]:
circle_layer1__0_radius10: uid 10, ports [], aliases [], 1 polygons, 0 references

Ring#

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

[6]:
gf.components.ring(radius=5, width=0.5, angle_resolution=2.5, layer=(1, 0))
../_images/notebooks_04_components__13_0.png
[6]:
ring_layer1__0_radius5: uid 11, ports [], aliases [], 1 polygons, 0 references
[7]:
gf.components.ring_single(
    width=0.5, gap=0.2, radius=10, length_x=4, length_y=2, layer=(1, 0)
)
../_images/notebooks_04_components__14_0.png
[7]:
ring_single_f77f5fc4: uid 12, ports ['o2', 'o1'], aliases [], 0 polygons, 6 references
[8]:
import gdsfactory as gf

gf.components.ring_double(
    width=0.5, gap=0.2, radius=10, length_x=4, length_y=2, layer=(1, 0)
)
../_images/notebooks_04_components__15_0.png
[8]:
ring_double_f77f5fc4: uid 31, ports ['o1', 'o2', 'o3', 'o4'], aliases [], 0 polygons, 4 references
[9]:
gf.components.ring_double(
    width=0.5,
    gap=0.2,
    radius=10,
    length_x=4,
    length_y=2,
    layer=(1, 0),
    bend=gf.components.bend_circular,
)
../_images/notebooks_04_components__16_0.png
[9]:
ring_double_72b012ff: uid 35, ports ['o1', 'o2', 'o3', 'o4'], aliases [], 0 polygons, 4 references

Bend circular#

The gf.components.bend_circular() function creates an arc. The radius refers to the center radius of the arc (halfway between the inner and outer radius).

[10]:
gf.components.bend_circular(radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0))
../_images/notebooks_04_components__18_0.png
[10]:
bend_circular_0f75605b: uid 36, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references

Bend euler#

The gf.components.bend_euler() function creates an adiabatic bend in which the bend radius changes gradually. Euler bends have lower loss than circular bends.

[11]:
gf.components.bend_euler(radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0))
../_images/notebooks_04_components__20_0.png
[11]:
bend_euler_0f75605b: uid 38, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references

Tapers#

gf.components.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.

[12]:
gf.components.taper(length=10, width1=6, width2=4, port=None, layer=(1, 0))
../_images/notebooks_04_components__22_0.png
[12]:
taper_e741c711: uid 40, ports ['o1', 'o2'], aliases [], 3 polygons, 0 references

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

[13]:
gf.components.ramp(length=10, width1=4, width2=8, layer=(1, 0))
../_images/notebooks_04_components__24_0.png
[13]:
ramp_40a909be: uid 41, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references

Common compound shapes#

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

[14]:
gf.components.L(width=7, size=(10, 20), layer=(1, 0))
../_images/notebooks_04_components__27_0.png
[14]:
L_layer1__0_width7: uid 42, ports ['e1', 'e2'], aliases [], 1 polygons, 0 references

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

[15]:
gf.components.C(width=7, size=(10, 20), layer=(1, 0))
../_images/notebooks_04_components__29_0.png
[15]:
C_b95f8aee: uid 43, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references

Text#

Gdsfactory has an implementation of the DEPLOF font with the majority of english ASCII characters represented (thanks to phidl)

[16]:
gf.components.text(
    text="Hello world!\nMultiline text\nLeft-justified",
    size=10,
    justify="left",
    layer=(1, 0),
)
# `justify` should be either 'left', 'center', or 'right'
../_images/notebooks_04_components__31_0.png
[16]:
text_c4bef3d3: uid 44, ports [], aliases [], 44 polygons, 0 references

Grid / packer / align / distribute#

Grid#

The gf.components.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).

[17]:
import gdsfactory as gf

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

c = gf.grid(
    components_list,
    spacing=(5, 1),
    separation=True,
    shape=(3, 4),
    align_x="x",
    align_y="y",
    edge_x="x",
    edge_y="ymax",
)
c
../_images/notebooks_04_components__34_0.png
[17]:
grid_c3306641: uid 59, ports [], aliases [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3)], 0 polygons, 12 references

Pack#

The gf.pack() function packs 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:

[18]:
import numpy as np
import gdsfactory as gf

np.random.seed(5)
D_list = [gf.components.rectangle(size=(i, i)) for i in range(1, 10)]

D_packed_list = gf.pack(
    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 require more computation
    sort_by_area=True,  # Pre-sorts the shapes by area
)
D = D_packed_list[0]  # Only one bin was created, so we plot that
D
../_images/notebooks_04_components__36_0.png
[18]:
pack_0_36b94b4e: uid 79, ports [], aliases [], 0 polygons, 9 references

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:

[19]:
np.random.seed(1)
D_list = [
    gf.components.ellipse(radii=tuple(np.random.rand(2) * n + 2)) for n in range(120)
]
D_packed_list = gf.pack(
    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
)

# Put all packed bins into a single device and spread them out with distribute()
F = gf.Component("packed")
[F.add_ref(D) for D in D_packed_list]
F.distribute(elements="all", direction="x", spacing=100, separation=True)
F
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/pack.py:195: UserWarning: unable to pack in one component, creating 4 components
  warnings.warn(f"unable to pack in one component, creating {groups} components")
../_images/notebooks_04_components__38_1.png
[19]:
packed: uid 204, ports [], aliases [], 0 polygons, 4 references

Note that the packing problem is an NP-complete problem, so gf.components.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:

image0

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

[20]:
c = gf.Component("rectangles")
# Create different-sized rectangles and add them to D
[
    c.add_ref(
        gf.components.rectangle(size=[n * 15 + 20, n * 15 + 20], layer=(2, 0))
    ).move([n, n * 4])
    for n in [0, 2, 3, 1, 2]
]
c
../_images/notebooks_04_components__43_0.png
[20]:
rectangles: uid 205, ports [], aliases [], 0 polygons, 5 references

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:

[21]:
D = gf.Component("rectangles_separated")
# Create different-sized rectangles and add them to D
[
    D.add_ref(gf.components.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,
)
D
../_images/notebooks_04_components__45_0.png
[21]:
rectangles_separated: uid 214, ports [], aliases [], 0 polygons, 5 references

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

[22]:
D = gf.Component("spacing100")
[
    D.add_ref(gf.components.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' (center)
D
../_images/notebooks_04_components__47_0.png
[22]:
spacing100: uid 223, ports [], aliases [], 0 polygons, 5 references

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

[23]:
D = gf.Component("alignment")
[
    D.add_ref(gf.components.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' (center)
D
../_images/notebooks_04_components__49_0.png
[23]:
alignment: uid 224, ports [], aliases [], 0 polygons, 5 references

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:

image0

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

[24]:
D = gf.Component("distribute")
# Create different-sized rectangles and add them to D then distribute them
[
    D.add_ref(gf.components.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)
D
../_images/notebooks_04_components__53_0.png
[24]:
distribute: uid 225, ports [], aliases [], 0 polygons, 5 references

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

[25]:
D = gf.Component("align")
# Create different-sized rectangles and add them to D then distribute them
[
    D.add_ref(gf.components.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")
D
../_images/notebooks_04_components__55_0.png
[25]:
align: uid 226, ports [], aliases [], 0 polygons, 5 references

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

[26]:
D = gf.Component("distribute")
# Create different-sized rectangles and add them to D then distribute them
[
    D.add_ref(gf.components.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")
D
../_images/notebooks_04_components__57_0.png
[26]:
distribute: uid 227, ports [], aliases [], 0 polygons, 5 references

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 gf.geometry.boolean() function can perform AND/OR/NOT/XOR operations, and will return a new geometry with the result of that operation.

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.

[27]:
import gdsfactory as gf

E = gf.components.ellipse(radii=(10, 5), layer=(1, 0))
R = gf.components.rectangle(size=[15, 5], layer=(2, 0))
C = gf.geometry.boolean(
    A=E, B=R, operation="not", precision=1e-6, num_divisions=[1, 1], layer=(3, 0)
)
# Other operations include 'and', 'or', 'xor', or equivalently 'A-B', 'B-A', 'A+B'

# Plot the originals and the result
D = gf.Component("bool")
D.add_ref(E)
D.add_ref(R).movey(-1.5)
D.add_ref(C).movex(30)
D
../_images/notebooks_04_components__61_0.png
[27]:
bool: uid 232, ports [], aliases [], 0 polygons, 3 references

To learn how booleans work you can try all the different operations not, and, or, xor

[28]:
import gdsfactory as gf

operation = "not"
operation = "and"
operation = "or"
operation = "xor"

r1 = (8, 8)
r2 = (11, 4)
r1 = (80, 80)
r2 = (110, 40)

angle_resolution = 0.1

c1 = gf.components.ellipse(radii=r1, layer=(1, 0), angle_resolution=angle_resolution)
c2 = gf.components.ellipse(radii=r2, layer=(1, 0), angle_resolution=angle_resolution)
[29]:
%time

c3 = gf.geometry.boolean_klayout(
    c1, c2, operation=operation, layer1=(1, 0), layer2=(1, 0), layer3=(1, 0)
)  # klayout booleans
c3
CPU times: user 3 µs, sys: 0 ns, total: 3 µs
Wall time: 5.96 µs
2022-06-28 17:00:50.898 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to '/tmp/tmpjrpx1xcd/gdsfactory/ellipse_4aa83906.gds'
2022-06-28 17:00:50.899 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to '/tmp/tmpjr6zceqh/gdsfactory/ellipse_cb56ffd1.gds'
../_images/notebooks_04_components__64_2.png
[29]:
boolean_klayout_8ae24e09: uid 238, ports [], aliases [], 1 polygons, 0 references
[30]:
%time
c4 = gf.geometry.boolean(c1, c2, operation=operation)
c4
CPU times: user 2 µs, sys: 1 µs, total: 3 µs
Wall time: 5.72 µs
../_images/notebooks_04_components__65_1.png
[30]:
boolean_9721e7fb: uid 240, ports [], aliases [], 2 polygons, 0 references

Offset#

The 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.

[31]:
import gdsfactory as gf

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

Texpanded = gf.geometry.offset(
    T, distance=2, join_first=True, precision=1e-6, num_divisions=[1, 1], layer=(2, 0)
)
Texpanded.name = "expanded"
Tshrink = gf.geometry.offset(
    T,
    distance=-1.5,
    join_first=True,
    precision=1e-6,
    num_divisions=[1, 1],
    layer=(2, 0),
)
Tshrink.name = "shrink"

# Plot the original geometry, the expanded, and the shrunk versions
offsets = gf.Component("top")
t1 = offsets.add_ref(T)
t2 = offsets.add_ref(Texpanded)
t3 = offsets.add_ref(Tshrink)
offsets.distribute([t1, t2, t3], direction="x", spacing=5)
offsets
../_images/notebooks_04_components__67_0.png
[31]:
top: uid 244, ports [], aliases [], 0 polygons, 3 references

Outline#

The 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.

[32]:
import gdsfactory as gf

# Create a blank device and add two shapes
X = gf.Component("outline")
X.add_ref(gf.components.cross(length=25, width=1, layer=(1, 0)))
X.add_ref(gf.components.ellipse(radii=[10, 5], layer=(2, 0)))

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

# Plot the original geometry and the result
c = gf.Component()
c.add_ref(X)
c.add_ref(O).movex(30)
c
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1054: UserWarning: Component 'Unnamed_5bd75859' contains 1 Unnamed cells
  warnings.warn(
../_images/notebooks_04_components__69_1.png
[32]:
Unnamed_5bd75859: uid 255, ports [], aliases [], 0 polygons, 2 references

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).

[33]:
gf.components.L(width=7, size=(10, 20), layer=(1, 0))
../_images/notebooks_04_components__71_0.png
[33]:
L_layer1__0_width7: uid 42, ports ['e1', 'e2'], aliases [], 1 polygons, 0 references
[34]:
# Outline the geometry and open a hole at each port
gf.geometry.outline(offsets, distance=5, open_ports=False, layer=(2, 0))  # No holes
../_images/notebooks_04_components__72_0.png
[34]:
outline_bd44224e: uid 260, ports [], aliases [], 1 polygons, 0 references
[35]:
gf.geometry.outline(
    offsets, distance=5, open_ports=True, layer=(2, 0)
)  # Hole is the same width as the port
../_images/notebooks_04_components__73_0.png
[35]:
outline_9be767f5: uid 265, ports [], aliases [], 1 polygons, 0 references
[36]:
gf.geometry.outline(
    offsets, distance=5, open_ports=10, layer=(2, 0)
)  # Change the hole size by entering a float
../_images/notebooks_04_components__74_0.png
[36]:
outline_f849b4db: uid 270, ports [], aliases [], 1 polygons, 0 references
[37]:
gf.geometry.outline(
    offsets, distance=5, open_ports=5, layer=(2, 0)
)  # Creates flush opening (open_ports > distance)
../_images/notebooks_04_components__75_0.png
[37]:
outline_22034eb2: uid 275, ports [], aliases [], 1 polygons, 0 references

Invert#

The gf.boolean.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.

[38]:
import gdsfactory as gf

E = gf.components.ellipse(radii=(10, 5))
D = gf.geometry.invert(E, border=0.5, precision=1e-6, layer=(2, 0))
D
../_images/notebooks_04_components__77_0.png
[38]:
invert_8d2bda7b: uid 283, ports [], aliases [], 1 polygons, 0 references

Union#

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

[39]:
import gdsfactory as gf

D = gf.Component("union")
e0 = D << gf.components.ellipse(layer=(1, 0))
e1 = D << gf.components.ellipse(layer=(2, 0))
e2 = D << gf.components.ellipse(layer=(3, 0))
e3 = D << gf.components.ellipse(layer=(4, 0))
e4 = D << gf.components.ellipse(layer=(5, 0))
e5 = D << gf.components.ellipse(layer=(6, 0))

e1.rotate(15 * 1)
e2.rotate(15 * 2)
e3.rotate(15 * 3)
e4.rotate(15 * 4)
e5.rotate(15 * 5)

D
../_images/notebooks_04_components__79_0.png
[39]:
union: uid 284, ports [], aliases [], 0 polygons, 6 references
[40]:
# We have two options to unioning - take all polygons, regardless of
# layer, and join them together (in this case on layer (2,0) like so:
D_joined = gf.geometry.union(D, by_layer=False, layer=(2, 0))
D_joined
../_images/notebooks_04_components__80_0.png
[40]:
union_4c8610ff: uid 291, ports [], aliases [], 1 polygons, 0 references
[41]:
# Or we can perform the union operate by-layer
D_joined_by_layer = gf.geometry.union(D, by_layer=True)
D_joined_by_layer
../_images/notebooks_04_components__81_0.png
[41]:
union_701ec5f2: uid 292, ports [], aliases [], 6 polygons, 0 references

XOR / diff#

The 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.

[42]:
import gdsfactory as gf

A = gf.Component("A")
A.add_ref(gf.components.ellipse(radii=[10, 5], layer=(1, 0)))
A.add_ref(gf.components.text("A")).move([3, 0])

B = gf.Component("B")
B.add_ref(gf.components.ellipse(radii=[11, 4], layer=(1, 0))).movex(4)
B.add_ref(gf.components.text("B")).move([3.2, 0])
X = gf.geometry.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 = gf.Component("xor_diff")
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)
D
../_images/notebooks_04_components__83_0.png
[42]:
xor_diff: uid 302, ports [], aliases [], 0 polygons, 5 references

Lithography structures#

Step-resolution#

The gf.components.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.

[43]:
D = gf.components.litho_steps(
    line_widths=[1, 2, 4, 8, 16], line_spacing=10, height=100, layer=(1, 0)
)
D
../_images/notebooks_04_components__85_0.png
[43]:
litho_steps_af496947: uid 303, ports [], aliases [], 0 polygons, 12 references

Calipers (inter-layer alignment)#

The gf.components.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.

[44]:
D = gf.components.litho_calipers(
    notch_size=[1, 5],
    notch_spacing=2,
    num_notches=7,
    offset_per_notch=0.1,
    row_spacing=0,
    layer1=(1, 0),
    layer2=(2, 0),
)
D
../_images/notebooks_04_components__88_0.png
[44]:
litho_calipers_4f052b1e: uid 318, ports [], aliases [], 0 polygons, 32 references

Paths / straights#

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

Circular arc#

[45]:
P = gf.path.arc(radius=10, angle=135, npoints=720)
f = P.plot()
../_images/notebooks_04_components__91_0.png

Straight#

[46]:
import gdsfactory as gf

P = gf.path.straight(length=5, npoints=100)
f = P.plot()
../_images/notebooks_04_components__93_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 straights. 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.

[47]:
P = gf.path.euler(radius=3, angle=90, p=1.0, use_eff=False, npoints=720)
f = P.plot()
../_images/notebooks_04_components__95_0.png

Smooth path from waypoints#

[48]:
import numpy as np
import gdsfactory as gf

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

P = gf.path.smooth(
    points=points,
    radius=2,
    bend=gf.path.euler,
    use_eff=False,
)
f = P.plot()
../_images/notebooks_04_components__97_0.png

Delay spiral#

[49]:
c = gf.components.spiral()
c
../_images/notebooks_04_components__99_0.png
[49]:
spiral: uid 324, ports ['o2', 'o1'], aliases [], 242 polygons, 0 references

Importing GDS files#

gf.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 gdsfactory component.

[50]:
D = gf.components.ellipse()
D.write_gds("myoutput.gds")
D2 = gf.import_gds(gdspath="myoutput.gds", cellname=None, flatten=False)
D2
2022-06-28 17:00:54.866 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'myoutput.gds'
../_images/notebooks_04_components__102_1.png
[50]:
ellipse: uid 326, ports [], aliases [], 1 polygons, 0 references

LayerColors#

The LayerColors 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 gf.layers.preview_layerset()

[51]:
import gdsfactory as gf

lys = gf.layers.LayerColors()
lys.add_layer("p", color="lightblue", gds_layer=21, gds_datatype=0)
lys.add_layer("p+", color="blue", gds_layer=23, gds_datatype=0)
lys.add_layer("p++", color="darkblue", gds_layer=25, gds_datatype=0)
lys.add_layer("n", color="lightgreen", gds_layer=20, gds_datatype=0)
lys.add_layer("n+", color="green", gds_layer=22, gds_datatype=0)
lys.add_layer("n++", color="darkgreen", gds_layer=24, gds_datatype=0)
D = gf.layers.preview_layerset(lys, size=100, spacing=100)
D
../_images/notebooks_04_components__104_0.png
[51]:
layerset: uid 327, ports [], aliases [], 0 polygons, 12 references

Useful contact pads / connectors#

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

[52]:
c = gf.components.compass(size=(4, 2), layer=(1, 0))
c
../_images/notebooks_04_components__106_0.png
[52]:
compass_layer1__0_size4__2: uid 358, ports ['e1', 'e2', 'e3', 'e4'], aliases [], 1 polygons, 0 references
[53]:
c = gf.components.nxn(north=3, south=4, east=0, west=0)
c
../_images/notebooks_04_components__107_0.png
[53]:
nxn_025a5d93: uid 359, ports ['o1', 'o2', 'o3', 'o7', 'o6', 'o5', 'o4'], aliases [], 0 polygons, 1 references
[54]:
c = gf.components.pad()
c
../_images/notebooks_04_components__108_0.png
[54]:
pad: uid 361, ports ['e1', 'e2', 'e3', 'e4', 'pad'], aliases [], 0 polygons, 1 references
[55]:
c = gf.components.pad_array90(columns=3)
c
../_images/notebooks_04_components__109_0.png
[55]:
pad_array_columns3_orientation90: uid 363, ports ['e11', 'e12', 'e13'], aliases [], 0 polygons, 1 references

Chip / die template#

[56]:
import gdsfactory as gf

D = gf.components.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=(2, 0),
    bbox_layer=(3, 0),
)
D
../_images/notebooks_04_components__111_0.png
[56]:
die_83021bd5: uid 364, ports [], aliases [], 5 polygons, 1 references

Optimal superconducting curves#

The following structures from phidl 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).

[57]:
import gdsfactory as gf
import phidl.geometry as pg

D = pg.optimal_hairpin(
    width=0.2, pitch=0.6, length=10, turn_ratio=4, num_pts=50, layer=(2, 0)
)
c = gf.read.from_phidl(D)
c
../_images/notebooks_04_components__113_0.png
[57]:
hairpin: uid 368, ports [1, 2], aliases [], 2 polygons, 0 references
[58]:
import gdsfactory as gf
import phidl.geometry as pg

D = pg.optimal_step(
    start_width=10,
    end_width=22,
    num_pts=50,
    width_tol=1e-3,
    anticrowding_factor=1.2,
    symmetric=False,
    layer=(2, 0),
)
c = gf.read.from_phidl(D)
c
../_images/notebooks_04_components__114_0.png
[58]:
step: uid 370, ports [1, 2], aliases [], 1 polygons, 0 references
[59]:
import phidl.geometry as pg

D = pg.optimal_90deg(width=100.0, num_pts=15, length_adjust=1, layer=(2, 0))
c = gf.read.from_phidl(D)
c
../_images/notebooks_04_components__115_0.png
[59]:
90deg: uid 372, ports [1, 2], aliases [], 1 polygons, 0 references
[60]:
import phidl.geometry as pg

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=(2, 0),
)
c = gf.read.from_phidl(D)
c
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1037: UserWarning: Duplicated cell names in 'snspd':  ['compass', 'rectangle']
  warnings.warn(
../_images/notebooks_04_components__116_1.png
[60]:
snspd: uid 379, ports [1, 2], aliases [], 0 polygons, 16 references
[61]:
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=(2, 0),
)
c = gf.read.from_phidl(D)
c
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1037: UserWarning: Duplicated cell names in 'snspd_expanded':  ['rectangle', 'compass']
  warnings.warn(
../_images/notebooks_04_components__117_1.png
[61]:
snspd_expanded: uid 388, ports [1, 2], aliases [], 0 polygons, 3 references

Copying and extracting geometry#

[62]:
E = gf.Component()
E.add_ref(gf.components.ellipse(layer=(1, 0)))
D = E.extract(layers=[(1, 0)])
D
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1054: UserWarning: Component 'Unnamed_126a4eb1_[[1, 0]]' contains 1 Unnamed cells
  warnings.warn(
../_images/notebooks_04_components__119_1.png
[62]:
Unnamed_126a4eb1_[[1, 0]]: uid 390, ports [], aliases [], 1 polygons, 0 references
[63]:
import gdsfactory as gf

X = gf.components.ellipse(layer=(2, 0))
c = X.copy()
c
../_images/notebooks_04_components__120_0.png
[63]:
ellipse_layer2__0_copy: uid 391, ports [], aliases [], 1 polygons, 0 references
[64]:
gf.components.copy_layers(gf.components.straight, layers=((1, 0), (2, 0)))
../_images/notebooks_04_components__121_0.png
[64]:
straight_layer2__0_copy_1a14b6fe: uid 392, ports [], aliases [], 0 polygons, 2 references

Dummy Fill / Tiling#

To keep constant density in some layers you can add dummy fill rectangles.

[65]:
coupler_lengths = [10, 20, 30, 40, 50, 60, 70, 80]
coupler_gaps = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
delta_lengths = [10, 100, 200, 300, 400, 500, 500]

mzi = gf.components.mzi_lattice(
    coupler_lengths=coupler_lengths,
    coupler_gaps=coupler_gaps,
    delta_lengths=delta_lengths,
)

# Add fill
c = gf.Component("component_with_fill")
layers = [(1, 0)]
fill_size = [0.5, 0.5]

c << gf.fill_rectangle(
    mzi,
    fill_size=fill_size,
    fill_layers=layers,
    margin=5,
    fill_densities=[0.8] * len(layers),
    avoid_layers=layers,
)

c << mzi
c.show(show_ports=True)
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/phidl/geometry.py:3822: FutureWarning: `selem` is a deprecated argument name for `binary_dilation`. It will be removed in version 1.0. Please use `footprint` instead.
  return morphology.binary_dilation(image=raster, selem=neighborhood)