Layout tutorial

Contents

try:
  import google.colab
  is_running_on_colab = True
  !pip install gdsfactory > /dev/null
  
except ImportError:
  is_running_on_colab = False
import gdsfactory as gf

Layout tutorial#

A Component is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)

In gdsfactory all dimensions are in microns

import gdsfactory as gf

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

# Plot the originals and the result
c = gf.Component("bool")
c.add_ref(e)
c.add_ref(r).movey(-1.5)
c.add_ref(b).movex(30)
c.plot()
2024-03-09 00:43:04.925 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bool.lyp'.
../_images/0b19d7b7f00a28bad105d23e4a1cdb0653c9bb938a01512d6bad8866c72765ee.png

Polygons#

You can add polygons to different layers.

import gdsfactory as gf


@gf.cell
def demo_polygons():
    # Create a blank component (essentially an empty GDS cell with some special features)
    c = gf.Component()

    # Create and add a polygon from separate lists of x points and y points
    # (Can also be added like [(x1,y1), (x2,y2), (x3,y3), ... ]
    poly1 = c.add_polygon(
        [(-8, 6, 7, 9), (-6, 8, 17, 5)], layer=(1, 0)
    )  # GDS layers are tuples of ints (but if we use only one number it assumes the other number is 0)
    return c


c = demo_polygons()
c.plot()  # show it in KLayout
2024-03-09 00:43:05.086 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_polygons.lyp'.
../_images/557f3f82abf369dc24b94d74d232428605be74fdfcc9a5df81b4b54d5c98f329.png
c = gf.Component("enclosure1")
r = c << gf.components.ring_single()
c.plot()
2024-03-09 00:43:05.252 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure1.lyp'.
../_images/d1f7ef2364db297165b36a929f61bda3592f30904bdca06fc487475a01ac2a64.png

Connect ports#

Components can have a “Port” that allows you to connect ComponentReferences together like legos.

You can write a simple function to make a rectangular straight, assign ports to the ends, and then connect those rectangles together.

Notice that connect transform each reference but things won’t remain connected if you move any of the references afterwards.

@gf.cell
def straight(length=10, width=1, layer=(1, 0)):
    WG = gf.Component()
    WG.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)
    WG.add_port(
        name="o1", center=[0, width / 2], width=width, orientation=180, layer=layer
    )
    WG.add_port(
        name="o2", center=[length, width / 2], width=width, orientation=0, layer=layer
    )
    return WG


c = gf.Component("straights_not_connected")

wg1 = c << straight(length=10, width=2.5, layer=(1, 0))
wg2 = c << straight(length=11, width=2.5, layer=(2, 0))
wg3 = c << straight(length=15, width=2.5, layer=(3, 0))
wg2.movey(10).rotate(10)
wg3.movey(20).rotate(15)

c.plot()
2024-03-09 00:43:05.389 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component straights_not_connected has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:43:05.403 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component straights_not_connected has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(
../_images/d55d9a84413951edd9a485c5838cfab0196fa0b4ed352db94eb3770b07f42c24.png

Now we can connect everything together using the ports:

Each straight has two ports: ‘o1’ and ‘o2’, respectively on the East and West sides of the rectangular straight component. These are arbitrary names defined in our straight() function above

# Let's keep wg1 in place on the bottom, and connect the other straights to it.
# To do that, on wg2 we'll grab the "o1" port and connect it to the "o2" on wg1:
wg2.connect("o1", wg1.ports["o2"])
# Next, on wg3 let's grab the "o1" port and connect it to the "o2" on wg2:
wg3.connect("o1", wg2.ports["o2"])

c.plot()
2024-03-09 00:43:05.545 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.
../_images/ed6343156433b90e89c78c9a9eabd4a61225b7e041a4e37b4fe06debafad7cbc.png

Ports can be added by copying existing ports. In the example below, ports are added at the component-level on c from the existing ports of children wg1 and wg3 (i.e. eastmost and westmost ports)

c.add_port("o1", port=wg1.ports["o1"])
c.add_port("o2", port=wg3.ports["o2"])
c.plot()
2024-03-09 00:43:05.751 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.
../_images/a1b338b3d2a2862e9e79a7f7442c9edda67b14c05fc13514b787657892d7d63f.png

Write#

You can write your Component to:

  • GDS file (Graphical Database System) or OASIS for chips.

  • gerber for PCB.

  • STL for 3d printing.

import gdsfactory as gf

c = gf.components.cross()
c.write_gds("demo.gds")
c.plot()
2024-03-09 00:43:05.882 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.gds'
2024-03-09 00:43:05.896 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/cross.lyp'.
../_images/68b83733440cf48392ed2431390825b7ac7d5ab354d5d1f7457bfc75aa1c3c4a.png

You can see the GDS file in Klayout viewer.

Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations …) in YAML

c.write_gds("demo.gds", with_metadata=True)
2024-03-09 00:43:06.025 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.gds'
2024-03-09 00:43:06.030 | INFO     | gdsfactory.component:_write_library:2025 - Write YAML metadata to 'demo.yml'
PosixPath('demo.gds')

You can also write it in OASIS format

c.write_oas("demo.oas")
2024-03-09 00:43:06.037 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.oas'
PosixPath('demo.oas')

Or in STL for 3D printing or for integrating with other 3D CAD or simulation tools.

c.write_stl("demo.stl")
Write PosixPath('/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/demo_1_0.stl') zmin = 0.000, height = 0.220
scene = c.to_3d()
scene.show()

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.

D = gf.components.ellipse()
D.write_gds("myoutput.gds")
D2 = gf.import_gds(gdspath="myoutput.gds", cellname=None, flatten=False)
D2.plot()
2024-03-09 00:43:06.376 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'myoutput.gds'
2024-03-09 00:43:06.389 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ellipse.lyp'.
../_images/e8e5cc8dbd791fc4168f5d55ae83d2afc01587aa20f06a807111bf648b5d6e1f.png

Generic_tech#

gdsfactory includes a generic Technology module in gdsfactory.generic_tech that you can use as an inspiration to create your own.

LayerMap#

A layer map maps layer names to a integer numbers pair (GDSlayer, GDSpurpose)

Each foundry uses different GDS layer numbers for different process steps.

We follow the generic layer numbers from the book “Silicon Photonics Design: From Devices to Systems Lukas Chrostowski, Michael Hochberg”.

GDS (layer, purpose)

layer_name

Description

1 , 0

WG

220 nm Silicon core

2 , 0

SLAB150

150nm Silicon slab (70nm shallow Etch for grating couplers)

3 , 0

SLAB90

90nm Silicon slab (for modulators)

4, 0

DEEPTRENCH

Deep trench

47, 0

MH

heater

41, 0

M1

metal 1

45, 0

M2

metal 2

40, 0

VIAC

VIAC to contact Ge, NPP or PPP

44, 0

VIA1

VIA1

46, 0

PADOPEN

Bond pad opening

51, 0

UNDERCUT

Undercut

66, 0

TEXT

Text markup

64, 0

FLOORPLAN

Mask floorplan

from pydantic import BaseModel
import gdsfactory as gf
from gdsfactory.generic_tech import LAYER, LAYER_STACK
from gdsfactory.generic_tech.get_klayout_pyxs import get_klayout_pyxs
from gdsfactory.technology import LayerLevel, LayerStack, LayerViews, LayerMap
from gdsfactory.generic_tech import get_generic_pdk
Layer = tuple[int, int]


class GenericLayerMap(LayerMap):
    """Generic layermap based on book.

    Lukas Chrostowski, Michael Hochberg, "Silicon Photonics Design",
    Cambridge University Press 2015, page 353
    You will need to create a new LayerMap with your specific foundry layers.
    """

    WAFER: Layer = (99999, 0)

    WG: Layer = (1, 0)
    WGCLAD: Layer = (111, 0)
    SLAB150: Layer = (2, 0)
    SLAB90: Layer = (3, 0)
    DEEPTRENCH: Layer = (4, 0)
    GE: Layer = (5, 0)
    UNDERCUT: Layer = (6, 0)
    WGN: Layer = (34, 0)
    WGN_CLAD: Layer = (36, 0)

    N: Layer = (20, 0)
    NP: Layer = (22, 0)
    NPP: Layer = (24, 0)
    P: Layer = (21, 0)
    PP: Layer = (23, 0)
    PPP: Layer = (25, 0)
    GEN: Layer = (26, 0)
    GEP: Layer = (27, 0)

    HEATER: Layer = (47, 0)
    M1: Layer = (41, 0)
    M2: Layer = (45, 0)
    M3: Layer = (49, 0)
    VIAC: Layer = (40, 0)
    VIA1: Layer = (44, 0)
    VIA2: Layer = (43, 0)
    PADOPEN: Layer = (46, 0)

    DICING: Layer = (100, 0)
    NO_TILE_SI: Layer = (71, 0)
    PADDING: Layer = (67, 0)
    DEVREC: Layer = (68, 0)
    FLOORPLAN: Layer = (64, 0)
    TEXT: Layer = (66, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    PORTH: Layer = (70, 0)
    SHOW_PORTS: Layer = (1, 12)
    LABEL: Layer = (201, 0)
    LABEL_SETTINGS: Layer = (202, 0)
    TE: Layer = (203, 0)
    TM: Layer = (204, 0)
    DRC_MARKER: Layer = (205, 0)
    LABEL_INSTANCE: Layer = (206, 0)
    ERROR_MARKER: Layer = (207, 0)
    ERROR_PATH: Layer = (208, 0)

    SOURCE: Layer = (110, 0)
    MONITOR: Layer = (101, 0)


LAYER = GenericLayerMap()
LAYER
GenericLayerMap(LABEL_INSTANCE=(206, 0), LABEL_SETTINGS=(202, 0), WAFER=(99999, 0), WG=(1, 0), WGCLAD=(111, 0), SLAB150=(2, 0), SLAB90=(3, 0), DEEPTRENCH=(4, 0), GE=(5, 0), UNDERCUT=(6, 0), WGN=(34, 0), WGN_CLAD=(36, 0), N=(20, 0), NP=(22, 0), NPP=(24, 0), P=(21, 0), PP=(23, 0), PPP=(25, 0), GEN=(26, 0), GEP=(27, 0), HEATER=(47, 0), M1=(41, 0), M2=(45, 0), M3=(49, 0), VIAC=(40, 0), VIA1=(44, 0), VIA2=(43, 0), PADOPEN=(46, 0), DICING=(100, 0), NO_TILE_SI=(71, 0), PADDING=(67, 0), DEVREC=(68, 0), FLOORPLAN=(64, 0), TEXT=(66, 0), PORT=(1, 10), PORTE=(1, 11), PORTH=(70, 0), SHOW_PORTS=(1, 12), LABEL=(201, 0), TE=(203, 0), TM=(204, 0), DRC_MARKER=(205, 0), ERROR_MARKER=(207, 0), ERROR_PATH=(208, 0), SOURCE=(110, 0), MONITOR=(101, 0))

Extract layers#

You can also extract layers using the extract function. This function returns a new flattened Component that contains the extracted layers. A flat Component does not have references, and all the polygons are absorbed into the top cell.

from gdsfactory.generic_tech import get_generic_pdk

PDK = get_generic_pdk()
PDK.activate()

LAYER_VIEWS = PDK.layer_views
c = LAYER_VIEWS.preview_layerset()
c.plot()
2024-03-09 00:43:06.592 | WARNING  | gdsfactory.component:__init__:211 - UserWarning: with_uuid is deprecated. Use @cell decorator instead.
2024-03-09 00:43:06.770 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/layerset_962ac90a.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:211: UserWarning: with_uuid is deprecated. Use @cell decorator instead.
  warnings.warn("with_uuid is deprecated. Use @cell decorator instead.")
../_images/3ea05a8f5d68060a227de07b6b4b44f702d271472825d42bb19dd5983fa74e98.png

LayerStack#

Each layer also includes the information of thickness and position of each layer after fabrication.

This LayerStack can be used for creating a 3D model with Component.to_3d or running Simulations.

A GDS has different layers to describe the different fabrication process steps. And each grown layer needs thickness information and z-position in the stack.

layer stack

Lets define the layer stack for the generic layers in the generic_technology.

from gdsfactory.generic_tech.layer_map import LAYER
from gdsfactory.technology import LayerLevel, LayerStack

nm = 1e-3


class LayerStackParameters:
    """values used by get_layer_stack and get_process."""

    thickness_wg: float = 220 * nm
    thickness_slab_deep_etch: float = 90 * nm
    thickness_slab_shallow_etch: float = 150 * nm
    sidewall_angle_wg: float = 10
    thickness_clad: float = 3.0
    thickness_nitride: float = 350 * nm
    thickness_ge: float = 500 * nm
    gap_silicon_to_nitride: float = 100 * nm
    zmin_heater: float = 1.1
    zmin_metal1: float = 1.1
    thickness_metal1: float = 700 * nm
    zmin_metal2: float = 2.3
    thickness_metal2: float = 700 * nm
    zmin_metal3: float = 3.2
    thickness_metal3: float = 2000 * nm
    substrate_thickness: float = 10.0
    box_thickness: float = 3.0
    undercut_thickness: float = 5.0


def get_layer_stack(
    thickness_wg=LayerStackParameters.thickness_wg,
    thickness_slab_deep_etch=LayerStackParameters.thickness_slab_deep_etch,
    thickness_slab_shallow_etch=LayerStackParameters.thickness_slab_shallow_etch,
    sidewall_angle_wg=LayerStackParameters.sidewall_angle_wg,
    thickness_clad=LayerStackParameters.thickness_clad,
    thickness_nitride=LayerStackParameters.thickness_nitride,
    thickness_ge=LayerStackParameters.thickness_ge,
    gap_silicon_to_nitride=LayerStackParameters.gap_silicon_to_nitride,
    zmin_heater=LayerStackParameters.zmin_heater,
    zmin_metal1=LayerStackParameters.zmin_metal1,
    thickness_metal1=LayerStackParameters.thickness_metal1,
    zmin_metal2=LayerStackParameters.zmin_metal2,
    thickness_metal2=LayerStackParameters.thickness_metal2,
    zmin_metal3=LayerStackParameters.zmin_metal3,
    thickness_metal3=LayerStackParameters.thickness_metal3,
    substrate_thickness=LayerStackParameters.substrate_thickness,
    box_thickness=LayerStackParameters.box_thickness,
    undercut_thickness=LayerStackParameters.undercut_thickness,
) -> LayerStack:
    """Returns generic LayerStack.

    based on paper https://www.degruyter.com/document/doi/10.1515/nanoph-2013-0034/html

    Args:
        thickness_wg: waveguide thickness in um.
        thickness_slab_deep_etch: for deep etched slab.
        thickness_shallow_etch: thickness for the etch in um.
        sidewall_angle_wg: waveguide side angle.
        thickness_clad: cladding thickness in um.
        thickness_nitride: nitride thickness in um.
        thickness_ge: germanium thickness.
        gap_silicon_to_nitride: distance from silicon to nitride in um.
        zmin_heater: TiN heater.
        zmin_metal1: metal1.
        thickness_metal1: metal1 thickness.
        zmin_metal2: metal2.
        thickness_metal2: metal2 thickness.
        zmin_metal3: metal3.
        thickness_metal3: metal3 thickness.
        substrate_thickness: substrate thickness in um.
        box_thickness: bottom oxide thickness in um.
        undercut_thickness: thickness of the silicon undercut.
    """

    thickness_deep_etch = thickness_wg - thickness_slab_deep_etch
    thickness_shallow_etch = thickness_wg - thickness_slab_shallow_etch

    return LayerStack(
        layers=dict(
            substrate=LayerLevel(
                layer=LAYER.WAFER,
                thickness=substrate_thickness,
                zmin=-substrate_thickness - box_thickness,
                material="si",
                mesh_order=101,
                background_doping_concentration=1e14,
                background_doping_ion="Boron",
                orientation="100",
            ),
            box=LayerLevel(
                layer=LAYER.WAFER,
                thickness=box_thickness,
                zmin=-box_thickness,
                material="sio2",
                mesh_order=9,
            ),
            core=LayerLevel(
                layer=LAYER.WG,
                thickness=thickness_wg,
                zmin=0.0,
                material="si",
                mesh_order=2,
                sidewall_angle=sidewall_angle_wg,
                width_to_z=0.5,
                background_doping_concentration=1e14,
                background_doping_ion="Boron",
                orientation="100",
                info={"active": True},
            ),
            shallow_etch=LayerLevel(
                layer=LAYER.SHALLOW_ETCH,
                thickness=thickness_shallow_etch,
                zmin=0.0,
                material="si",
                mesh_order=1,
                layer_type="etch",
                into=["core"],
                derived_layer=LAYER.SLAB150,
            ),
            deep_etch=LayerLevel(
                layer=LAYER.DEEP_ETCH,
                thickness=thickness_deep_etch,
                zmin=0.0,
                material="si",
                mesh_order=1,
                layer_type="etch",
                into=["core"],
                derived_layer=LAYER.SLAB90,
            ),
            clad=LayerLevel(
                layer=LAYER.WAFER,
                zmin=0.0,
                material="sio2",
                thickness=thickness_clad,
                mesh_order=10,
            ),
            slab150=LayerLevel(
                layer=LAYER.SLAB150,
                thickness=150e-3,
                zmin=0,
                material="si",
                mesh_order=3,
            ),
            slab90=LayerLevel(
                layer=LAYER.SLAB90,
                thickness=thickness_slab_deep_etch,
                zmin=0.0,
                material="si",
                mesh_order=2,
            ),
            nitride=LayerLevel(
                layer=LAYER.WGN,
                thickness=thickness_nitride,
                zmin=thickness_wg + gap_silicon_to_nitride,
                material="sin",
                mesh_order=2,
            ),
            ge=LayerLevel(
                layer=LAYER.GE,
                thickness=thickness_ge,
                zmin=thickness_wg,
                material="ge",
                mesh_order=1,
            ),
            undercut=LayerLevel(
                layer=LAYER.UNDERCUT,
                thickness=-undercut_thickness,
                zmin=-box_thickness,
                material="air",
                z_to_bias=(
                    [0, 0.3, 0.6, 0.8, 0.9, 1],
                    [-0, -0.5, -1, -1.5, -2, -2.5],
                ),
                mesh_order=1,
            ),
            via_contact=LayerLevel(
                layer=LAYER.VIAC,
                thickness=zmin_metal1 - thickness_slab_deep_etch,
                zmin=thickness_slab_deep_etch,
                material="Aluminum",
                mesh_order=1,
                sidewall_angle=-10,
                width_to_z=0,
            ),
            metal1=LayerLevel(
                layer=LAYER.M1,
                thickness=thickness_metal1,
                zmin=zmin_metal1,
                material="Aluminum",
                mesh_order=2,
            ),
            heater=LayerLevel(
                layer=LAYER.HEATER,
                thickness=750e-3,
                zmin=zmin_heater,
                material="TiN",
                mesh_order=2,
            ),
            via1=LayerLevel(
                layer=LAYER.VIA1,
                thickness=zmin_metal2 - (zmin_metal1 + thickness_metal1),
                zmin=zmin_metal1 + thickness_metal1,
                material="Aluminum",
                mesh_order=1,
            ),
            metal2=LayerLevel(
                layer=LAYER.M2,
                thickness=thickness_metal2,
                zmin=zmin_metal2,
                material="Aluminum",
                mesh_order=2,
            ),
            via2=LayerLevel(
                layer=LAYER.VIA2,
                thickness=zmin_metal3 - (zmin_metal2 + thickness_metal2),
                zmin=zmin_metal2 + thickness_metal2,
                material="Aluminum",
                mesh_order=1,
            ),
            metal3=LayerLevel(
                layer=LAYER.M3,
                thickness=thickness_metal3,
                zmin=zmin_metal3,
                material="Aluminum",
                mesh_order=2,
            ),
        )
    )


LAYER_STACK = get_layer_stack()
layer_stack220 = LAYER_STACK
import gdsfactory as gf

c = gf.components.straight_heater_metal(length=40)
c.plot()
2024-03-09 00:43:07.007 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_heater_metal_undercut_4c784430.lyp'.
../_images/8d1514b3ad4f8616d159d5e62d2ca54d0bfd69fbf09c2323c08b2e3b8577ecb7.png
scene = c.to_3d(layer_stack=layer_stack220)
scene.show()
import gdsfactory as gf

c = gf.components.taper_strip_to_ridge_trenches()
c.plot()
2024-03-09 00:43:07.329 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/taper_strip_to_ridge_trenches.lyp'.
../_images/15794684e90a2a169975c3d548f6aa807f234144efb5575b88eb74994788648f.png
scene = c.to_3d(layer_stack=layer_stack220)
scene.show()
# lets assume we have 900nm silicon instead of 220nm, You will see a much thicker waveguide under the metal heater.
layer_stack900 = get_layer_stack(thickness_wg=900 * nm)
scene = c.to_3d(layer_stack=layer_stack900)
scene.show()
import gdsfactory as gf

c = gf.components.grating_coupler_elliptical_trenches()
c.plot()
2024-03-09 00:43:07.587 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/grating_coupler_elliptical_trenches.lyp'.
../_images/19ed20f19de48aa3e4c15da2749b01b80342e4d16899f636a03df694b9cf8e19.png
scene = c.to_3d()
scene.show()

3D rendering#

To render components in 3D you will need to define two things:

  1. LayerStack: for each layer contains thickness of each material and z position

  2. LayerViews: for each layer contains view (color, pattern, opacity). You can load it with gf.technology.LayerView.load_lyp()

heater = gf.components.straight_heater_metal(length=50)
heater.plot()
2024-03-09 00:43:07.927 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_heater_metal_undercut_8589be07.lyp'.
../_images/a1d688b1fac33c71be28469d7a7c93c451aa5f394bbf292d07ad488380d5a897.png
scene = heater.to_3d()
scene.show()

As the sequence is defined as a string you can use the string operations to easily build complex sequences

References and ports#

gdsfactory defines your component once in memory and can add multiple References (Instances) to the same component.

As you build components you can include references to other components. Adding a reference is like having a pointer to a component.

The GDSII specification allows the use of references, and similarly gdsfactory uses them (with the add_ref() function). what is a reference? Simply put: A reference does not contain any geometry. It only points to an existing geometry.

Say you have a ridiculously large polygon with 100 billion vertices that you call BigPolygon. It’s huge, and you need to use it in your design 250 times. Well, a single copy of BigPolygon takes up 1MB of memory, so you don’t want to make 250 copies of it You can 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, position, rotation and mirror. This way, you can keep one copy of BigPolygon and use it again and again.

You can start by making a blank Component and add a single polygon to it.

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk

# Create a blank Component
p = gf.Component("component_with_polygon")

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

# plot the Component with the polygon in it
p.plot()
2024-03-09 00:43:08.249 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/component_with_polygon.lyp'.
../_images/bd06862bffb996008bebb39b7b51347a48522f77d49397a6d896f82059aef938.png

Now, you want to reuse this polygon repeatedly without creating multiple copies of it.

To do so, you need to make a second blank Component, this time called c.

In this new Component you reference our Component p which contains our polygon.

c = gf.Component("Component_with_references")  # Create a new blank Component
poly_ref = c.add_ref(p)  # Reference the Component "p" that has the polygon in it
c.plot()
2024-03-09 00:43:08.393 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Component_with_references.lyp'.
../_images/bd06862bffb996008bebb39b7b51347a48522f77d49397a6d896f82059aef938.png

you just made a copy of your polygon – but remember, you didn’t actually make a second polygon, you just made a reference (aka pointer) to the original polygon. Let’s add two more references to c:

poly_ref2 = c.add_ref(p)  # Reference the Component "p" that has the polygon in it
poly_ref3 = c.add_ref(p)  # Reference the Component "p" that has the polygon in it
c.plot()
2024-03-09 00:43:08.585 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Component_with_references.lyp'.
../_images/bd06862bffb996008bebb39b7b51347a48522f77d49397a6d896f82059aef938.png

Now you have 3x polygons all on top of each other. Again, this would appear useless, except that you can manipulate each reference independently. Notice that when you called c.add_ref(p) above, we saved the result to a new variable each time (poly_ref, poly_ref2, and poly_ref3)? You can use those variables to reposition the references.

poly_ref2.rotate(90)  # Rotate the 2nd reference we made 90 degrees
poly_ref3.rotate(180)  # Rotate the 3rd reference we made 180 degrees
c.plot()
2024-03-09 00:43:08.874 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Component_with_references.lyp'.
../_images/09feed37bfe651509d2ee37428f6adce61b26e7bd4e4215baf1cf860c5984d01.png

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

Modifying the referenced geometry#

What happens when you change the original geometry that the reference points to? In your case, your references in c all point to the Component p that with the original polygon. Let’s try adding a second polygon to p.

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

# Add a 2nd polygon to "p"
xpts = [14, 14, 16, 16]
ypts = [0, 2, 2, 0]
p.add_polygon([xpts, ypts], layer=(1, 0))
p.plot()
2024-03-09 00:43:09.054 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/component_with_polygon.lyp'.
../_images/77d124159ca12a267b2af861cfb957d3e5543113d194fd29d64eeda762ed6933.png

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

c.plot()
2024-03-09 00:43:09.238 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Component_with_references.lyp'.
../_images/74845ab38addbbae7c3bdffa22a3198dc926137a33c8eb76e5b6d020fc4f49dd.png

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

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

c2 = gf.Component("array_sample")  # Create a new blank Component
d_ref1 = c2.add_ref(c)  # Reference the Component "c" that 3 references in it
d_ref2 = c2 << c  # Use the "<<" operator to create a 2nd reference to c.plot()
d_ref3 = c2 << c  # Use the "<<" operator to create a 3rd reference to c.plot()

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

c2.plot()
2024-03-09 00:43:09.433 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/array_sample.lyp'.
../_images/93a52d851997b466907e7d9fe04f0d14e8a659aa53f66b8c87b481bc5c9b908c.png

As you’ve seen you have two ways to add a reference to our component:

  1. create the reference and add it to the component

c = gf.Component("reference_sample")
w = gf.components.straight(width=0.6)
wr = w.ref()
c.add(wr)
c.plot()
2024-03-09 00:43:09.625 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/reference_sample.lyp'.
../_images/30cc45359f18592bea7a3a9b4c7daf81bccdedf46fa4482d0cebf01543953316.png
  1. or do it in a single line

c = gf.Component("reference_sample_shorter_syntax")
wr = c << gf.components.straight(width=0.6)
c.plot()
2024-03-09 00:43:09.809 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/reference_sample_shorter_syntax.lyp'.
../_images/30cc45359f18592bea7a3a9b4c7daf81bccdedf46fa4482d0cebf01543953316.png

in both cases you can move the reference wr after created

c = gf.Component("two_references")
wr1 = c << gf.components.straight(width=0.6)
wr2 = c << gf.components.straight(width=0.6)
wr2.movey(10)
c.add_ports(wr1.get_ports_list(), prefix="bot_")
c.add_ports(wr2.get_ports_list(), prefix="top_")
c.ports
{'bot_o1': {'name': 'bot_o1', 'width': 0.6, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'bot_o2': {'name': 'bot_o2', 'width': 0.6, 'center': [10.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 'top_o1': {'name': 'top_o1', 'width': 0.6, 'center': [0.0, 10.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'top_o2': {'name': 'top_o2', 'width': 0.6, 'center': [10.0, 10.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}

You can also auto_rename ports using gdsfactory default convention, where ports are numbered clockwise starting from the bottom left

c.auto_rename_ports()
c.ports
{'o1': {'name': 'o1', 'width': 0.6, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o4': {'name': 'o4', 'width': 0.6, 'center': [10.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.6, 'center': [0.0, 10.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o3': {'name': 'o3', 'width': 0.6, 'center': [10.0, 10.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
c.plot()
2024-03-09 00:43:10.018 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/two_references.lyp'.
../_images/e39206240705ed1c74731b43bbbaf5a7f681408ba62f28c42bc358f816eaf8e5.png

Arrays of references#

In GDS, there’s a type of structure called a “ComponentReference” which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, Component includes this functionality with the add_array() function. Note that CellArrays are not compatible with ports (since there is no way to access/modify individual elements in a GDS cellarray)

gdsfactory also provides with more flexible arrangement options if desired, see for example grid() and packer().

As well as gf.components.array

Let’s make a new Component and put a big array of our Component c in it:

c3 = gf.Component("array_of_references")  # Create a new blank Component
aref = c3.add_array(
    c, columns=6, rows=3, spacing=[20, 15]
)  # Reference the Component "c" 3 references in it with a 3 rows, 6 columns array
c3.plot()
2024-03-09 00:43:10.199 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/array_of_references.lyp'.
../_images/5fc07e0c471d9700fcfad1db345299ed3e6f791f8550acaeb24f70a3b37e4919.png

CellArrays don’t have ports and there is no way to access/modify individual elements in a GDS cellarray.

gdsfactory provides you with similar functions in gf.components.array and gf.components.array_2d

c4 = gf.Component("demo_array")  # Create a new blank Component
aref = c4 << gf.components.array(component=c, columns=3, rows=2)
c4.add_ports(aref.get_ports_list())
c4.plot()
2024-03-09 00:43:10.395 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_array.lyp'.
../_images/3878ab1a5e07c50d1b6e05b7a0448b21274d0f92bf44a6a03499d5bfbf42074d.png
help(gf.components.array)
Help on function array in module gdsfactory.components.array_component:

array(component: 'ComponentSpec' = 'pad', spacing: 'tuple[float, float]' = (150.0, 150.0), columns: 'int' = 6, rows: 'int' = 1, add_ports: 'bool' = True, size: 'Float2 | None' = None) -> 'Component'
    Returns an array of components.
    
    Args:
        component: to replicate.
        spacing: x, y spacing.
        columns: in x.
        rows: in y.
        add_ports: add ports from component into the array.
        size: Optional x, y size. Overrides columns and rows.
    
    Raises:
        ValueError: If columns > 1 and spacing[0] = 0.
        ValueError: If rows > 1 and spacing[1] = 0.
    
    .. code::
    
        2 rows x 4 columns
         ___        ___       ___          ___
        |   |      |   |     |   |        |   |
        |___|      |___|     |___|        |___|
    
         ___        ___       ___          ___
        |   |      |   |     |   |        |   |
        |___|      |___|     |___|        |___|

You can also create an array of references for periodic structures. Let’s create a Distributed Bragg Reflector

@gf.cell
def dbr_period(w1=0.5, w2=0.6, l1=0.2, l2=0.4, straight=gf.components.straight):
    """Return one DBR period."""
    c = gf.Component()
    r1 = c << straight(length=l1, width=w1)
    r2 = c << straight(length=l2, width=w2)
    r2.connect(port="o1", destination=r1.ports["o2"])
    c.add_port("o1", port=r1.ports["o1"])
    c.add_port("o2", port=r2.ports["o2"])
    return c


l1 = 0.2
l2 = 0.4
n = 3
period = dbr_period(l1=l1, l2=l2)
period.plot()
2024-03-09 00:43:10.574 | WARNING  | gdsfactory.component_reference:connect:812 - UserWarning: Port width mismatch: 0.6 != 0.5 in straight_length0p4_width0p6 on layer (1, 0)
2024-03-09 00:43:10.594 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/dbr_period.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component_reference.py:812: UserWarning: Port width mismatch: 0.6 != 0.5 in straight_length0p4_width0p6 on layer (1, 0)
  warnings.warn(message)
../_images/7d885283b5f177fa7294c0753caa973bec35eaf3aaf805a7433d8bc93f696eb8.png
dbr = gf.Component("DBR")
dbr.add_array(period, columns=n, rows=1, spacing=(l1 + l2, 100))
dbr.plot()
2024-03-09 00:43:10.780 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/DBR.lyp'.
../_images/a0c61c571664e8b3d8686d7bd0f8cdff3a6312da638ad0c23af5ff14fe04395c.png

Finally we need to add ports to the new component

p0 = dbr.add_port("o1", port=period.ports["o1"])
p1 = dbr.add_port("o2", port=period.ports["o2"])

p1.center = [(l1 + l2) * n, 0]
dbr.plot()
2024-03-09 00:43:10.968 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/DBR.lyp'.
../_images/001d101d0c66e63ca567afa16b4dd42f5ccd50a513216f95fd06e878eb4e335c.png

Connect references#

We have seen that once you create a reference you can manipulate the reference to move it to a location. Here we are going to connect that reference to a port. Remember that we follow that a certain reference source connects to a destination port

bend = gf.components.bend_circular()
bend.plot()
2024-03-09 00:43:11.159 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_circular.lyp'.
../_images/0f4fe2c6ca36e7119143aac8ccf7bd4342b57975b9c06e7176bf1f9a95a40bb4.png
c = gf.Component("sample_reference_connect")

mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", destination=mmi.ports["o2"])

c.add_port("o1", port=mmi.ports["o1"])
c.add_port("o2", port=b.ports["o2"])
c.add_port("o3", port=mmi.ports["o3"])
c.plot()
2024-03-09 00:43:11.359 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_reference_connect.lyp'.
../_images/3002b3a428e7b6110b3a964a5e6d3ef334a398386c90f1530e853a1c2e0735d4.png

You can also access the ports directly from the references

c = gf.Component("sample_reference_connect_simpler")

mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", destination=mmi["o2"])

c.add_port("o1", port=mmi["o1"])
c.add_port("o2", port=b["o2"])
c.add_port("o3", port=mmi["o3"])
c.plot()
2024-03-09 00:43:11.664 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_reference_connect_simpler.lyp'.
../_images/3002b3a428e7b6110b3a964a5e6d3ef334a398386c90f1530e853a1c2e0735d4.png

Notice that connect mates two ports together and does not imply that ports will remain connected.

Port#

You can name the ports as you want and use gf.port.auto_rename_ports(prefix='o') to rename them later on.

Here is the default naming convention.

Ports are numbered clock-wise starting from the bottom left corner.

Optical ports have o prefix and Electrical ports e prefix.

The port naming comes in most cases from the gdsfactory.cross_section. For example:

  • gdsfactory.cross_section.strip has ports o1 for input and o2 for output.

  • gdsfactory.cross_section.metal1 has ports e1 for input and e2 for output.

size = 4
c = gf.components.nxn(west=2, south=2, north=2, east=2, xsize=size, ysize=size)
c.plot()
2024-03-09 00:43:11.850 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/nxn_afe554f8.lyp'.
../_images/caa06d45840fd36502f8605c2d48bf53a75d1a4aab04d5030a88a21810ae49a1.png
c = gf.components.straight_heater_metal(length=30)
c.plot()
2024-03-09 00:43:12.053 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_heater_metal_undercut_690cad76.lyp'.
../_images/ad5d8eab96401f5d00b419729c50320831bb33761ead61d658902a0916b31a9d.png
c.ports
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.5, 'center': [30.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 'l_e1': {'name': 'l_e1', 'width': 11.0, 'center': [-15.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e2': {'name': 'l_e2', 'width': 11.0, 'center': [-10.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e3': {'name': 'l_e3', 'width': 11.0, 'center': [-4.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e4': {'name': 'l_e4', 'width': 11.0, 'center': [-10.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e1': {'name': 'r_e1', 'width': 11.0, 'center': [34.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e2': {'name': 'r_e2', 'width': 11.0, 'center': [40.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e3': {'name': 'r_e3', 'width': 11.0, 'center': [45.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e4': {'name': 'r_e4', 'width': 11.0, 'center': [40.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'}}

You can get the optical ports by layer

c.get_ports_dict(layer=(1, 0))
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.5, 'center': [30.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}

or by width

c.get_ports_dict(width=0.5)
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.5, 'center': [30.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
c0 = gf.components.straight_heater_metal()
c0.ports
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.5, 'center': [320.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 'l_e1': {'name': 'l_e1', 'width': 11.0, 'center': [-15.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e2': {'name': 'l_e2', 'width': 11.0, 'center': [-10.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e3': {'name': 'l_e3', 'width': 11.0, 'center': [-4.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'l_e4': {'name': 'l_e4', 'width': 11.0, 'center': [-10.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e1': {'name': 'r_e1', 'width': 11.0, 'center': [324.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e2': {'name': 'r_e2', 'width': 11.0, 'center': [330.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e3': {'name': 'r_e3', 'width': 11.0, 'center': [335.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'r_e4': {'name': 'r_e4', 'width': 11.0, 'center': [330.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'}}
c1 = c0.copy()
c1.auto_rename_ports_layer_orientation()
c1.ports
{'1_0_W0': {'name': '49_0_W1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 '1_0_E0': {'name': '49_0_E1', 'width': 0.5, 'center': [320.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 '49_0_W0': {'name': '49_0_W0', 'width': 11.0, 'center': [-15.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_N0': {'name': '49_0_N0', 'width': 11.0, 'center': [-10.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_E0': {'name': '49_0_E0', 'width': 11.0, 'center': [-4.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_S0': {'name': '49_0_S0', 'width': 11.0, 'center': [-10.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_W2': {'name': '49_0_W2', 'width': 11.0, 'center': [324.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_N1': {'name': '49_0_N1', 'width': 11.0, 'center': [330.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_E2': {'name': '49_0_E2', 'width': 11.0, 'center': [335.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 '49_0_S1': {'name': '49_0_S1', 'width': 11.0, 'center': [330.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'}}
c2 = c0.copy()
c2.auto_rename_ports()
c2.ports
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 'o2': {'name': 'o2', 'width': 0.5, 'center': [320.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 'e1': {'name': 'e1', 'width': 11.0, 'center': [-15.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e3': {'name': 'e3', 'width': 11.0, 'center': [-10.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e5': {'name': 'e5', 'width': 11.0, 'center': [-4.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e8': {'name': 'e8', 'width': 11.0, 'center': [-10.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e2': {'name': 'e2', 'width': 11.0, 'center': [324.9, 0.0], 'orientation': 180.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e4': {'name': 'e4', 'width': 11.0, 'center': [330.4, 5.5], 'orientation': 90.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e6': {'name': 'e6', 'width': 11.0, 'center': [335.9, 0.0], 'orientation': 0.0, 'layer': [49, 0], 'port_type': 'electrical'},
 'e7': {'name': 'e7', 'width': 11.0, 'center': [330.4, -5.5], 'orientation': 270.0, 'layer': [49, 0], 'port_type': 'electrical'}}

You can also rename them with a different port naming convention

  • prefix: add e for electrical o for optical

  • clockwise

  • counter-clockwise

  • orientation E East, W West, N North, S South

Here is the default one we use (clockwise starting from bottom left west facing port)

             3   4
             |___|_
         2 -|      |- 5
            |      |
         1 -|______|- 6
             |   |
             8   7

c = gf.Component("demo_ports")
nxn = gf.components.nxn(west=2, north=2, east=2, south=2, xsize=4, ysize=4)
ref = c.add_ref(nxn)
c.add_ports(ref.ports)
c.plot()
2024-03-09 00:43:12.291 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_ports.lyp'.
../_images/caa06d45840fd36502f8605c2d48bf53a75d1a4aab04d5030a88a21810ae49a1.png
ref.get_ports_list()  # by default returns ports clockwise starting from bottom left west facing port
[{'name': 'o1', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o2', 'width': 0.5, 'center': [0.0, 2.75], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o3', 'width': 0.5, 'center': [1.25, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o4', 'width': 0.5, 'center': [2.75, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o5', 'width': 0.5, 'center': [4.0, 2.75], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o6', 'width': 0.5, 'center': [4.0, 1.25], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o7', 'width': 0.5, 'center': [2.75, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'},
 {'name': 'o8', 'width': 0.5, 'center': [1.25, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'}]
c.auto_rename_ports()
c.plot()
2024-03-09 00:43:12.485 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_ports.lyp'.
../_images/caa06d45840fd36502f8605c2d48bf53a75d1a4aab04d5030a88a21810ae49a1.png

Lets extend the East facing ports (orientation = 0 deg)

cross_section = gf.cross_section.strip()

nxn = gf.components.nxn(
    west=2, north=2, east=2, south=2, xsize=4, ysize=4, cross_section=cross_section
)
c = gf.components.extension.extend_ports(component=nxn, orientation=0)
c.plot()
2024-03-09 00:43:12.684 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/nxn_extend_ports_98f616c8.lyp'.
../_images/f58c42e1fc0c85170e74f2302683329f46b62395901993d47ac90494499ad495.png
c.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  center       orientation  layer   port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.5   │ [0.0, 1.25] │ 180         │ [1, 0] │ optical   │
│ o2   │ 0.5   │ [0.0, 2.75] │ 180         │ [1, 0] │ optical   │
│ o3   │ 0.5   │ [1.25, 4.0] │ 90          │ [1, 0] │ optical   │
│ o4   │ 0.5   │ [2.75, 4.0] │ 90          │ [1, 0] │ optical   │
│ o5   │ 0.5   │ [9.0, 2.75] │ 0.0         │ [1, 0] │ optical   │
│ o6   │ 0.5   │ [9.0, 1.25] │ 0.0         │ [1, 0] │ optical   │
│ o7   │ 0.5   │ [2.75, 0.0] │ 270         │ [1, 0] │ optical   │
│ o8   │ 0.5   │ [1.25, 0.0] │ 270         │ [1, 0] │ optical   │
└──────┴───────┴─────────────┴─────────────┴────────┴───────────┘
df = c.get_ports_pandas()
df
name width center orientation layer port_type shear_angle
0 o1 0.5 [0.0, 1.25] 180.0 [1, 0] optical None
1 o2 0.5 [0.0, 2.75] 180.0 [1, 0] optical None
2 o3 0.5 [1.25, 4.0] 90.0 [1, 0] optical None
3 o4 0.5 [2.75, 4.0] 90.0 [1, 0] optical None
4 o5 0.5 [9.0, 2.75] 0.0 [1, 0] optical None
5 o6 0.5 [9.0, 1.25] 0.0 [1, 0] optical None
6 o7 0.5 [2.75, 0.0] 270.0 [1, 0] optical None
7 o8 0.5 [1.25, 0.0] 270.0 [1, 0] optical None
df[df.port_type == "optical"]
name width center orientation layer port_type shear_angle
0 o1 0.5 [0.0, 1.25] 180.0 [1, 0] optical None
1 o2 0.5 [0.0, 2.75] 180.0 [1, 0] optical None
2 o3 0.5 [1.25, 4.0] 90.0 [1, 0] optical None
3 o4 0.5 [2.75, 4.0] 90.0 [1, 0] optical None
4 o5 0.5 [9.0, 2.75] 0.0 [1, 0] optical None
5 o6 0.5 [9.0, 1.25] 0.0 [1, 0] optical None
6 o7 0.5 [2.75, 0.0] 270.0 [1, 0] optical None
7 o8 0.5 [1.25, 0.0] 270.0 [1, 0] optical None

Port markers (Pins)#

You can add pins (port markers) to each port. Different foundries do this differently, so gdsfactory supports all of them.

  • square with port inside the component.

  • square centered (half inside, half outside component).

  • triangular pointing towards the outside of the port.

  • path (SiEPIC).

by default Component.show() will add triangular pins, so you can see the direction of the port in Klayout.

c = gf.components.mmi1x2()
c.plot()
2024-03-09 00:43:12.907 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mmi1x2.lyp'.
../_images/5831eaaee87f93da564b8600f149b9287cf4dd6adaa8e43199b057c31c5b052b.png

Component_sequence#

When you have repetitive connections you can describe the connectivity as an ASCII map

bend180 = gf.components.bend_circular180()
wg_pin = gf.components.straight_pin(length=40)
wg = gf.components.straight()

# Define a map between symbols and (component, input port, output port)
symbol_to_component = {
    "D": (bend180, "o1", "o2"),
    "C": (bend180, "o2", "o1"),
    "P": (wg_pin, "o1", "o2"),
    "-": (wg, "o1", "o2"),
}

# Generate a sequence
# This is simply a chain of characters. Each of them represents a component
# with a given input and and a given output

sequence = "DC-P-P-P-P-CD"
component = gf.components.component_sequence(
    sequence=sequence, symbol_to_component=symbol_to_component
)
component.name = "component_sequence"
component.plot()
2024-03-09 00:43:13.120 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/component_sequence.lyp'.
../_images/dff04f52191e46b2908d0bd93ed551940c4878ccd71f66121db9161964d54e29.png

You can install the klayout klive plugin to be able to see live updates on your GDS files:

KLayout package

Movement#

You can move, rotate and mirror ComponentReference as well as Port, Polygon, ComponentReference, Label, and Group

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk

# Start with a blank Component
c = gf.Component("demo_movement")

# Create some more Components with shapes
T = gf.components.text("hello", size=10, layer=(1, 0))
E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
R = gf.components.rectangle(size=(10, 3), layer=(3, 0))

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

c.plot()
2024-03-09 00:43:13.311 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_movement.lyp'.
../_images/f5b8df089e34cc5a9ea0aba1188386c7b3473b855ca42bf37cc09705084a57c2.png
c = gf.Component("move_one_ellipse")
e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1.movex(10)
c.plot()
2024-03-09 00:43:13.505 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/move_one_ellipse.lyp'.
../_images/0692ecd7bf15e9c1775e736f10e6c6c14e7d43a4ab0c83c39a918d099df77988.png
c = gf.Component("move_one_ellipse_xmin")
e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2.xmin = e1.xmax
c.plot()
2024-03-09 00:43:13.693 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/move_one_ellipse_xmin.lyp'.
../_images/04ac6b03e00d4ceb63bd6defee89a3b0b2149482eae3d14eef69e81e850d61c0.png

Now you can practice move and rotate the objects.

c = gf.Component("two_ellipses_on_top_of_each_other")
E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << E
e2 = c << E
c.plot()
2024-03-09 00:43:13.883 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/two_ellipses_on_top_of_each_other.lyp'.
../_images/a587c1552790109a6c6b63a9414d90c5efe8a7bc6f4875ac45f53b32f66a31bd.png
c = gf.Component("ellipse_moved")
e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << e
e2 = c << e
e2.move(origin=[5, 5], destination=[10, 10])  # Translate by dx = 5, dy = 5
c.plot()
2024-03-09 00:43:14.074 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ellipse_moved.lyp'.
../_images/bbf70f07c10b92b14d8e48e773a553f1bb098b8bc4c91ac31f00a72f9ff54e4b.png
c = gf.Component("ellipse_moved_v2")
e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << e
e2 = c << e
e2.move([5, 5])  # Translate by dx = 5, dy = 5
c.plot()
2024-03-09 00:43:14.268 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ellipse_moved_v2.lyp'.
../_images/bbf70f07c10b92b14d8e48e773a553f1bb098b8bc4c91ac31f00a72f9ff54e4b.png
c = gf.Component("rectangles")
r = gf.components.rectangle(size=(10, 5), layer=(2, 0))
rect1 = c << r
rect2 = c << r

rect1.rotate(45)  # Rotate the first straight by 45 degrees around (0,0)
rect2.rotate(
    -30, center=[1, 1]
)  # Rotate the second straight by -30 degrees around (1,1)
c.plot()
2024-03-09 00:43:14.556 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component rectangles has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:43:14.576 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rectangles.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component rectangles has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(
../_images/65305ea62094365fba6162115466adefc534a7ddc6d0a12b5496e6625a86bf6c.png
c = gf.Component("mirror_demo")
text = c << gf.components.text("hello")
text.mirror(p1=[1, 1], p2=[1, 3])  # Reflects across the line formed by p1 and p2
c.plot()
2024-03-09 00:43:14.771 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mirror_demo.lyp'.
../_images/52834538b44e12b3a6768940d9d1a3ba83d01153fc85d0e6841bfff1a1b12005.png
c = gf.Component("hello")
text = c << gf.components.text("hello")
c.plot()
2024-03-09 00:43:14.961 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/hello.lyp'.
../_images/4e0d36f0b70f0d3767e6ee240f819ff72074e0aa6d719e11c665f65d4d48f48c.png

Each Component and ComponentReference 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

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(text.xmax)  # Gives you the rightmost (+x) edge of the text bounding box
bounding box:
[[ 1.  0.]
 [34. 11.]]
xsize and ysize:
33.0
11.0
center:
[17.5  5.5]
xmax
34.0

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

c = gf.Component("canvas")
text = c << gf.components.text("hello")
E = gf.components.ellipse(radii=(10, 5), layer=(3, 0))
R = gf.components.rectangle(size=(10, 5), layer=(2, 0))
rect1 = c << R
rect2 = c << R
ellipse = c << E

c.plot()
2024-03-09 00:43:15.164 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/canvas.lyp'.
../_images/804c0a668ec20fa15a1206a4e8996e0d785304b3a4de9907be3466d55cc023b0.png
# 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

c.plot()
2024-03-09 00:43:15.358 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/canvas.lyp'.
../_images/765a3c795e84c45cbaeba697d888263c8d0c4d9ff20ccac281cc089eac6d8d44.png

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

print(c.xmax)  # Prints out '10.0'

c2 = c.mirror((0, 1))  # Mirror across line made by (0,0) and (0,1)
c2.plot()
10.0
2024-03-09 00:43:15.551 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/_mirror_componentcanvas.lyp'.
../_images/a13bfaa6181142b7f6eedf5d4348e229bc4b7278bbd4e2350ec3df77161fb071.png

A bounding box is the smallest enclosing box which contains all points of the geometry.

c = gf.Component("hi_bbox")
text = c << gf.components.text("hi")
bbox = text.bbox
c << gf.components.bbox(bbox=bbox, layer=(2, 0))
c.plot()
2024-03-09 00:43:15.744 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/hi_bbox.lyp'.
../_images/eeaa1e344c1a8fefe8bb59e265986fa9fff77a86e091334a95ef6a93aed1f484.png
# gf.get_padding_points can also add a bbox with respect to the bounding box edges
c = gf.Component("sample_padding")
text = c << gf.components.text("bye")
device_bbox = text.bbox
c.add_polygon(gf.get_padding_points(text, default=1), layer=(2, 0))
c.plot()
2024-03-09 00:43:15.937 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_padding.lyp'.
../_images/d814b6155af097a55ae6f2a7bc4feed857e78817ed52388be89b0a4d79af4ca4.png

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

print("Center of Component c:")
print(c.center)

print("X-max of Component c:")
print(c.xmax)
Center of Component c:
[12.   3.5]
X-max of Component c:
24.0
D = gf.Component("rect")
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = D << R
D.plot()
2024-03-09 00:43:16.136 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rect.lyp'.
../_images/54d8792c2e0b43218ea1d599b1dad93f3521dc72916ddeff50a79200d1be6a90.png

You can chain many of the movement/manipulation functions because they all return the object they manipulate.

For instance you can combine two expressions:

rect1.rotate(angle=37)
rect1.move([10, 20])
D.plot()
2024-03-09 00:43:16.297 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component rect has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:43:16.318 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rect.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component rect has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(
../_images/770d8d9340e1be490de5a2f12cacef5172ff87fc35a978cf6238eaaee6ac6c01.png

…into this single-line expression

D = gf.Component("single_expression")
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = D << R
rect1.rotate(angle=37).move([10, 20])
D.plot()
2024-03-09 00:43:16.491 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component single_expression has invalid transformations. Try component.flatten_offgrid_references() first.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component single_expression has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(
2024-03-09 00:43:16.512 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/single_expression.lyp'.
../_images/770d8d9340e1be490de5a2f12cacef5172ff87fc35a978cf6238eaaee6ac6c01.png

Cell#

A cell is a function that returns a Component.

Make sure you add the @cell decorator to each function that returns a Component.

@cell comes from PCell parametric cell, where the function returns a different Component depending on the input parameters.

Why do we need cells?

  • In GDS each component must have a unique name. Ideally the name is also consistent from run to run, in case you want to merge GDS files that were created at different times or computers.

  • Two components stored in the GDS file cannot have the same name. They need to be references (instances) of the same component. See References tutorial. That way we only have to store the component in memory once and all the references are just pointers to that component.

What does the @cell decorator does?

  1. Gives the component a unique name depending on the parameters that you pass to it.

  2. Creates a cache of components where we use the name as the key. The first time the function runs, the cache stores the component, so the second time, you get the component directly from the cache, so you don’t create the same component twice.

A decorator is a function that runs over a function, so when you do.

@gf.cell
def mzi_with_bend():
    c = gf.Component()
    mzi = c << gf.components.mzi()
    bend = c << gf.components.bend_euler()
    return c

it’s equivalent to

def mzi_with_bend():
    c = gf.Component()
    mzi = c << gf.components.mzi()
    bend = c << gf.components.bend_euler(radius=radius)
    return c


mzi_with_bend_decorated = gf.cell(mzi_with_bend)

Lets see how it works.

import gdsfactory as gf
from gdsfactory.cell import print_cache
from gdsfactory.generic_tech import get_generic_pdk


def mzi_with_bend(radius: float = 10.0) -> gf.Component:
    c = gf.Component("Unnamed_cells_can_cause_issues")
    mzi = c << gf.components.mzi()
    bend = c << gf.components.bend_euler(radius=radius)
    bend.connect("o1", mzi.ports["o2"])
    return c


c = mzi_with_bend()
print(f"this cell {c.name!r} does NOT get automatic name")
c.plot()
this cell 'Unnamed_cells_can_cause_issues' does NOT get automatic name
2024-03-09 00:43:16.717 | WARNING  | gdsfactory.component:_write_library:1998 - UserWarning: Unnamed cells, 1 in 'Unnamed_cells_can_cause_issues'
2024-03-09 00:43:16.732 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Unnamed_cells_can_cause_issues.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1663: UserWarning: Unnamed cells, 1 in 'Unnamed_cells_can_cause_issues'
  gdspath = component.write_gds(logging=False)
../_images/6455cf383377d15630d493368826b550f504aa6f4c4228db28e718904b997471.png
mzi_with_bend_decorated = gf.cell(mzi_with_bend)
c = mzi_with_bend_decorated(radius=10)
print(f"this cell {c.name!r} gets automatic name thanks to the `cell` decorator")
c.plot()
this cell 'mzi_with_bend_radius10' gets automatic name thanks to the `cell` decorator
2024-03-09 00:43:16.925 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_with_bend_radius10.lyp'.
../_images/6455cf383377d15630d493368826b550f504aa6f4c4228db28e718904b997471.png
@gf.cell
def mzi_with_bend(radius: float = 10.0) -> gf.Component:
    c = gf.Component()
    mzi = c << gf.components.mzi()
    bend = c << gf.components.bend_euler(radius=radius)
    bend.connect("o1", mzi.ports["o2"])
    return c


print(f"this cell {c.name!r} gets automatic name thanks to the `cell` decorator")
c.plot()
this cell 'mzi_with_bend_radius10' gets automatic name thanks to the `cell` decorator
2024-03-09 00:43:17.244 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_with_bend_radius10.lyp'.
../_images/6455cf383377d15630d493368826b550f504aa6f4c4228db28e718904b997471.png

Sometimes when you are changing the inside code of the function, you need to remove the component from the cache to make sure the code runs again.

This is useful when using jupyter notebooks or the file watcher

@gf.cell
def wg(length=10, width=1, layer=(1, 0)):
    print("BUILDING waveguide")
    c = gf.Component()
    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)
    c.add_port(
        name="o1", center=[0, width / 2], width=width, orientation=180, layer=layer
    )
    c.add_port(
        name="o2", center=[length, width / 2], width=width, orientation=0, layer=layer
    )
    return c
c = wg()
gf.remove_from_cache(c)
c = wg()
gf.remove_from_cache(c)
c = wg()
gf.remove_from_cache(c)
BUILDING waveguide
BUILDING waveguide
BUILDING waveguide

Path and CrossSection#

You can create a Path in gdsfactory and extrude it with an arbitrary CrossSection.

Lets create a path:

  • Create a blank Path.

  • Append points to the Path either using the built-in functions (arc(), straight(), euler() …) or by providing your own lists of points

  • Specify CrossSection with layers and offsets.

  • Extrude Path with a CrossSection to create a Component with the path polygons in it.

from functools import partial

import matplotlib.pyplot as plt
import numpy as np

import gdsfactory as gf
from gdsfactory.cross_section import Section
from gdsfactory.generic_tech import get_generic_pdk

Path#

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.

p1 = gf.path.straight(length=5)
p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)
p = p1 + p2
f = p.plot()
../_images/3a23b9fa12c5faa8e598a15d627b1d0ee5065af2dcfb6516c510f7d4d237ddea.png
p1 = gf.path.straight(length=5)
p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)
p = p2 + p1
f = p.plot()
../_images/a30984f9e43c3df523900e419ed12b7ecd3b93315490ea33744191f997d8c3fd.png
P = gf.Path()
P += gf.path.arc(radius=10, angle=90)  # Circular arc
P += gf.path.straight(length=10)  # Straight section
P += gf.path.euler(radius=3, angle=-90)  # Euler bend (aka "racetrack" curve)
P += gf.path.straight(length=40)
P += gf.path.arc(radius=8, angle=-45)
P += gf.path.straight(length=10)
P += gf.path.arc(radius=8, angle=45)
P += gf.path.straight(length=10)

f = P.plot()
../_images/addf5103cf389c3bee2c9cd09bd101278673b2a4bec54f66ebd3aeb6ec63c7a8.png
p2 = P.copy().rotate()
f = p2.plot()
../_images/2276a0b4f03f3a3ba1110b5b92c22321cacc575dd5a9ed923ebad93cc2274cec.png
P.points - p2.points
array([[  0.        ,   0.        ],
       [  0.07775818,  -0.18109421],
       [  0.16015338,  -0.36012627],
       [  0.24713097,  -0.53697746],
       [  0.33863327,  -0.71153054],
       [  0.43459961,  -0.88366975],
       [  0.53496636,  -1.05328096],
       [  0.63966697,  -1.2202517 ],
       [  0.74863202,  -1.38447127],
       [  0.86178925,  -1.54583076],
       [  0.97906364,  -1.7042232 ],
       [  1.10037742,  -1.85954356],
       [  1.22565016,  -2.01168884],
       [  1.35479879,  -2.16055818],
       [  1.48773767,  -2.30605285],
       [  1.62437867,  -2.44807638],
       [  1.76463118,  -2.58653461],
       [  1.9084022 ,  -2.72133573],
       [  2.0555964 ,  -2.85239035],
       [  2.20611618,  -2.97961158],
       [  2.35986175,  -3.10291506],
       [  2.51673115,  -3.22221903],
       [  2.67662037,  -3.33744439],
       [  2.83942339,  -3.44851474],
       [  3.00503227,  -3.55535642],
       [  3.1733372 ,  -3.6578986 ],
       [  3.34422657,  -3.75607328],
       [  3.51758709,  -3.84981537],
       [  3.69330379,  -3.93906271],
       [  3.87126017,  -4.02375613],
       [  4.05133823,  -4.10383946],
       [  4.23341856,  -4.1792596 ],
       [  4.41738044,  -4.24996655],
       [  4.60310189,  -4.31591343],
       [  4.79045976,  -4.3770565 ],
       [  4.97932982,  -4.43335522],
       [  5.16958684,  -4.48477228],
       [  5.36110467,  -4.53127356],
       [  5.55375631,  -4.57282824],
       [  5.74741403,  -4.60940877],
       [  5.94194941,  -4.64099089],
       [  6.13723348,  -4.66755366],
       [  6.33313674,  -4.68907946],
       [  6.52952929,  -4.70555403],
       [  6.72628092,  -4.71696644],
       [  6.92326117,  -4.72330911],
       [  7.12033942,  -4.72457786],
       [  7.317385  ,  -4.72077183],
       [  7.51426725,  -4.71189355],
       [  7.71085564,  -4.69794891],
       [  7.90701981,  -4.67894715],
       [  8.10262968,  -4.65490087],
       [  8.29755556,  -4.62582602],
       [  8.4916682 ,  -4.59174187],
       [  8.6848389 ,  -4.55267103],
       [  8.87693955,  -4.50863939],
       [  9.0678428 ,  -4.45967617],
       [  9.25742205,  -4.40581381],
       [  9.44555161,  -4.34708805],
       [  9.63210673,  -4.28353781],
       [  9.81696372,  -4.21520523],
       [ 10.        ,  -4.14213562],
       [ 17.07106781,  -1.21320344],
       [ 17.22259744,  -1.15062977],
       [ 17.3745295 ,  -1.0890412 ],
       [ 17.52724719,  -1.02943054],
       [ 17.68109502,  -0.9728054 ],
       [ 17.83635884,  -0.92019408],
       [ 17.99324531,  -0.87264941],
       [ 18.15186066,  -0.83125013],
       [ 18.31218874,  -0.79709882],
       [ 18.47406876,  -0.77131591],
       [ 18.63717281,  -0.75502883],
       [ 18.80098407,  -0.74935572],
       [ 18.98113405,  -0.75643383],
       [ 19.16017334,  -0.77762453],
       [ 19.33699811,  -0.81279716],
       [ 19.51051817,  -0.86173488],
       [ 19.67966373,  -0.92413597],
       [ 19.84339193,  -0.9996157 ],
       [ 20.00069333,  -1.08770872],
       [ 20.15059814,  -1.18787191],
       [ 20.29218212,  -1.29948772],
       [ 20.42457237,  -1.42186801],
       [ 20.53639293,  -1.54171156],
       [ 20.6402082 ,  -1.66856024],
       [ 20.73644339,  -1.80125797],
       [ 20.82566384,  -1.93877567],
       [ 20.90854811,  -2.08020737],
       [ 20.98586445,  -2.22476202],
       [ 21.05845073,  -2.37175194],
       [ 21.12719755,  -2.5205788 ],
       [ 21.19303416,  -2.67071762],
       [ 21.25691665,  -2.82169951],
       [ 21.31981802,  -2.97309339],
       [ 33.03554677, -31.25736464],
       [ 33.1091836 , -31.44370627],
       [ 33.17668408, -31.63235747],
       [ 33.23797592, -31.82311621],
       [ 33.29299349, -32.01577822],
       [ 33.34167789, -32.21013721],
       [ 33.38397696, -32.40598505],
       [ 33.41984543, -32.60311201],
       [ 33.44924488, -32.80130701],
       [ 33.47214384, -33.00035782],
       [ 33.48851777, -33.20005129],
       [ 33.49834914, -33.40017358],
       [ 33.50162744, -33.60051039],
       [ 33.49834914, -33.80084721],
       [ 33.48851777, -34.0009695 ],
       [ 33.47214384, -34.20066296],
       [ 33.44924488, -34.39971377],
       [ 33.41984543, -34.59790877],
       [ 33.38397696, -34.79503574],
       [ 33.34167789, -34.99088357],
       [ 33.29299349, -35.18524256],
       [ 33.23797592, -35.37790458],
       [ 33.17668408, -35.56866332],
       [ 33.1091836 , -35.75731451],
       [ 33.03554677, -35.94365614],
       [ 30.10661458, -43.01472396],
       [ 30.03297776, -43.20106559],
       [ 29.96547728, -43.38971678],
       [ 29.90418544, -43.58047552],
       [ 29.84916786, -43.77313754],
       [ 29.80048347, -43.96749653],
       [ 29.75818439, -44.16334436],
       [ 29.72231592, -44.36047132],
       [ 29.69291647, -44.55866633],
       [ 29.67001752, -44.75771713],
       [ 29.65364359, -44.9574106 ],
       [ 29.64381221, -45.15753289],
       [ 29.64053392, -45.35786971],
       [ 29.64381221, -45.55820652],
       [ 29.65364359, -45.75832881],
       [ 29.67001752, -45.95802228],
       [ 29.69291647, -46.15707309],
       [ 29.72231592, -46.35526809],
       [ 29.75818439, -46.55239505],
       [ 29.80048347, -46.74824289],
       [ 29.84916786, -46.94260187],
       [ 29.90418544, -47.13526389],
       [ 29.96547728, -47.32602263],
       [ 30.03297776, -47.51467382],
       [ 30.10661458, -47.70101546],
       [ 33.03554677, -54.77208327]])

You can also modify our Path in the same ways as any other gdsfactory object:

  • Manipulation with move(), rotate(), mirror(), etc

  • Accessing properties like xmin, y, center, bbox, etc

P.movey(10)
P.xmin = 20
f = P.plot()
../_images/62ef308343d3b7278765482570f12d8dfc040534ec87be70a8fdb70c9efda3f0.png

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

P.length()
105.34098399267245

CrossSection#

Now that you’ve got your path defined, the next step is to define the cross-section of the path. To do this, you can create a blank CrossSection and add whatever cross-sections you want to it. You can then combine the Path and the CrossSection using the gf.path.extrude() function to generate a Component:

Option 1: Single layer and 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:

# Extrude the Path and the CrossSection
c = gf.path.extrude(P, layer=(1, 0), width=1.5)
c.plot()
2024-03-09 00:43:18.015 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_bcab871c.lyp'.
../_images/043359dade72783725d807ae9b4170054347cffe5c0fe39572e56abd3f154269.png

Option 2: Arbitrary Cross-section#

You can also extrude an arbitrary cross_section

Now, what if we want a more complicated straight? For instance, in some photonic applications it’s helpful to have a shallow etch that appears on either side of the straight (often called a trench or 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. Let’s try adding something like that in:

p = gf.path.straight()

# Add a few "sections" to the cross-section
s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=("in", "out"))
s1 = gf.Section(width=2, offset=2, layer=(2, 0))
s2 = gf.Section(width=2, offset=-2, layer=(2, 0))
x = gf.CrossSection(sections=[s0, s1, s2])

c = gf.path.extrude(p, cross_section=x)
c.plot()
2024-03-09 00:43:18.161 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_726f5d95.lyp'.
../_images/597735726a1e61a01dc19854d393c7cc4b0b29fb7b7b5a04b13d16ea3ad8ad3e.png
p = gf.path.arc()

# Combine the Path and the CrossSection
b = gf.path.extrude(p, cross_section=x)
b.plot()
2024-03-09 00:43:18.302 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_1f37832e.lyp'.
../_images/1b684f3c2844a4e0be39eb4602cb8ddc29d9cc18faff8cedc230dae32a7e105b.png

An arbitrary cross-section can also help place components along a path. This component can be useful for defining wiring vias.

import gdsfactory as gf
from gdsfactory.cross_section import ComponentAlongPath

# Create the path
p = gf.path.straight()
p += gf.path.arc(10)
p += gf.path.straight()

# Define a cross-section with a via
via = ComponentAlongPath(
    component=gf.c.rectangle(size=(1, 1), centered=True), spacing=5, padding=2
)
s = gf.Section(width=0.5, offset=0, layer=(1, 0), port_names=("in", "out"))
x = gf.CrossSection(sections=[s], components_along_path=[via])

# Combine the path with the cross-section
c = gf.path.extrude(p, cross_section=x)
c.plot()
2024-03-09 00:43:18.451 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_6613dfe7.lyp'.
../_images/5965475c0e85d22a544aca542ca4ddc9cb0be644d24eeee589233888ecaee03f.png
import gdsfactory as gf
from gdsfactory.cross_section import ComponentAlongPath

# Create the path
p = gf.path.straight()
p += gf.path.arc(10)
p += gf.path.straight()

# Define a cross-section with a via
via0 = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=0)
viap = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=+2)
vian = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=-2)
x = gf.CrossSection(sections=[s], components_along_path=[via0, viap, vian])

# Combine the path with the cross-section
c = gf.path.extrude(p, cross_section=x)
c.plot()
2024-03-09 00:43:18.606 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_d8006cf4.lyp'.
../_images/0fd6307ff656bfdcb7ea1fb23ffd23ba7c9d1fcd316fa94aa3cf9aa7c9cabc5c.png

Building 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()

P = gf.Path()

# Create the basic Path components
left_turn = gf.path.euler(radius=4, angle=90)
right_turn = gf.path.euler(radius=4, angle=-90)
straight = gf.path.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,
    ]
)

f = P.plot()
../_images/d454f194b3b0649a6f1228f6fc6d8362d61781d920b775866a9f576bcc2c94b7.png
P = (
    straight
    + left_turn
    + straight
    + right_turn
    + straight
    + straight
    + right_turn
    + left_turn
    + straight
)
f = P.plot()
../_images/2708ef7f477bc23068ab42b58c676425a433fc0cb791ccc39f8f13f6cd62a52b.png

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

P = gf.Path()

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

P.append(s_turn)
f = P.plot()
../_images/9b27fc4e2af9f9a674fc11d26a06b40c2debb8d78eb5c39c8308da2a904ae2ad.png

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

P = gf.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)
f = P.plot()
../_images/b6d0fce36fa8a0f3965ebde521cd0c6e5f09e24ee983f9a4cd3d4d3ef073f321.png

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

P = gf.Path([straight, left_turn, straight, right_turn, straight])
f = P.plot()
../_images/a7c8bc2b1d29cff56785e7a561fa5b0fb81c9fe6457782d0e8ffbb221ac81143.png

Waypoint smooth paths#

You can also build smooth paths between waypoints with the smooth() function

points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])
plt.plot(points[:, 0], points[:, 1], ".-")
plt.axis("equal")
(17.5, 72.5, 8.5, 41.5)
../_images/83917b0a1d4ce16f81176b3a128e70b729beb7e24c7befb10453023074070f5f.png
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,  # Alternatively, use pp.arc
    use_eff=False,
)
f = P.plot()
../_images/5906dc3e13c6290f1f6e9808bdd6b96d8644113d479ad84066e78e075f530bc9.png

Waypoint sharp 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

P = gf.Path([(20, 10), (30, 10), (40, 30), (50, 30), (50, 20), (70, 20)])
f = P.plot()
../_images/0cc3b2677d6d6e5054f283ecbfdcb03ec25f0d3cfdd5cb6aa009119053366c74.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 (gf.path.straight(length, num_pts = 2)) otherwise the extrusion algorithm will show defects.

P = gf.Path()
P += gf.path.straight(length=10, npoints=2)
P.end_angle += 90  # "Turn" 90 deg (left)
P += gf.path.straight(length=10, npoints=2)  # "Walk" length of 10
P.end_angle += -135  # "Turn" -135 degrees (right)
P += gf.path.straight(length=15, npoints=2)  # "Walk" length of 10
P.end_angle = 0  # Force the direction to be 0 degrees
P += gf.path.straight(length=10, npoints=2)  # "Walk" length of 10
f = P.plot()
../_images/13c3df7e65b9632fe18eda9e5080b8d08a4a48d06aec63d66ea21b4063021d4a.png
s0 = gf.Section(width=1, offset=0, layer=(1, 0))
s1 = gf.Section(width=1.5, offset=2.5, layer=(2, 0))
s2 = gf.Section(width=1.5, offset=-2.5, layer=(3, 0))
X = gf.CrossSection(sections=[s0, s1, s2])
c = gf.path.extrude(P, X)
c.show()
c.plot()
2024-03-09 00:43:19.899 | WARNING  | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
2024-03-09 00:43:20.035 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_4f2acc50.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/klive.py:49: UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
  warnings.warn(
../_images/0af32effb79b8e467149ed80c914d2b30be702c4de008e8e09e5372578f154be.png

Custom curves#

Now let’s have some fun and try to make a loop-de-loop structure with parallel straights 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.

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)
    return np.array((x, y)).T


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

# Create the crosssection
s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=("in", "out"))
s1 = gf.Section(width=0.5, offset=2, layer=(2, 0))
s2 = gf.Section(width=0.5, offset=4, layer=(3, 0))
s3 = gf.Section(width=1, offset=0, layer=(4, 0))
X = gf.CrossSection(sections=[s0, s1, s2, s3])

c = gf.path.extrude(P, X)
c.plot()
2024-03-09 00:43:20.192 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/extrude_37947a9a.lyp'.
../_images/2e9d0f312dc99b05fbca498b85e3b94daec1789b92e8b13312b9ff97fdef1f77.png

You can create Paths from any array of points – just be sure that they form smooth curves! If we examine our path P we can see that all we’ve simply created a long list of points:

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
(1092, 2)
1092

Shapes and generic cells#

gdsfactory provides some generic parametric cells in gf.components that you can customize for your application.

Basic shapes#

Rectangle#

To create a simple rectangle, there are two functions:

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

import gdsfactory as gf

r1 = gf.components.rectangle(size=(4.5, 2), layer=(1, 0))
r1.plot()
2024-03-09 00:43:20.346 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rectangle_layer1__0_size4p5__2.lyp'.
../_images/09f91c8a15858698028a67a1d16cba9c3027e54d460ceb9ecb0a4b63145dee4f.png

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():

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.plot()
2024-03-09 00:43:20.481 | WARNING  | gdsfactory.component:_write_library:1998 - UserWarning: Unnamed cells, 1 in 'Unnamed_74b66945'
2024-03-09 00:43:20.495 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Unnamed_74b66945.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1663: UserWarning: Unnamed cells, 1 in 'Unnamed_74b66945'
  gdspath = component.write_gds(logging=False)
../_images/8879984dc4ac135ff7f29482a5c55fb1d191764cac951593f8818c3b7b587a6f.png

Cross#

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

c = gf.components.cross(length=10, width=0.5, layer=(1, 0))
c.plot()
2024-03-09 00:43:20.642 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/cross_6c558a55.lyp'.
../_images/a65da4990d0c8ef88cf0c83899d11d7671563bb031d1947cd84f603590bd1e60.png

Ellipse#

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

c = gf.components.ellipse(radii=(10, 5), angle_resolution=2.5, layer=(1, 0))
c.plot()
2024-03-09 00:43:20.839 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ellipse_layer1__0_radii10__5.lyp'.
../_images/e8e5cc8dbd791fc4168f5d55ae83d2afc01587aa20f06a807111bf648b5d6e1f.png

Circle#

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

c = gf.components.circle(radius=10, angle_resolution=2.5, layer=(1, 0))
c.plot()
2024-03-09 00:43:21.029 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/circle_layer1__0_radius10.lyp'.
../_images/c37e5a9f8b02bf6457742ac74b4a717f38db95ce11d204f3166a1dc1557eab58.png

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

c = gf.components.ring(radius=5, width=0.5, angle_resolution=2.5, layer=(1, 0))
c.plot()
2024-03-09 00:43:21.221 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ring_layer1__0_radius5.lyp'.
../_images/7548182b59c97fe6bc0bb74dfd3ca9b3dcdde0b1ffb64533d0823485ed833338.png
xs = gf.cross_section.strip(layer=(2, 0))
c = gf.components.ring_single(
    gap=0.2, radius=10, length_x=4, length_y=2, cross_section=xs
)
c.plot()
2024-03-09 00:43:21.442 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ring_single_c67cc39a.lyp'.
../_images/7fe08e9677f8a0e3eb5256c5046803209dda185cd3f9a8e453dab00033eb343c.png
import gdsfactory as gf

c = gf.components.ring_double(
    gap=0.2, radius=10, length_x=4, length_y=2, cross_section=xs
)
c.plot()
2024-03-09 00:43:21.634 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ring_double_c67cc39a.lyp'.
../_images/fdc6abcd5ef677c720ca14353066c38eb2a09e2fbb638151306138daadcc1aa1.png
c = gf.components.ring_double(
    gap=0.2,
    radius=10,
    length_x=4,
    length_y=2,
    bend=gf.components.bend_circular,
    cross_section=xs,
)
c.plot()
2024-03-09 00:43:21.842 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ring_double_f73b28f6.lyp'.
../_images/827d6e40970835c4d45ead614bb502ecbbe51ef1cc07a818f57a0b6827f564b3.png

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

c = gf.components.bend_circular(
    radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0)
)
c.plot()
2024-03-09 00:43:22.016 | WARNING  | gdsfactory.cross_section:validate_radius:212 - UserWarning: min_bend_radius 2.0 < CrossSection.radius_min 5.0. 
2024-03-09 00:43:22.038 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_circular_8ddd34dd.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/cross_section.py:212: UserWarning: min_bend_radius 2.0 < CrossSection.radius_min 5.0. 
  warnings.warn(message)
../_images/5a788e208713cfc99ac9ec533ce21afbfe8635b0427a9a9143e6fa164a613f8b.png

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.

c = gf.components.bend_euler(radius=2.0, width=0.5, angle=90, npoints=720, layer=(1, 0))
c.plot()
2024-03-09 00:43:22.237 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_euler_8ddd34dd.lyp'.
../_images/777a5e5e5afbd0a6421cee2fcf64af06b7fda57ef2dde91e7ee065d6bf9ca854.png

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.

c = gf.components.taper(length=10, width1=6, width2=4, port=None, layer=(1, 0))
c.plot()
2024-03-09 00:43:22.432 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/taper_e741c711.lyp'.
../_images/1ec6cceed49ad93e3c5a6e9800d924b06ba5c03ed40234c404f062cbcf6e61a1.png

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.

c = gf.components.ramp(length=10, width1=4, width2=8, layer=(1, 0))
c.plot()
2024-03-09 00:43:22.741 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ramp_40a909be.lyp'.
../_images/aeffb729b3a1fe1f153c3cc83bdf58609e3dc14637278fca5227d3fa5c3287a9.png

Common compound shapes#

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

c = gf.components.L(width=7, size=(10, 20), layer=(1, 0))
c.plot()
2024-03-09 00:43:22.930 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/L_layer1__0_width7.lyp'.
../_images/c274c4b9131e461f9a567fc85ac7db67b6ba7327ff905246ba1e66689d1060d2.png

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

c = gf.components.C(width=7, size=(10, 20), layer=(1, 0))
c.plot()
2024-03-09 00:43:23.131 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/C_b95f8aee.lyp'.
../_images/1d6dbbfc5f416d8b2589ba1e28c4a63214461dba69451bdcfa861e1f68141e2e.png

Text#

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

c = 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'
c.plot()
2024-03-09 00:43:23.334 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/text_c4bef3d3.lyp'.
../_images/7d65c0b7d96ce8b88ac71d801963aef69e44d9bceb2d81ce07742fa70252cdb1.png

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.

D = gf.components.litho_steps(
    line_widths=[1, 2, 4, 8, 16], line_spacing=10, height=100, layer=(1, 0)
)
D.plot()
2024-03-09 00:43:23.547 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/litho_steps_af496947.lyp'.
../_images/5603554c69b8b1840c8fa6df707977bbd6ff797459dac430a0b182ea5948b580.png

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.

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.plot()
2024-03-09 00:43:23.748 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/litho_calipers_4f052b1e.lyp'.
../_images/6b7b60c5073690d5d308fac84501145838e2f935c3d1fbd772beaedfbaab2110.png

Paths#

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

Circular arc#

P = gf.path.arc(radius=10, angle=135, npoints=720)
f = P.plot()
../_images/7283f6fdf87de270f71a5ab967c8a86cea871451da029d995c1b2ef5f892fd40.png

Straight#

import gdsfactory as gf

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

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

Smooth path from waypoints#

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/5906dc3e13c6290f1f6e9808bdd6b96d8644113d479ad84066e78e075f530bc9.png

Delay spiral#

c = gf.components.spiral_double()
c.plot()
2024-03-09 00:43:24.440 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/spiral_double.lyp'.
../_images/d21b13ddb4c005b54b09699d12138d23409ff53fab13e0fb5ac693f39db696f5.png
c = gf.components.spiral_inner_io()
c.plot()
2024-03-09 00:43:24.754 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/spiral_inner_io.lyp'.
../_images/21c1597fb271181df2844e501b24290e1b16efc4b0a3502e53ecd1d023b44068.png
c = gf.components.spiral_external_io()
c.plot()
2024-03-09 00:43:25.025 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/spiral_external_io.lyp'.
../_images/7d98cd3e70ec5691b95b762f0b96e8d7f1102389c093944211ea48b13f8a35e4.png

Useful contact pads / connectors#

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

c = gf.components.compass(size=(4, 2), layer=(1, 0))
c.plot()
2024-03-09 00:43:25.218 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/compass_layer1__0_size4__2.lyp'.
../_images/7a4b2345d8e081782278f09f205657223f0863f5a3192d657a5cb09c008fc813.png
c = gf.components.nxn(north=3, south=4, east=0, west=0)
c.plot()
2024-03-09 00:43:25.425 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/nxn_025a5d93.lyp'.
../_images/abf5517e408cd369ad440b572b1d823b631677cade54fa520dc9be291e96810f.png
c = gf.components.pad()
c.plot()
2024-03-09 00:43:25.620 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pad.lyp'.
../_images/fcf9ab5e1491c5cf29f81ff197274f0f3d1260b911e98fef608ec3224438c4d6.png
c = gf.components.pad_array90(columns=3)
c.plot()
2024-03-09 00:43:25.950 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pad_array_columns3_orientation90.lyp'.
../_images/a7054aebaabb05c7e48b6f0a469ba8767418d1f7aebd7fabc410f255b879538a.png

Chip / die template#

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.plot()
2024-03-09 00:43:26.137 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/die_83021bd5.lyp'.
../_images/2008ad805725872fbfc21103c8d5915ae6e84bc2a1076f3832489c8cdf208b8a.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).

import gdsfactory as gf

c = gf.components.optimal_hairpin(
    width=0.2, pitch=0.6, length=10, turn_ratio=4, num_pts=50, layer=(2, 0)
)
c.plot()
2024-03-09 00:43:26.340 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/optimal_hairpin_layer2__0.lyp'.
../_images/88a07ddef32d514f17d26fcc63f79d822413e04ca2661bdd19023ddd89a40f5e.png
c = gf.c.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.plot()
2024-03-09 00:43:26.557 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/optimal_step_layer2__0.lyp'.
../_images/a76ba681dfb19970888560562cff326499842e1fc6becf215f013fa6e1400da7.png
c = gf.c.optimal_90deg(width=100.0, num_pts=15, length_adjust=1, layer=(2, 0))
c.plot()
2024-03-09 00:43:26.743 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/optimal_90deg_layer2__0_width100p0.lyp'.
../_images/caf85176ee2a3bb58c1ad0f1f88fc0a18304cd23c5da417a465de9f6952f764b.png
c = gf.c.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.plot()
2024-03-09 00:43:26.950 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/snspd_layer2__0.lyp'.
../_images/9b5ba54bcfb0a50e01f9dac408d1e93883c9ce27d3d6af3e7ac3210778978684.png

Geometry#

gdsfactory provides you with some geometric functions

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.

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, 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.plot()
2024-03-09 00:43:27.143 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bool$1.lyp'.
../_images/0b19d7b7f00a28bad105d23e4a1cdb0653c9bb938a01512d6bad8866c72765ee.png

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

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)
%time

c3 = gf.geometry.boolean_klayout(
    c1, c2, operation=operation, layer1=(1, 0), layer2=(1, 0), layer3=(1, 0)
)  # KLayout booleans
c3.plot()
CPU times: user 5 µs, sys: 5 µs, total: 10 µs
Wall time: 6.44 µs
2024-03-09 00:43:27.319 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to '/tmp/gdsfactory/ellipse_4aa83906.gds'
2024-03-09 00:43:27.320 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to '/tmp/gdsfactory/ellipse_cb56ffd1.gds'
2024-03-09 00:43:27.366 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/boolean_klayout_372f801c.lyp'.
../_images/4b0111d6937a93b52c7c1e239e3a3a8845f0f57e4c7041dfe8e90ec51d03ae21.png
%time
c4 = gf.geometry.boolean(c1, c2, operation=operation)
c4.plot()
CPU times: user 5 µs, sys: 5 µs, total: 10 µs
Wall time: 6.91 µs
2024-03-09 00:43:27.588 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/boolean_ff8996c1.lyp'.
../_images/d9d2f277ea57770ec72885150fa925ae50acca728e3f13b4e85a6932395beb89.png

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 and does not respect layers.

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, precision=1e-6, layer=(2, 0))
Texpanded.name = "expanded"
Tshrink = gf.geometry.offset(T, distance=-1.5, precision=1e-6, 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.plot()
2024-03-09 00:43:27.783 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/top.lyp'.
../_images/33398361aef3d1a91be66034947f18dd3615c33668a67e7b0ef6b7313a9cc338.png

gf.geometry.offset is also useful for remove acute angle DRC errors.

You can do a positive offset to grow the polygons followed by a negative offset.

c = gf.Component("demo_dataprep")
c1 = gf.components.coupler_ring(cross_section="xs_rc2", radius=20)
c1.plot()
2024-03-09 00:43:27.996 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/coupler_ring_16974529.lyp'.
../_images/f1a4095836ea4fa0e9d30e63db8cf28845f56333580787b38585bbc5309a9463.png
d = 0.8
c2 = gf.geometry.offset(c1, distance=+d, layer=(3, 0))
c3 = gf.geometry.offset(c2, distance=-d, layer=(3, 0))
c << c1
c << c3
c.plot()
2024-03-09 00:43:28.186 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_dataprep.lyp'.
../_images/ca64c280d9c985c00c13d10bf42adcd3bbe4a5466cae2cb125b4bcf38a44931c.png

You can also run it as a decorator.

from functools import partial
from gdsfactory.geometry.maskprep import get_polygons_over_under, over_under

over_under_slab = partial(over_under, layers=((3, 0),), distances=(0.5,))

c = gf.components.coupler_ring(cross_section="xs_rc2", radius=20)
c = over_under_slab(c)
c.plot()
2024-03-09 00:43:28.381 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/coupler_ring_over_under_f3623ac4.lyp'.
../_images/fa69e90f4129221f0079d692f6b58f3dbae3535bb7212b2073b054baef81c4c2.png

You can also add extra polygons on top

get_polygons_over_under_slab = partial(
    get_polygons_over_under, layers=[(3, 0)], distances=(0.5,)
)
ring = gf.components.coupler_ring(cross_section="xs_rc2", radius=20)
ring_clean = over_under_slab(ring)

c = gf.Component("compnent_clean")
ref = c << ring
polygons = get_polygons_over_under_slab(ref)
c.add(polygons)
c.plot()
2024-03-09 00:43:28.574 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/compnent_clean.lyp'.
../_images/e92aac72bf17c428c0c878f59d2edf3a59ca9082ba4f2e2ba447385c027fb7a1.png

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.

import gdsfactory as gf

# Create a blank device and add two shapes
X = gf.Component("outline_demo")
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("outline_compare")
c.add_ref(X)
c.add_ref(O).movex(30)
c.plot()
2024-03-09 00:43:28.906 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/outline_compare.lyp'.
../_images/e45a8fc8da7e73de2bcda352737129df049c6b90f40b595752cde85db75acf6f.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).

c = gf.components.L(width=7, size=(10, 20), layer=(1, 0))
c.plot()
2024-03-09 00:43:29.090 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/L_layer1__0_width7.lyp'.
../_images/c274c4b9131e461f9a567fc85ac7db67b6ba7327ff905246ba1e66689d1060d2.png
# Outline the geometry and open a hole at each port
c = gf.geometry.outline(offsets, distance=5, open_ports=False, layer=(2, 0))  # No holes
c.plot()
2024-03-09 00:43:29.286 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/outline_50bb359a.lyp'.
../_images/df47004534cb6c656acbd7ef21d20c0e25fb36c7af23d6102f88bd28ada60ee3.png
c = gf.geometry.outline(
    offsets, distance=5, open_ports=True, layer=(2, 0)
)  # Hole is the same width as the port
c.plot()
2024-03-09 00:43:29.481 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/outline_8963d50a.lyp'.
../_images/df47004534cb6c656acbd7ef21d20c0e25fb36c7af23d6102f88bd28ada60ee3.png
c = gf.geometry.outline(
    offsets, distance=5, open_ports=10, layer=(2, 0)
)  # Change the hole size by entering a float
c.plot()
2024-03-09 00:43:29.679 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/outline_ba9178ba.lyp'.
../_images/df47004534cb6c656acbd7ef21d20c0e25fb36c7af23d6102f88bd28ada60ee3.png
c = gf.geometry.outline(
    offsets, distance=5, open_ports=5, layer=(2, 0)
)  # Creates flush opening (open_ports > distance)
c.plot()
2024-03-09 00:43:29.878 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/outline_859d56b3.lyp'.
../_images/df47004534cb6c656acbd7ef21d20c0e25fb36c7af23d6102f88bd28ada60ee3.png

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.

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.plot()
2024-03-09 00:43:30.074 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/invert_be8c5f94.lyp'.
../_images/5386d055d2cb48e91c89370fc909938b566b57fb8e4125adf74ace74bd185cc1.png

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.

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.plot()
2024-03-09 00:43:30.252 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component union has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:43:30.275 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/union.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component union has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(
../_images/e656468025241bf38683b10e497ca9c3051debb8ec3398b9d21e5a0d0940cf53.png
# 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.plot()
2024-03-09 00:43:30.478 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/union_componentunion_layer2__0.lyp'.
../_images/7bef44aba6b0e1ad89deecd2c1dcf6a13f456afec06318a3b47c602eab5d2c91.png
# Or we can perform the union operate by-layer
D_joined_by_layer = gf.geometry.union(D, by_layer=True)
D_joined_by_layer.plot()
2024-03-09 00:43:30.673 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/union_0b116f46.lyp'.
../_images/5b87c9214d6dd3bad18cb6838028ce6df375a236271d6788e565c0e27613dffb.png

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 Component. If they are not identical, any areas not shared by the two geometries will remain.

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.plot()
2024-03-09 00:43:30.880 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/xor_diff.lyp'.
../_images/779dc8f973e48d414c5e75e83c28f9dcd8ee600e617255a9d5a6f8f37812b816.png

Grid / pack / 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. The edge_x/edge_y arguments specify inter-row/inter-column alignment (unused if separation = True).

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.plot()
2024-03-09 00:43:31.096 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/grid_a0d419de.lyp'.
../_images/4a9f4a7a1a6879ec7a9f7a942cafbecb6ef34b6b4603c4aed296eb465d96c5d0.png

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

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 Components 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:

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 Components
    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.plot()
2024-03-09 00:43:31.303 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pack_0.lyp'.
../_images/2545d8ef40ec18703ded71cb943f4ee488994f7032e7f7e516bd01a7755aecea.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:

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 Components
    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.plot()
2024-03-09 00:43:31.563 | WARNING  | gdsfactory.pack:pack:249 - UserWarning: unable to pack in one component, creating 4 components
2024-03-09 00:43:31.578 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/packed.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/pack.py:249: UserWarning: unable to pack in one component, creating 4 components
  warnings.warn(f"unable to pack in one component, creating {groups} components")
../_images/5ed8074c21899206a30c0e8e0431d7289a7e79be10435cb2ed7353e333f0308b.png

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 Components 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 Component evenly in the x or y direction. It is meant to duplicate the distribute functionality present in Inkscape / Adobe Illustrator:

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

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.plot()
2024-03-09 00:43:31.900 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rectangles$1.lyp'.
../_images/19dd72a9aa0dd4c1b935351316fb28e34a7817482100f4ca299b0076ec96a1e7.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:

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.plot()
2024-03-09 00:43:32.093 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rectangles_separated.lyp'.
../_images/4324e8e3d4b10a99b35bb1f34d011986e111ca09e8dcc508ce899b26059ecab7.png

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:

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.plot()
2024-03-09 00:43:32.280 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/spacing100.lyp'.
../_images/1e9c468eff4b61f95811eed51b231e5c0c70269555649734e8a0c256f317129f.png

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:

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.plot()
2024-03-09 00:43:32.477 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/alignment.lyp'.
../_images/bff02b37b7af91ad7ff34c8476213587477430b64793d03c57a4d72c914fbf0d.png

Align#

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

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

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.plot()
2024-03-09 00:43:32.670 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/distribute.lyp'.
../_images/4324e8e3d4b10a99b35bb1f34d011986e111ca09e8dcc508ce899b26059ecab7.png

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

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.plot()
2024-03-09 00:43:32.866 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/align.lyp'.
../_images/c6e08ab37444c08d1a5dad6dfac843b361ba9361106702b154cb33e6ddae9d36.png

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

D = gf.Component("distribute_align_y")
# 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.plot()
2024-03-09 00:43:33.060 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/distribute_align_y.lyp'.
../_images/4ae6c07f8821d8187c1e909ffcd3d5faf312310a08bed1a93308bbd0ecfbdb43.png

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

Components with hierarchy#

You can define components Parametric cells (waveguides, bends, couplers) with basic input parameters (width, length, radius …) and reuse the PCells in more complex PCells.

from functools import partial

import toolz

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.typings import ComponentSpec, CrossSectionSpec

gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()

Problem

When using hierarchical cells where you pass N subcells with M parameters you can end up with N*M parameters. This is make code hard to read.

@gf.cell
def bend_with_straight_with_too_many_input_parameters(
    bend=gf.components.bend_euler,
    straight=gf.components.straight,
    length: float = 3,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    direction: str = "ccw",
    cross_section: CrossSectionSpec = "xs_sc",
) -> gf.Component:
    """ "As hierarchical cells become more complex, the number of input parameters can increase significantly."""
    c = gf.Component()
    b = bend(
        angle=angle,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        direction=direction,
        cross_section=cross_section,
    )
    s = straight(length=length, cross_section=cross_section)

    bref = c << b
    sref = c << s

    sref.connect("o2", bref.ports["o2"])
    c.info["length"] = b.info["length"] + s.info["length"]
    return c


c = bend_with_straight_with_too_many_input_parameters()
c.plot()
2024-03-09 00:43:33.262 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_with_too_many_input_parameters.lyp'.

../_images/7a3cd5a1ae79593876187b75328e685fa89df8a84924980b0d97ffb05476046b.png

Solution

You can use a ComponentSpec parameter for every subcell. The ComponentSpec can be a dictionary with arbitrary number of settings, a string, or a function.

ComponentSpec#

When defining a Parametric cell you can use other ComponentSpec as an arguments. It can be a:

  1. string: function name of a cell registered on the active PDK. "bend_circular"

  2. dict: dict(component='bend_circular', settings=dict(radius=20))

  3. function: Using functools.partial you can customize the default parameters of a function.

@gf.cell
def bend_with_straight(
    bend: ComponentSpec = gf.components.bend_euler,
    straight: ComponentSpec = gf.components.straight,
) -> gf.Component:
    """Much simpler version.

    Args:
        bend: input bend.
        straight: output straight.
    """
    c = gf.Component()
    b = gf.get_component(bend)
    s = gf.get_component(straight)

    bref = c << b
    sref = c << s

    sref.connect("o2", bref.ports["o2"])
    c.info["length"] = b.info["length"] + s.info["length"]
    return c


c = bend_with_straight()
c.plot()
2024-03-09 00:43:33.459 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight.lyp'.

../_images/61e67d4e18621de76b611e8f53cf89d300bae2dac2c20dd2dd97b3f7ecc52707.png

1. string#

You can use any string registered in the Pdk. Go to the PDK tutorial to learn how to register cells in a PDK.

c = bend_with_straight(bend="bend_circular")
c.plot()
2024-03-09 00:43:33.651 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_bendbend_circular.lyp'.

../_images/adb3e5c277f1f1c514af9dd31b5319c3f68f129bfba17ceab5fb9814e64b805d.png

2. dict#

You can pass a dict of settings.

c = bend_with_straight(bend=dict(component="bend_circular", settings=dict(radius=20)))
c.plot()
2024-03-09 00:43:33.847 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_113977eb.lyp'.

../_images/1da9be557fe76f4bac3b3ee4579a4dcf1c610389678cd3906b233fe40a03ce71.png

3. function#

You can pass a function of a function with customized default input parameters from functools import partial

Partial lets you define different default parameters for a function, so you can modify the default settings for each child cell.

c = bend_with_straight(bend=partial(gf.components.bend_circular, radius=30))
c.plot()
2024-03-09 00:43:34.042 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_65155870.lyp'.

../_images/0570ce954f4700d59c46305a3331e6acf3f8a6fada29055b082a1cf76aa93e47.png
bend20 = partial(gf.components.bend_circular, radius=20)
b = bend20()
b.plot()
2024-03-09 00:43:34.233 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_circular_radius20.lyp'.

../_images/07d086a1a60a6184b791dd5539001f8cdf52847372236d0e62f7c2596ceea119.png
type(bend20)

<class 'functools.partial'>
bend20.func.__name__

'bend_circular'
bend20.keywords

{'radius': 20}
b = bend_with_straight(bend=bend20)
b.plot()
2024-03-09 00:43:34.447 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_4f9bb335.lyp'.

../_images/1da9be557fe76f4bac3b3ee4579a4dcf1c610389678cd3906b233fe40a03ce71.png
print(b.info)
length=41.416
# You can still modify the bend to have any bend radius
b3 = bend20(radius=10)
b3.plot()
2024-03-09 00:43:34.639 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/bend_circular_radius10.lyp'.

../_images/0f4fe2c6ca36e7119143aac8ccf7bd4342b57975b9c06e7176bf1f9a95a40bb4.png

PDK custom fab#

You can define a new PDK by creating function that customize partial parameters of the generic functions.

Lets say that this PDK uses layer (41, 0) for the pads (instead of the layer used in the generic pad function).

pad_custom_layer = partial(gf.components.pad, layer=(41, 0))
c = pad_custom_layer()
c.plot()
2024-03-09 00:43:34.968 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pad_layer41__0.lyp'.

../_images/ad773eccdd8014a3242dc1d0e05fad162d52b358b0086478018787e7e130fa52.png

Composing functions#

You can combine more complex functions out of smaller functions.

Lets say that we want to add tapers and grating couplers to a wide waveguide.

c1 = gf.components.straight()
c1.plot()
2024-03-09 00:43:35.152 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight.lyp'.

../_images/0c6d7875d0546ee6ef064aed17072c60d3c17adea63e07e7ed5719147180b126.png
straight_wide = partial(gf.components.straight, width=3)
c3 = straight_wide()
c3.plot()
2024-03-09 00:43:35.338 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_width3.lyp'.

../_images/32bfbb8b24105556db28448710c2b5d8249348c8f91fbf90aec643662e9e0732.png
c1 = gf.components.straight(width=3)
c1.plot()
2024-03-09 00:43:35.526 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_width3.lyp'.

../_images/32bfbb8b24105556db28448710c2b5d8249348c8f91fbf90aec643662e9e0732.png
c2 = gf.add_tapers(c1)
c2.plot()
2024-03-09 00:43:35.717 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_add_tapers_componentstraight_width3.lyp'.

../_images/517a1896d4b7ee53e824308c0ba2c52171520057e8722ae4e7a1154588a0cbc7.png
c2.settings  # You can still access the child metadata

CellSettings(
    taper={'function': 'taper'},
    select_ports={
        'function': 'select_ports',
        'settings': {'port_type': 'optical'},
        'module': 'gdsfactory.port'
    },
    ports=None,
    taper_port_name1='o1',
    taper_port_name2='o2',
    component=ComponentSpec(
        settings=CellSettings(
            length=10.0,
            npoints=2,
            cross_section='xs_sc',
            post_process=None,
            info=None,
            width=3
        ),
        function='straight',
        module='gdsfactory.components.straight'
    )
)
c3 = gf.routing.add_fiber_array(c2, with_loopback=False)
c3.plot()
2024-03-09 00:43:35.941 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png
c3.info  # You can still access the child metadata

Info(
    length=10.0,
    width=3,
    route_info_type='xs_9950ba04',
    route_info_length=10.0,
    route_info_weight=10.0,
    route_info_xs_9950ba04_length=10.0
)

Lets do it with a single step thanks to toolz.pipe

add_fiber_array = partial(gf.routing.add_fiber_array, with_loopback=False)
add_tapers = gf.add_tapers

# pipe is more readable than the equivalent add_fiber_array(add_tapers(c1))
c3 = toolz.pipe(c1, add_tapers, add_fiber_array)
c3.plot()
2024-03-09 00:43:36.143 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png

we can even combine add_tapers and add_fiber_array thanks to toolz.compose or toolz.compose

For example:

add_tapers_fiber_array = toolz.compose_left(add_tapers, add_fiber_array)
c4 = add_tapers_fiber_array(c1)
c4.plot()
2024-03-09 00:43:36.342 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png

is equivalent to

c5 = add_fiber_array(add_tapers(c1))
c5.plot()
2024-03-09 00:43:36.544 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png

as well as equivalent to

add_tapers_fiber_array = toolz.compose(add_fiber_array, add_tapers)
c6 = add_tapers_fiber_array(c1)
c6.plot()
2024-03-09 00:43:36.745 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png

or

c7 = toolz.pipe(c1, add_tapers, add_fiber_array)
c7.plot()
2024-03-09 00:43:36.941 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.

../_images/3f7cada9432a320fa92f0221f27f8b8cf9c86f4fefe6b42549975346d3f6cdb7.png
c7.info

Info(
    length=10.0,
    width=3,
    route_info_type='xs_9950ba04',
    route_info_length=10.0,
    route_info_weight=10.0,
    route_info_xs_9950ba04_length=10.0
)
c7.pprint()
{
'settings': {
│   │   'component': {
│   │   │   'settings': {
│   │   │   │   'taper': {'function': 'taper'},
│   │   │   │   'select_ports': {
│   │   │   │   │   'function': 'select_ports',
│   │   │   │   │   'settings': {'port_type': 'optical'},
│   │   │   │   │   'module': 'gdsfactory.port'
│   │   │   │   },
│   │   │   │   'ports': None,
│   │   │   │   'taper_port_name1': 'o1',
│   │   │   │   'taper_port_name2': 'o2',
│   │   │   │   'component': {
│   │   │   │   │   'settings': {
│   │   │   │   │   │   'length': 10.0,
│   │   │   │   │   │   'npoints': 2,
│   │   │   │   │   │   'cross_section': 'xs_sc',
│   │   │   │   │   │   'post_process': None,
│   │   │   │   │   │   'info': None,
│   │   │   │   │   │   'width': 3
│   │   │   │   │   },
│   │   │   │   │   'function': 'straight',
│   │   │   │   │   'module': 'gdsfactory.components.straight'
│   │   │   │   }
│   │   │   },
│   │   │   'function': 'add_tapers',
│   │   │   'module': 'gdsfactory.add_tapers'
│   │   },
│   │   'grating_coupler': {
│   │   │   'function': 'grating_coupler_elliptical_trenches',
│   │   │   'settings': {'polarization': 'te', 'taper_angle': 35},
│   │   │   'module': 'gdsfactory.components.grating_coupler_elliptical_trenches'
│   │   },
│   │   'gc_port_name': 'o1',
│   │   'gc_port_labels': None,
│   │   'io_rotation': None,
│   │   'component_name': None,
│   │   'select_ports': {
│   │   │   'function': 'select_ports',
│   │   │   'settings': {'port_type': 'optical'},
│   │   │   'module': 'gdsfactory.port'
│   │   },
│   │   'cross_section': 'xs_sc',
│   │   'get_input_labels_function': {
│   │   │   'function': 'get_input_labels',
│   │   │   'settings': {'get_input_label_text_function': {'function': 'get_input_label_text_dash'}},
│   │   │   'module': 'gdsfactory.routing.get_input_labels'
│   │   },
│   │   'layer_label': None,
│   │   'dev_id': None,
│   │   'text': None,
│   │   'id_placement': 'center',
│   │   'id_placement_offset': [0, 0],
│   │   'post_process': None,
│   │   'info': None,
│   │   'with_loopback': False
},
'function': 'add_fiber_array',
'module': 'gdsfactory.routing.add_fiber_array',
'name': 'add_tapers_add_fiber_array_89d50adc',
'info': {
│   │   'length': 10.0,
│   │   'width': 3,
│   │   'route_info_type': 'xs_9950ba04',
│   │   'route_info_length': 10.0,
│   │   'route_info_weight': 10.0,
│   │   'route_info_xs_9950ba04_length': 10.0
}
}

Routing#

Optical and high speed RF ports have an orientation that routes need to follow to avoid sharp turns that produce reflections.

we have routing functions that route:

  • single route between 2 ports

    • get_route

    • get_route_from_steps

    • get_route_astar

  • group of routes between 2 groups of ports using a river/bundle/bus router. At the moment it works only when all ports on each group have the same orientation.

    • get_bundle

    • get_bundle_from_steps

The most useful function is get_bundle which supports both single and groups of routes, and can also route with length matching, which ensures that all routes have the same length.

The biggest limitation is that it requires to have all the ports with the same orientation, for that you can use gf.routing.route_ports_to_side

from functools import partial

import gdsfactory as gf
from gdsfactory.cell import cell
from gdsfactory.component import Component
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.port import Port
c = gf.Component("sample_no_routes")
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((100, 50))
c.plot()
2024-03-09 00:43:37.166 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_no_routes.lyp'.

../_images/fa175a7c7f2a9042658e152c4a15649a02053edb920aa03a13edd6915d243b51.png

get_route#

get_route returns a Manhattan route between 2 ports

help(gf.routing.get_route)
Help on function get_route in module gdsfactory.routing.get_route:

get_route(input_port: 'Port', output_port: 'Port', bend: 'ComponentSpec' = <function bend_euler at 0x7f9c480e65c0>, with_sbend: 'bool' = False, straight: 'ComponentSpec' = <function straight at 0x7f9c480e63e0>, taper: 'ComponentSpec | None' = None, start_straight_length: 'float | None' = None, end_straight_length: 'float | None' = None, min_straight_length: 'float | None' = None, cross_section: 'None | CrossSectionSpec | MultiCrossSectionAngleSpec' = 'xs_sc', **kwargs) -> 'Route'
    Returns a Manhattan Route between 2 ports.
    
    The references are straights, bends and tapers.
    `get_route` is an automatic version of `get_route_from_steps`.
    
    Args:
        input_port: start port.
        output_port: end port.
        bend: bend spec.
        with_sbend: add sbend in case there are routing errors.
        straight: straight spec.
        taper: taper spec.
        start_straight_length: length of starting straight.
        end_straight_length: length of end straight.
        min_straight_length: min length of straight for any intermediate segment.
        cross_section: spec.
        kwargs: cross_section settings.
    
    
    .. plot::
        :include-source:
    
        import gdsfactory as gf
    
        c = gf.Component('sample_connect')
        mmi1 = c << gf.components.mmi1x2()
        mmi2 = c << gf.components.mmi1x2()
        mmi2.move((40, 20))
        route = gf.routing.get_route(mmi1.ports["o2"], mmi2.ports["o1"], radius=5)
        c.add(route.references)
        c.plot()
c = gf.Component("sample_connect")
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((100, 50))
route = gf.routing.get_route(mmi1.ports["o2"], mmi2.ports["o1"])
c.add(route.references)
c.plot()
2024-03-09 00:43:37.366 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_connect.lyp'.

../_images/cdc10e0466f1fedd14099f1767d36906e1e31e321e536062b282aee329c90dde.png
route

Route(
    references=[
        ComponentReference (parent Component "bend_euler_0c287e08", ports ['o1', 'o2'], origin (69.99, 0.625), rotation 0.0, x_reflection False),
        ComponentReference (parent Component "bend_euler_0c287e08", ports ['o1', 'o2'], origin (79.99, 40.0), rotation 90.0, x_reflection True),
        ComponentReference (parent Component "straight_c537e2f5", ports ['o1', 'o2'], origin (15.5, 0.625), rotation 0.0, x_reflection False),
        ComponentReference (parent Component "straight_d8c87ca2", ports ['o1', 'o2'], origin (79.99, 10.625), rotation 90.0, x_reflection False),
        ComponentReference (parent Component "straight_e4d523ad", ports ['o1', 'o2'], origin (89.99, 50.0), rotation 0.0, x_reflection False)
    ],
    labels=[],
    ports=(
        {'name': 'o1', 'width': 0.5, 'center': [15.5, 0.625], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
        {'name': 'o2', 'width': 0.5, 'center': [90.0, 50.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}
    ),
    length=117.149
)

Problem: get_route with obstacles

sometimes there are obstacles that connect strip does not see!

c = gf.Component("sample_problem")
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((110, 50))
x = c << gf.components.cross(length=20)
x.move((135, 20))
route = gf.routing.get_route(mmi1.ports["o2"], mmi2.ports["o2"])
c.add(route.references)
c.plot()
2024-03-09 00:43:37.578 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_problem.lyp'.

../_images/320ad569b4bf9bb94ed665f02d4d47bc690dff6b238b04dfa5aff7023a218055.png

Solutions:

  • specify the route steps

get_route_from_steps#

get_route_from_steps is a manual version of get_route where you can define only the new steps x or y together with increments dx or dy

c = gf.Component("get_route_from_steps")
w = gf.components.straight()
left = c << w
right = c << w
right.move((100, 80))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 25

port1 = left.ports["o2"]
port2 = right.ports["o2"]

routes = gf.routing.get_route_from_steps(
    port1=port1,
    port2=port2,
    steps=[
        {"x": 20, "y": 0},
        {"x": 20, "y": 20},
        {"x": 120, "y": 20},
        {"x": 120, "y": 80},
    ],
)
c.add(routes.references)
c.plot()
2024-03-09 00:43:37.776 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_route_from_steps.lyp'.

../_images/607d900d64b889d69374a1238ab9b7a1abb2dda93424f845b7faa0cdeb3955c3.png
c = gf.Component("get_route_from_steps_shorter_syntax")
w = gf.components.straight()
left = c << w
right = c << w
right.move((100, 80))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 25

port1 = left.ports["o2"]
port2 = right.ports["o2"]

routes = gf.routing.get_route_from_steps(
    port1=port1,
    port2=port2,
    steps=[
        {"x": 20},
        {"y": 20},
        {"x": 120},
        {"y": 80},
    ],
)
c.add(routes.references)
c.plot()
2024-03-09 00:43:37.973 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_route_from_steps_shorter_syntax.lyp'.

../_images/607d900d64b889d69374a1238ab9b7a1abb2dda93424f845b7faa0cdeb3955c3.png

get_bundle#

To route groups of ports avoiding waveguide collisions, you should use get_bundle instead of get_route.

get_bundle uses a river/bundle/bus router.

At the moment it works only when each group of ports have the same orientation.

ys_right = [0, 10, 20, 40, 50, 80]
pitch = 127.0
N = len(ys_right)
ys_left = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

right_ports = [
    gf.Port(f"R_{i}", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)
    for i in range(N)
]
left_ports = [
    gf.Port(f"L_{i}", center=(-200, ys_left[i]), width=0.5, orientation=0, layer=layer)
    for i in range(N)
]

# you can also mess up the port order and it will sort them by default
left_ports.reverse()

c = gf.Component(name="connect_bundle_v2")
routes = gf.routing.get_bundle(
    left_ports, right_ports, sort_ports=True, start_straight_length=100
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:38.353 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/connect_bundle_v2.lyp'.

../_images/94f338a138e89c38effa5ba03c288167e36341c3b0041c4973acd9fce6549e99.png
xs_top = [0, 10, 20, 40, 50, 80]
pitch = 127.0
N = len(xs_top)
xs_bottom = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

top_ports = [
    gf.Port(f"top_{i}", center=(xs_top[i], 0), width=0.5, orientation=270, layer=layer)
    for i in range(N)
]

bot_ports = [
    gf.Port(
        f"bot_{i}",
        center=(xs_bottom[i], -300),
        width=0.5,
        orientation=90,
        layer=layer,
    )
    for i in range(N)
]

c = gf.Component(name="connect_bundle_separation")
routes = gf.routing.get_bundle(
    top_ports, bot_ports, separation=5.0, end_straight_length=100
)
for route in routes:
    c.add(route.references)

c.plot()
2024-03-09 00:43:38.593 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/connect_bundle_separation.lyp'.

../_images/b4e566d5ecc032551d2037421c4ebab4a7b617307bd6499e417dd0d4db7a6bec.png

get_bundle can also route bundles through corners

@cell
def test_connect_corner(N=6, config="A"):
    d = 10.0
    sep = 5.0
    top_cell = gf.Component()
    layer = (1, 0)

    if config in ["A", "B"]:
        a = 100.0
        ports_A_TR = [
            Port(
                f"A_TR_{i}",
                center=(d, a / 2 + i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_TL = [
            Port(
                f"A_TL_{i}",
                center=(-d, a / 2 + i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BR = [
            Port(
                f"A_BR_{i}",
                center=(d, -a / 2 - i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BL = [
            Port(
                f"A_BL_{i}",
                center=(-d, -a / 2 - i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]

        ports_B_TR = [
            Port(
                f"B_TR_{i}",
                center=(a / 2 + i * sep, d),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_TL = [
            Port(
                f"B_TL_{i}",
                center=(-a / 2 - i * sep, d),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BR = [
            Port(
                f"B_BR_{i}",
                center=(a / 2 + i * sep, -d),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BL = [
            Port(
                f"B_BL_{i}",
                center=(-a / 2 - i * sep, -d),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]

    elif config in ["C", "D"]:
        a = N * sep + 2 * d
        ports_A_TR = [
            Port(
                f"A_TR_{i}",
                center=(a, d + i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_TL = [
            Port(
                f"A_TL_{i}",
                center=(-a, d + i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BR = [
            Port(
                f"A_BR_{i}",
                center=(a, -d - i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BL = [
            Port(
                f"A_BL_{i}",
                center=(-a, -d - i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]

        ports_B_TR = [
            Port(
                f"B_TR_{i}",
                center=(d + i * sep, a),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_TL = [
            Port(
                f"B_TL_{i}",
                center=(-d - i * sep, a),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BR = [
            Port(
                f"B_BR_{i}",
                center=(d + i * sep, -a),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BL = [
            Port(
                f"B_BL_{i}",
                center=(-d - i * sep, -a),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]

    if config in ["A", "C"]:
        for ports1, ports2 in zip(ports_A, ports_B):
            routes = gf.routing.get_bundle(ports1, ports2, layer=(2, 0), radius=5)
            for route in routes:
                top_cell.add(route.references)

    elif config in ["B", "D"]:
        for ports1, ports2 in zip(ports_A, ports_B):
            routes = gf.routing.get_bundle(ports2, ports1, layer=(2, 0), radius=5)
            for route in routes:
                top_cell.add(route.references)

    return top_cell


c = test_connect_corner(config="A")
c.plot()
2024-03-09 00:43:38.892 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/test_connect_corner.lyp'.

../_images/c993b97859157116771bf1e7dd7c10c16776bdbe2d9d97587b66d7b63ab81282.png
c = test_connect_corner(config="C")
c.plot()
2024-03-09 00:43:39.239 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/test_connect_corner_configC.lyp'.

../_images/5d479f23e8468b15fcb3ef90e72b366a126008650e265fb374a244bfc16ba1f4.png
@cell
def test_connect_bundle_udirect(dy=200, orientation=270, layer=(1, 0)):
    xs1 = [-100, -90, -80, -55, -35, 24, 0] + [200, 210, 240]
    axis = "X" if orientation in [0, 180] else "Y"
    pitch = 10.0
    N = len(xs1)
    xs2 = [70 + i * pitch for i in range(N)]

    if axis == "X":
        ports1 = [
            Port(
                f"top_{i}",
                center=(0, xs1[i]),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bottom_{i}",
                center=(dy, xs2[i]),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

    else:
        ports1 = [
            Port(
                f"top_{i}",
                center=(xs1[i], 0),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bottom_{i}",
                center=(xs2[i], dy),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

    top_cell = Component()
    routes = gf.routing.get_bundle(
        ports1, ports2, radius=10.0, enforce_port_ordering=False
    )
    for route in routes:
        top_cell.add(route.references)

    return top_cell


c = test_connect_bundle_udirect()
c.plot()
2024-03-09 00:43:39.517 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/test_connect_bundle_udirect.lyp'.

../_images/b037fc88409e878dc56f1a1c22faa239df693afc425d8f5e0e621f20a0f57cf9.png
@cell
def test_connect_bundle_u_indirect(dy=-200, orientation=180, layer=(1, 0)):
    xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240]
    axis = "X" if orientation in [0, 180] else "Y"
    pitch = 10.0
    N = len(xs1)
    xs2 = [50 + i * pitch for i in range(N)]

    a1 = orientation
    a2 = a1 + 180

    if axis == "X":
        ports1 = [
            Port(f"top_{i}", center=(0, xs1[i]), width=0.5, orientation=a1, layer=layer)
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bot_{i}",
                center=(dy, xs2[i]),
                width=0.5,
                orientation=a2,
                layer=layer,
            )
            for i in range(N)
        ]

    else:
        ports1 = [
            Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bot_{i}",
                center=(xs2[i], dy),
                width=0.5,
                orientation=a2,
                layer=layer,
            )
            for i in range(N)
        ]

    top_cell = Component()
    routes = gf.routing.get_bundle(
        ports1,
        ports2,
        bend=gf.components.bend_euler,
        radius=5,
    )
    for route in routes:
        top_cell.add(route.references)

    return top_cell


c = test_connect_bundle_u_indirect(orientation=0)
c.plot()
2024-03-09 00:43:39.836 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/test_connect_bundle_u_indirect_orientation0.lyp'.

../_images/1c5ac448a72b4a25b7fd161137d245a51936098e92231b6dc08e3b5fcd5b6627.png
@gf.cell
def test_north_to_south(layer=(1, 0)):
    dy = 200.0
    xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

    pitch = 10.0
    N = len(xs1)
    xs2 = [-20 + i * pitch for i in range(N // 2)]
    xs2 += [400 + i * pitch for i in range(N // 2)]

    a1 = 90
    a2 = a1 + 180

    ports1 = [
        gf.Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
        for i in range(N)
    ]

    ports2 = [
        gf.Port(f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)
        for i in range(N)
    ]

    c = gf.Component()
    routes = gf.routing.get_bundle(ports1, ports2, auto_widen=False)
    for route in routes:
        c.add(route.references)

    return c


c = test_north_to_south()
c.plot()
2024-03-09 00:43:40.121 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/test_north_to_south.lyp'.

../_images/74ea6add0f381fd1f21ebd7838c256797455cb8fbab20a588b07a14839642b66.png
@gf.cell
def demo_connect_bundle():
    """combines all the connect_bundle tests"""
    y = 400.0
    x = 500
    y0 = 900
    dy = 200.0
    c = gf.Component()
    for j, s in enumerate([-1, 1]):
        for i, orientation in enumerate([0, 90, 180, 270]):
            ci = test_connect_bundle_u_indirect(dy=s * dy, orientation=orientation)
            ref = ci.ref(position=(i * x, j * y))
            c.add(ref)

            ci = test_connect_bundle_udirect(dy=s * dy, orientation=orientation)
            ref = ci.ref(position=(i * x, j * y + y0))
            c.add(ref)

    for i, config in enumerate(["A", "B", "C", "D"]):
        ci = test_connect_corner(config=config)
        ref = ci.ref(position=(i * x, 1700))
        c.add(ref)

    return c


c = demo_connect_bundle()
c.plot()
2024-03-09 00:43:41.294 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_connect_bundle.lyp'.

../_images/c050b32b926e6dd00ba1ae0403a876d7fe78c60fffe5237505859bd693e1cc27.png
c = gf.Component("route_bend_5um")
c1 = c << gf.components.mmi2x2()
c2 = c << gf.components.mmi2x2()

c2.move((100, 50))
routes = gf.routing.get_bundle(
    [c1.ports["o4"], c1.ports["o3"]], [c2.ports["o1"], c2.ports["o2"]], radius=5
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:41.639 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_bend_5um.lyp'.

../_images/535feae9ec82c995349f8f81c5ab98d5a3c2e4b6324b616670705262d9a16d1f.png
c = gf.Component("electrical")
c1 = c << gf.components.pad()
c2 = c << gf.components.pad()
c2.move((200, 100))
routes = gf.routing.get_bundle(
    [c1.ports["e3"]], [c2.ports["e1"]], cross_section=gf.cross_section.metal1
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:41.844 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/electrical.lyp'.

../_images/cd1cea074754bb5337542a1d3ae7adbd9e9fc3a6b3324d869813e3b8dc196a53.png
c = gf.Component("get_bundle_with_ubends_bend_from_top")
pad_array = gf.components.pad_array()

c1 = c << pad_array
c2 = c << pad_array
c2.rotate(90)
c2.movex(1000)
c2.ymax = -200

routes_bend180 = gf.routing.get_routes_bend180(
    ports=c2.get_ports_list(),
    radius=75 / 2,
    cross_section=gf.cross_section.metal1,
    bend_port1="e1",
    bend_port2="e2",
)
c.add(routes_bend180.references)

routes = gf.routing.get_bundle(
    c1.get_ports_list(), routes_bend180.ports, cross_section=gf.cross_section.metal1
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:42.015 | WARNING  | gdsfactory.routing.get_routes_bend180:get_routes_bend180:57 - UserWarning: This function is deprecated and will be removed in next major release.
2024-03-09 00:43:42.090 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_bundle_with_ubends_bend_from_top.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/routing/get_routes_bend180.py:57: UserWarning: This function is deprecated and will be removed in next major release.
  warnings.warn(

../_images/a5189a22b3ac992ae1b0a04b49f4e5ca3efc19c61dfbda2f9b02802f7ea3684f.png
c = gf.Component("get_bundle_with_ubends_bend_from_bottom")
pad_array = gf.components.pad_array()

c1 = c << pad_array
c2 = c << pad_array
c2.rotate(90)
c2.movex(1000)
c2.ymax = -200

routes_bend180 = gf.routing.get_routes_bend180(
    ports=c2.get_ports_list(),
    radius=75 / 2,
    cross_section=gf.cross_section.metal1,
    bend_port1="e2",
    bend_port2="e1",
)
c.add(routes_bend180.references)

routes = gf.routing.get_bundle(
    c1.get_ports_list(), routes_bend180.ports, cross_section=gf.cross_section.metal1
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:42.314 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_bundle_with_ubends_bend_from_bottom.lyp'.

../_images/c379474fb6a0ef47a914a15ff7584e963e1b63a9fcf66216c0a285e2ebd7cf0c.png

Problem

Sometimes 90 degrees routes do not have enough space for a Manhattan route

c = gf.Component("route_fail_1")
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
c.plot()
2024-03-09 00:43:42.515 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_fail_1.lyp'.

../_images/6bd088171e3e6f502166f0658b5a416404feb5511779e3fc3c6b6b4cffd1f5f7.png
c = gf.Component("route_fail_v2")
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
routes = gf.routing.get_bundle(
    c1.get_ports_list(orientation=0),
    c2.get_ports_list(orientation=180),
    auto_widen=False,
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:42.739 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_fail_v2.lyp'.

../_images/1b55cfd60ba6b05facb93f4a3a4f943e573df69a94e8c8ffba2734cdf26dc0c6.png
c = gf.Component("route_fail_v3")
pitch = 2.0
ys_left = [0, 10, 20]
N = len(ys_left)
ys_right = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

right_ports = [
    gf.Port(f"R_{i}", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)
    for i in range(N)
]
left_ports = [
    gf.Port(f"L_{i}", center=(-50, ys_left[i]), width=0.5, orientation=0, layer=layer)
    for i in range(N)
]
left_ports.reverse()
routes = gf.routing.get_bundle(right_ports, left_ports, radius=5)

for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:42.915 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_fail_v3.lyp'.

../_images/abbebf174c1abd4d3aa8703f9f9bf6de5133cc47590f66df8e2416f5779015af.png

Solution

Add Sbend routes using get_bundle_sbend

c = gf.Component("route_solution_1_get_bundle_sbend")
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
routes = gf.routing.get_bundle_sbend(
    c1.get_ports_list(orientation=0),
    c2.get_ports_list(orientation=180),
    enforce_port_ordering=False,  # sort ports automatically
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:43.080 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_solution_1_get_bundle_sbend.lyp'.

../_images/f92b82b4157ffad28e70cd215191ef61879280675f66d91c65aaa622071ec746.png

You can also get_bundle adding with_sbend=True

c = gf.Component("route_solution_2_get_bundle")
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
routes = gf.routing.get_bundle(
    c1.get_ports_list(orientation=0),
    c2.get_ports_list(orientation=180),
    enforce_port_ordering=False,  # to sort the ports automatically
    with_sbend=True,
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:43.235 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/route_solution_2_get_bundle.lyp'.

../_images/f92b82b4157ffad28e70cd215191ef61879280675f66d91c65aaa622071ec746.png

get_bundle with path_length_match#

Sometimes you need to route two groups of ports keeping the same route lengths.

c = gf.Component("path_length_match_routing")
dy = 2000.0
xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

pitch = 100.0
N = len(xs1)
xs2 = [-20 + i * pitch for i in range(N)]

a1 = 90
a2 = a1 + 180
layer = (1, 0)

ports1 = [
    gf.Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
    for i in range(N)
]
ports2 = [
    gf.Port(f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)
    for i in range(N)
]

routes = gf.routing.get_bundle(
    ports1,
    ports2,
    path_length_match_loops=1,
    path_length_match_modify_segment_i=-2,
    end_straight_length=800,
)

for route in routes:
    c.add(route.references)
    print(route.length)
c.show()
c.plot()
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2659.822
2024-03-09 00:43:43.598 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/path_length_match_routing.lyp'.

../_images/093750589256ba0f0f63584caf269d78dc13b67efe11cb56d06d479483a2222f.png

path_length_match with extra length#

You can also add some extra length to all the routes

c = gf.Component("get_bundle_path_length_match_extra_length")

dy = 2000.0
xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

pitch = 100.0
N = len(xs1)
xs2 = [-20 + i * pitch for i in range(N)]

a1 = 90
a2 = a1 + 180
layer = (1, 0)

ports1 = [
    gf.Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
    for i in range(N)
]
ports2 = [
    gf.Port(f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)
    for i in range(N)
]

routes = gf.routing.get_bundle(
    ports1,
    ports2,
    path_length_match_extra_length=44,
    path_length_match_loops=2,
    end_straight_length=800,
)
for route in routes:
    c.add(route.references)
    print(f"length = {route.length}um")
c.plot()
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
length = 2862.37um
2024-03-09 00:43:43.962 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_bundle_path_length_match_extra_length.lyp'.

../_images/2bd32ee7f11f6ff142c52820acc283a52fef134f18c4fd193cceab8cf687ef3e.png

path length match with extra loops#

You can also increase the number of loops

c = gf.Component("get_route_path_length_match_nb_loops")

dy = 2000.0
xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

pitch = 200.0
N = len(xs1)
xs2 = [-20 + i * pitch for i in range(N)]

a1 = 90
a2 = a1 + 180
layer = (1, 0)

ports1 = [
    gf.Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
    for i in range(N)
]
ports2 = [
    gf.Port(f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)
    for i in range(N)
]

routes = gf.routing.get_bundle(
    ports1,
    ports2,
    path_length_match_loops=2,
    auto_widen=False,
    end_straight_length=800,
    separation=30,
)
for route in routes:
    c.add(route.references)
    print(f"length = {route.length}um")
c.plot()
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
length = 3586.37um
2024-03-09 00:43:44.371 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_route_path_length_match_nb_loops.lyp'.

../_images/a42422517c11291d0b1545ab419c1c4d941a7539abaa1cca23402f7157d74fa6.png

Sometimes you need to modify separation to ensure waveguides don’t overlap.

c = gf.Component("problem_path_length_match")
c1 = c << gf.components.straight_array(spacing=90)
c2 = c << gf.components.straight_array(spacing=5)
c2.movex(200)
c1.y = 0
c2.y = 0

routes = gf.routing.get_bundle(
    c1.get_ports_list(orientation=0),
    c2.get_ports_list(orientation=180),
    end_straight_length=0,
    start_straight_length=0,
    separation=30,  # not enough
    radius=5,
    path_length_match_loops=1,
)

for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:44.611 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/problem_path_length_match.lyp'.

../_images/b7a56f075015aaec4b2c513e041f006bc0fd27f76a327a2d1db13553bddd1a24.png
c = gf.Component("solution_path_length_match")
c1 = c << gf.components.straight_array(spacing=90)
c2 = c << gf.components.straight_array(spacing=5)
c2.movex(200)
c1.y = 0
c2.y = 0

routes = gf.routing.get_bundle(
    c1.get_ports_list(orientation=0),
    c2.get_ports_list(orientation=180),
    end_straight_length=0,
    start_straight_length=0,
    separation=80,  # increased
    path_length_match_loops=1,
    radius=5,
)

for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:44.988 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/solution_path_length_match.lyp'.

../_images/37159990b3c8a87e54e90796c9c011ebb50fc38fc13eec0734bc6d7aba08721f.png

get bundle with different orientation ports#

When trying to route ports with different orientations you need to bring them to a common x or y

  1. Use route_ports_to_side to bring all the ports to a common angle orientation and x or y.

  2. Use get_bundle to connect to the other group of ports.

from gdsfactory.samples.big_device import big_device

c = gf.Component("sample_route")
c1 = c << big_device()
c2 = c << gf.components.grating_coupler_array(n=len(c1.ports), rotation=-90)

routes, ports = gf.routing.route_ports_to_side(c1.ports, side="south")
for route in routes:
    c.add(route.references)

c2.ymin = -600
c2.x = 0

routes = gf.routing.get_bundle(ports, c2.ports)
for route in routes:
    c.add(route.references)

c.plot_klayout()
c.show()
2024-03-09 00:43:46.011 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_route.lyp'.

../_images/14bb38dc07d03d8874614fec1f839b2dfc036355f8ba9c8263dd017d72293e59.png

get_bundle_from_steps#

This is a manual version of get_bundle that is more convenient than defining the waypoints.

c = gf.Component("get_route_from_steps_sample")
w = gf.components.array(
    partial(gf.components.straight, layer=(2, 0)),
    rows=3,
    columns=1,
    spacing=(0, 50),
)

left = c << w
right = c << w
right.move((200, 100))
p1 = left.get_ports_list(orientation=0)
p2 = right.get_ports_list(orientation=180)

routes = gf.routing.get_bundle_from_steps(
    p1,
    p2,
    steps=[{"x": 150}],
)

for route in routes:
    c.add(route.references)

c.plot()
2024-03-09 00:43:46.255 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_route_from_steps_sample.lyp'.

../_images/02fcf9150a640046f1f7c9264e918f55789121ae048f059442b6eec159790f47.png

Routing to IO#

Routing electrical#

For routing low speed DC electrical ports you can use sharp corners instead of smooth bends.

You can also define port.orientation = None to ignore the port orientation for low speed DC ports.

For single route between ports you can use get_route_electrical

get_route_electrical#

get_route_electrical has bend = wire_corner with a 90deg bend corner.

from functools import partial

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.samples.big_device import big_device

gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()

c = gf.Component("pads")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((70, 200))
c.plot()
2024-03-09 00:43:46.401 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads.lyp'.

../_images/d6aa58a0bf3a2662a3295d1f4fb391fa7ef1650213055b601775cd72acc0fa29.png
c = gf.Component("pads_with_routes_with_bends")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((70, 200))
route = gf.routing.get_route_electrical(
    pt.ports["e11"], pb.ports["e11"], bend="bend_euler", radius=30
)
c.add(route.references)
c.plot()
2024-03-09 00:43:46.561 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_with_routes_with_bends.lyp'.

../_images/0fef6526d17e0821e264790d45affe8192fd67793f8a0bb59389d1480d574b7e.png
c = gf.Component("pads_with_routes_with_wire_corners")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((70, 200))
route = gf.routing.get_route_electrical(
    pt.ports["e11"], pb.ports["e11"], bend="wire_corner"
)
c.add(route.references)
c.plot()
2024-03-09 00:43:46.715 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_with_routes_with_wire_corners.lyp'.

../_images/3c36386793a9594267548cab4cf4d04bd3fa3bcae2a02add9c36b75f2b8b9fc1.png
c = gf.Component("pads_with_routes_with_wire_corners_no_orientation")
pt = c << gf.components.pad_array(orientation=None, columns=3)
pb = c << gf.components.pad_array(orientation=None, columns=3)
pt.move((70, 200))
route = gf.routing.get_route_electrical(
    pt.ports["e11"], pb.ports["e11"], bend="wire_corner"
)
c.add(route.references)
c.plot()
2024-03-09 00:43:46.867 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_with_routes_with_wire_corners_no_orientation.lyp'.

../_images/dd765c153ade08d7376f123005fcfd46b55ff572901a9a6a265841b01bff9d4a.png
c = gf.Component("multi-layer")
columns = 2
ptop = c << gf.components.pad_array(columns=columns)
pbot = c << gf.components.pad_array(orientation=90, columns=columns)

ptop.movex(300)
ptop.movey(300)
route = gf.routing.get_route_electrical_multilayer(
    ptop.ports["e11"],
    pbot.ports["e11"],
    end_straight_length=100,
)
c.add(route.references)
c.plot()
2024-03-09 00:43:47.028 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/multi-layer.lyp'.

../_images/b10cea908f5ae836c2d427a855b302411010a81a55e75a6eaf268c524d2f920d.png

There is also bend = wire_corner45 for 45deg bend corner with parametrizable “radius”:

c = gf.Component("pads_with_routes_with_wire_corner45")
pt = c << gf.components.pad_array(orientation=270, columns=1)
pb = c << gf.components.pad_array(orientation=90, columns=1)
pt.move((300, 300))
route = gf.routing.get_route_electrical(
    pt.ports["e11"], pb.ports["e11"], bend="wire_corner45", radius=30
)
c.add(route.references)
c.plot()
2024-03-09 00:43:47.180 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_with_routes_with_wire_corner45.lyp'.

../_images/fa640825d07b1b9fa1f779b23914d4968847f9d4805060be619fd92ecf1beb19.png
c = gf.Component("pads_with_routes_with_wire_corner45")
pt = c << gf.components.pad_array(orientation=270, columns=1)
pb = c << gf.components.pad_array(orientation=90, columns=1)
pt.move((300, 300))
route = gf.routing.get_route_electrical(
    pt.ports["e11"], pb.ports["e11"], bend="wire_corner45", radius=100
)
c.add(route.references)
c.plot()
2024-03-09 00:43:47.335 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_with_routes_with_wire_corner45$1.lyp'.

../_images/fa640825d07b1b9fa1f779b23914d4968847f9d4805060be619fd92ecf1beb19.png

route_quad#

c = gf.Component("pads_route_quad")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((100, 200))
route = c << gf.routing.route_quad(pt.ports["e11"], pb.ports["e11"], layer=(49, 0))
c.plot()
2024-03-09 00:43:47.479 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_route_quad.lyp'.

../_images/0704be013091c45749650fecfe53695ff9c1b81faf20a041cab54debbbf333ad.png

get_route_from_steps#

c = gf.Component("pads_route_from_steps")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((100, 200))
route = gf.routing.get_route_from_steps(
    pb.ports["e11"],
    pt.ports["e11"],
    steps=[
        {"y": 200},
    ],
    cross_section="xs_m3",
    bend=gf.components.wire_corner,
)
c.add(route.references)
c.plot()
2024-03-09 00:43:47.622 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_route_from_steps.lyp'.

../_images/8fb057f422ab86cb2af7ff04a96c31f858e5923963447597935e9285d58b2cd3.png
c = gf.Component("pads_route_from_steps_None_orientation")
pt = c << gf.components.pad_array(orientation=None, columns=3)
pb = c << gf.components.pad_array(orientation=None, columns=3)
pt.move((100, 200))
route = gf.routing.get_route_from_steps(
    pb.ports["e11"],
    pt.ports["e11"],
    steps=[
        {"y": 200},
    ],
    cross_section="xs_m3",
    bend=gf.components.wire_corner,
)
c.add(route.references)
c.plot()
2024-03-09 00:43:47.765 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_route_from_steps_None_orientation.lyp'.

../_images/c9247a7e74dd146e675f7a986341098f68e8a913e9078e91493d58d424160a4d.png

get_bundle_electrical#

For routing groups of ports you can use get_bundle that returns a bundle of routes using a bundle router (also known as bus or river router)

c = gf.Component("pads_bundle")
pt = c << gf.components.pad_array(orientation=270, columns=3)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((100, 200))

routes = gf.routing.get_bundle_electrical(
    pb.ports, pt.ports, end_straight_length=60, separation=30
)

for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:48.101 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_bundle.lyp'.

../_images/135a421762d7a39b793f0af782a9e4c02a678a913b1bb35a8dbeeb75bcdd7e9e.png

get_bundle_from_steps_electrical#

c = gf.Component("pads_bundle_steps")
pt = c << gf.components.pad_array(
    partial(gf.components.pad, size=(30, 30)),
    orientation=270,
    columns=3,
    spacing=(50, 0),
)
pb = c << gf.components.pad_array(orientation=90, columns=3)
pt.move((300, 500))

routes = gf.routing.get_bundle_from_steps_electrical(
    pb.ports, pt.ports, end_straight_length=60, separation=30, steps=[{"dy": 100}]
)

for route in routes:
    c.add(route.references)

c.plot()
2024-03-09 00:43:48.268 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/pads_bundle_steps.lyp'.

../_images/781f0698356101aeb786e7c575a89183de3e00a2146644dd97068ff07c88deaf.png

get_bundle_electrical_multilayer#

To avoid metal crossings you can use one metal layer.

c = gf.Component("get_bundle_multi_layer")
columns = 2
ptop = c << gf.components.pad_array(columns=columns)
pbot = c << gf.components.pad_array(orientation=90, columns=columns)

ptop.movex(300)
ptop.movey(300)
routes = gf.routing.get_bundle_electrical_multilayer(
    ptop.ports, pbot.ports, end_straight_length=100, separation=20
)
for route in routes:
    c.add(route.references)
c.plot()
2024-03-09 00:43:48.441 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/get_bundle_multi_layer.lyp'.

../_images/87e13b202fd4827e61b0d52ddb848ff99241d5539f440f95aa47faa3af283a6f.png

Routing to pads#

You can also route to electrical pads.

c = gf.components.straight_heater_metal(length=100.0)
cc = gf.routing.add_pads_bot(component=c, port_names=("l_e4", "r_e4"), fanout_length=50)
cc.plot()
2024-03-09 00:43:48.621 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_heater_metal_undercut_add_pads_bot_946cf186.lyp'.

../_images/d5e3f763203dd2a1bacb4c3536ad43b9412459fd4f7bfebaae25498f91cb59d9.png
c = gf.components.straight_heater_metal(length=100.0)
cc = gf.routing.add_pads_top(component=c)
cc.plot()
2024-03-09 00:43:48.855 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_pads_bot_add_pads_top_70d254fb.lyp'.

../_images/dcb4310e26c79e409e4cadc6c6955f60ff1329360cd8acae818237b16a9884bd.png
c = gf.components.straight_heater_metal(length=100.0)
cc = gf.routing.add_pads_top(component=c, port_names=("l_e2", "r_e2"))
cc.plot()
2024-03-09 00:43:49.022 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_pads_bot_add_pads_top_11518f66.lyp'.

../_images/46b6aaeb9e87f523fa614c000c11a6617fd82574779efac345e0993120d121fe.png
n = west = north = south = east = 10
spacing = 20
c = gf.components.nxn(
    xsize=n * spacing,
    ysize=n * spacing,
    west=west,
    east=east,
    north=north,
    south=south,
    port_type="electrical",
    wg_width=10,
)
c.plot()
2024-03-09 00:43:49.171 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/nxn_f0b567f7.lyp'.

../_images/361721f6c1ee10186a5be9cc4fd698f1f521fb00d66c4fe7a073cb986ebfb576.png
cc = gf.routing.add_pads_top(component=c)
cc.plot()
2024-03-09 00:43:49.739 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/add_pads_bot_add_pads_top_componentnxn_f0b567f7.lyp'.

../_images/29fa63c739c75db965b16018452c9f0cb2c6169231d28324850168c20b3550d5.png

Routing to optical terminations#

Route to Fiber Array#

You can route to a fiber array.

component = big_device(nports=10)
c = gf.routing.add_fiber_array(component=component, radius=10.0, fanout_length=60.0)
c.plot()
2024-03-09 00:43:50.358 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/big_device_add_fiber_array_165286cd.lyp'.

../_images/71bb0ae5a6ee7a9560facd20eb02b2139fc0042a1d496ec9fa62094c92a38f3c.png

You can also mix and match TE and TM grating couplers. Notice that the TM polarization grating coupler is bigger.

import gdsfactory as gf

c = gf.components.mzi_phase_shifter()
gcte = gf.components.grating_coupler_te

cc = gf.routing.add_fiber_array(
    component=c,
    optical_routing_type=2,
    grating_coupler=[
        gf.components.grating_coupler_te,
        gf.components.grating_coupler_tm,
    ],
    radius=20,
)
cc.plot()
2024-03-09 00:43:50.609 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_add_fiber_array_c3d2fe92.lyp'.

../_images/afb57ef9b25150e7379b71ae872e9e37b0075c126e778fca40defd943b97bb9d.png

Route to Single fibers#

You can route to a single fiber input and single fiber output.

c = gf.components.ring_single()
cc = gf.routing.add_fiber_single(component=c)
cc.plot()
2024-03-09 00:43:50.764 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ring_single_add_fiber_single_componentring_single.lyp'.

../_images/ddb0e30177f8dcb49aecb0ec39a8d1648bdc55bad477b79b0804bd4909daa2db.png

Route to edge couplers#

You can also route Edge couplers to a fiber array or to both sides of the chip.

For routing to both sides you can follow different strategies:

  1. Place the edge couplers and route your components to the edge couplers.

  2. Extend your component ports to each side.

  3. Anything you imagine …

import numpy as np

import gdsfactory as gf
from gdsfactory.generic_tech import LAYER


@gf.cell
def sample_die(size=(8e3, 40e3), y_spacing: float = 10) -> gf.Component:
    """Returns a sample die

    Args:
        size: size of the die.
        y_spacing: spacing between components.

    Returns:
        c: a sample die.

    """
    c = gf.Component()

    die = c << gf.c.rectangle(size=np.array(size), layer=LAYER.FLOORPLAN, centered=True)
    die = c << gf.c.rectangle(
        size=np.array(size) - 2 * np.array((50, 50)),
        layer=LAYER.FLOORPLAN,
        centered=True,
    )
    ymin = die.ymin
    ec = gf.components.edge_coupler_silicon()

    cells = [
        "mmi1x2",
        "mmi2x2",
        "coupler",
        "mzi",
        "mzi_phase_shifter",
        "ring_single",
        "ring_double",
    ]

    for cell in cells:
        ci = gf.get_component(cell)
        ci = (
            gf.routing.add_pads_top(
                ci,
                pad=gf.components.pad,
                pad_spacing=150,
            )
            if ci.get_ports_list(port_type="electrical")
            else ci
        )
        ref = c << ci
        ref.ymin = ymin
        ref.x = 0
        ymin = ref.ymax + y_spacing

        routes_left, ports_left = gf.routing.route_ports_to_side(
            ref.get_ports_list(orientation=180),
            cross_section="xs_sc",
            side="west",
            x=die.xmin + ec.xsize,
        )
        for route in routes_left:
            c.add(route.references)

        routes_right, ports_right = gf.routing.route_ports_to_side(
            ref.get_ports_list(orientation=0),
            cross_section="xs_sc",
            x=die.xmax - ec.xsize,
            side="east",
        )
        for route in routes_right:
            c.add(route.references)

        for port in ports_right:
            ref = c << ec
            ref.connect("o1", port)
            text = c << gf.c.text(
                text=f"{ci.name}-{port.name.split('_')[0]}", size=10, layer=LAYER.MTOP
            )
            text.xmax = ref.xmax - 10
            text.y = ref.y

        for port in ports_left:
            ref = c << ec
            ref.connect("o1", port)
            text = c << gf.c.text(
                text=f"{ci.name}-{port.name.split('_')[0]}", size=10, layer=LAYER.MTOP
            )
            text.xmin = ref.xmin + 10
            text.y = ref.y

    return c


if __name__ == "__main__":
    c = sample_die()
    c.show(show_ports=True)
    c.plot()
2024-03-09 00:43:51.262 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/sample_die.lyp'.

../_images/d35e45b5be51ecd90b9ed717b6804136b2541fecd60e6751f1f93d53b0692348.png

PDK examples#

Different PDKs have different component libraries, design rules and layer stacks (GDS layers, materials and thickness).

When you install a PDK you have to make sure you also installed the correct version of gdsfactory.

Notice that some PDKs may have require different gdsfactory versions.

from collections.abc import Callable
from functools import partial

from pydantic import BaseModel

import gdsfactory as gf
from gdsfactory.add_pins import add_pin_rectangle_inside
from gdsfactory.component import Component
from gdsfactory.config import CONF
from gdsfactory.cross_section import cross_section
from gdsfactory.technology import (
    LayerLevel,
    LayerStack,
    LayerView,
    LayerViews,
    LayerMap,
)
from gdsfactory.typings import Layer
from gdsfactory.config import print_version_pdks, print_version_plugins
from gdsfactory.generic_tech import get_generic_pdk

gf.config.rich_output()
nm = 1e-3
p = gf.get_active_pdk()
p.name

'generic'
print_version_plugins()
                                                      Modules                                                      
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃    Package  version                                                                                      Path ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│     python  3.11.8 (main, Feb  7 2024, 04:02:05) [GCC        /opt/hostedtoolcache/Python/3.11.8/x64/bin/pyth… │
│             11.4.0]                                                                                           │
│ gdsfactory  7.19.0                                           /opt/hostedtoolcache/Python/3.11.8/x64/lib/pyth… │
│   gplugins  0.10.2                                           ['/opt/hostedtoolcache/Python/3.11.8/x64/lib/py… │
│        ray  not installed                                                                                     │
│    femwell  0.1.10                                           ['/opt/hostedtoolcache/Python/3.11.8/x64/lib/py… │
│     devsim  not installed                                                                                     │
│     tidy3d  2.5.2                                            ['/opt/hostedtoolcache/Python/3.11.8/x64/lib/py… │
│       meep  not installed                                                                                     │
│       meow  not installed                                                                                     │
│     lumapi  not installed                                                                                     │
│        sax  0.12.1                                           ['/opt/hostedtoolcache/Python/3.11.8/x64/lib/py… │
└────────────┴─────────────────────────────────────────────────┴──────────────────────────────────────────────────┘
print_version_pdks()
               PDKs               
┏━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━┓
┃ Package  version        Path ┃
┡━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━┩
│     aim  not installed       │
│     amf  not installed       │
│      ct  not installed       │
│   gf180  not installed       │
│    gf45  not installed       │
│     hhi  not installed       │
│    imec  not installed       │
│  sky130  not installed       │
│     sph  not installed       │
│      tj  not installed       │
│  ubcpdk  not installed       │
│    gvtt  not installed       │
└─────────┴───────────────┴──────┘

FabA#

FabA only has one waveguide layer available that is defined in GDS layer (30, 0)

The waveguide traces are 2um wide.

class LayerMapFabA(LayerMap):
    WG: Layer = (34, 0)
    SLAB150: Layer = (2, 0)
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    TEXT: Layer = (66, 0)


LAYER = LayerMapFabA()


class FabALayerViews(LayerViews):
    WG: LayerView = LayerView(color="gold")
    SLAB150: LayerView = LayerView(color="red")
    TE: LayerView = LayerView(color="green")


LAYER_VIEWS = FabALayerViews(layer_map=dict(LAYER))


def get_layer_stack_faba(
    thickness_wg: float = 500 * nm, thickness_slab: float = 150 * nm
) -> LayerStack:
    """Returns fabA LayerStack"""

    return LayerStack(
        layers=dict(
            strip=LayerLevel(
                layer=LAYER.WG,
                thickness=thickness_wg,
                zmin=0.0,
                material="si",
            ),
            strip2=LayerLevel(
                layer=LAYER.SLAB150,
                thickness=thickness_slab,
                zmin=0.0,
                material="si",
            ),
        )
    )


LAYER_STACK = get_layer_stack_faba()

WIDTH = 2

# Specify a cross_section to use
strip = partial(gf.cross_section.cross_section, width=WIDTH, layer=LAYER.WG)

mmi1x2 = partial(
    gf.components.mmi1x2,
    width=WIDTH,
    width_taper=WIDTH,
    width_mmi=3 * WIDTH,
    cross_section=strip,
)

generic_pdk = get_generic_pdk()

fab_a = gf.Pdk(
    name="Fab_A",
    cells=dict(mmi1x2=mmi1x2),
    cross_sections=dict(strip=strip),
    layers=dict(LAYER),
    base_pdk=generic_pdk,
    layer_views=LAYER_VIEWS,
    layer_stack=LAYER_STACK,
)
fab_a.activate()

gc = partial(
    gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip
)

c = gf.components.mzi()
c_gc = gf.routing.add_fiber_array(component=c, grating_coupler=gc, with_loopback=False)
c_gc.plot()
2024-03-09 00:43:52.565 | WARNING  | gdsfactory.pdk:activate:316 - UserWarning: base_pdk is deprecated. Use base_pdks instead
2024-03-09 00:43:52.617 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_add_fiber_array_8b18d76b.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/pdk.py:316: UserWarning: base_pdk is deprecated. Use base_pdks instead
  warnings.warn("base_pdk is deprecated. Use base_pdks instead")

../_images/e575f04277bf66f35abcbaaa0ced3e3483ca9d0833cfd614b593feac48e5be97.png
scene = c_gc.to_3d()
scene.show(show_ports=True)

FabB#

FabB has photonic waveguides that require rectangular cladding layers to avoid dopants

Lets say that the waveguides are defined in layer (2, 0) and are 0.3um wide, 1um thick

nm = 1e-3


class LayerMapFabB(LayerMap):
    WG: Layer = (2, 0)
    SLAB150: Layer = (3, 0)
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    TEXT: Layer = (66, 0)
    DOPING_BLOCK1: Layer = (61, 0)
    DOPING_BLOCK2: Layer = (62, 0)


LAYER = LayerMapFabB()


# The LayerViews class supports grouping LayerViews within each other.
# These groups are maintained when exporting a LayerViews object to a KLayout layer properties (.lyp) file.
class FabBLayerViews(LayerViews):
    WG: LayerView = LayerView(color="red")
    SLAB150: LayerView = LayerView(color="blue")
    TE: LayerView = LayerView(color="green")
    PORT: LayerView = LayerView(color="green", alpha=0)

    class DopingBlockGroup(LayerView):
        DOPING_BLOCK1: LayerView = LayerView(color="green", alpha=0)
        DOPING_BLOCK2: LayerView = LayerView(color="green", alpha=0)

    DopingBlocks: LayerView = DopingBlockGroup()


LAYER_VIEWS = FabBLayerViews(layer_map=LAYER)


def get_layer_stack_fab_b(
    thickness_wg: float = 1000 * nm, thickness_slab: float = 150 * nm
) -> LayerStack:
    """Returns fabA LayerStack."""

    return LayerStack(
        layers=dict(
            strip=LayerLevel(
                layer=LAYER.WG,
                thickness=thickness_wg,
                zmin=0.0,
                material="si",
            ),
            strip2=LayerLevel(
                layer=LAYER.SLAB150,
                thickness=thickness_slab,
                zmin=0.0,
                material="si",
            ),
        )
    )


LAYER_STACK = get_layer_stack_fab_b()


WIDTH = 0.3
BBOX_LAYERS = (LAYER.DOPING_BLOCK1, LAYER.DOPING_BLOCK2)
BBOX_OFFSETS = (3, 3)

# use cladding_layers and cladding_offsets if the foundry prefers conformal blocking doping layers instead of squared
# bbox_layers and bbox_offsets makes rectangular waveguides.
strip = partial(
    gf.cross_section.cross_section,
    width=WIDTH,
    layer=LAYER.WG,
    # bbox_layers=BBOX_LAYERS,
    # bbox_offsets=BBOX_OFFSETS,
    cladding_layers=BBOX_LAYERS,
    cladding_offsets=BBOX_OFFSETS,
)

straight = partial(gf.components.straight, cross_section=strip)
bend_euler = partial(gf.components.bend_euler, cross_section=strip)
mmi1x2 = partial(
    gf.components.mmi1x2,
    cross_section=strip,
    width=WIDTH,
    width_taper=WIDTH,
    width_mmi=4 * WIDTH,
)
mzi = partial(gf.components.mzi, cross_section=strip, splitter=mmi1x2)
gc = partial(
    gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip
)

cells = dict(
    gc=gc,
    mzi=mzi,
    mmi1x2=mmi1x2,
    bend_euler=bend_euler,
    straight=straight,
    taper=gf.components.taper,
)
cross_sections = dict(strip=strip)

pdk = gf.Pdk(
    name="fab_b",
    cells=cells,
    cross_sections=cross_sections,
    layers=dict(LAYER),
    layer_views=LAYER_VIEWS,
    layer_stack=LAYER_STACK,
)
pdk.activate()


c = mzi()
wg_gc = gf.routing.add_fiber_array(
    component=c, grating_coupler=gc, cross_section=strip, with_loopback=False
)
wg_gc.plot()
2024-03-09 00:43:52.945 | WARNING  | gdsfactory.cross_section:copy:301 - UserWarning: CrossSection.copy() only modifies the attributes of the first section.
2024-03-09 00:43:53.007 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_add_fiber_array_197a5f68.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/pdk.py:538: UserWarning: CrossSection.copy() only modifies the attributes of the first section.
  return cross_section.copy(**kwargs)

../_images/762bb562505072f9adbd6d3361f5cdfe3d0b2c82ec3f05ee4bf70a130f1a4464.png
scene = wg_gc.to_3d()
scene.show(show_ports=True)

FabC#

Lets assume that fab C has similar technology to the generic PDK in gdsfactory and that you just want to remap some layers, and adjust the widths.

nm = 1e-3


class LayerMapFabC(LayerMap):
    WG: Layer = (10, 1)
    WG_CLAD: Layer = (10, 2)
    WGN: Layer = (34, 0)
    WGN_CLAD: Layer = (36, 0)
    SLAB150: Layer = (2, 0)
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    TEXT: Layer = (66, 0)


LAYER = LayerMapFabC()
WIDTH_NITRIDE_OBAND = 0.9
WIDTH_NITRIDE_CBAND = 1.0
PORT_TYPE_TO_LAYER = dict(optical=(100, 0))


# This is something you usually define in KLayout
class FabCLayerViews(LayerViews):
    WG: LayerView = LayerView(color="black")
    SLAB150: LayerView = LayerView(color="blue")
    WGN: LayerView = LayerView(color="orange")
    WGN_CLAD: LayerView = LayerView(color="blue", alpha=0, visible=False)

    class SimulationGroup(LayerView):
        TE: LayerView = LayerView(color="green")
        PORT: LayerView = LayerView(color="green", alpha=0)

    Simulation: LayerView = SimulationGroup()

    class DopingGroup(LayerView):
        DOPING_BLOCK1: LayerView = LayerView(color="green", alpha=0, visible=False)
        DOPING_BLOCK2: LayerView = LayerView(color="green", alpha=0, visible=False)

    Doping: LayerView = DopingGroup()


LAYER_VIEWS = FabCLayerViews(layer_map=LAYER)


def get_layer_stack_fab_c(
    thickness_wg: float = 350.0 * nm, thickness_clad: float = 3.0
) -> LayerStack:
    """Returns generic LayerStack"""

    return LayerStack(
        layers=dict(
            core=LayerLevel(
                layer=LAYER.WGN,
                thickness=thickness_wg,
                zmin=0,
            ),
            clad=LayerLevel(
                layer=LAYER.WGN_CLAD,
                thickness=thickness_clad,
                zmin=0,
            ),
        )
    )


LAYER_STACK = get_layer_stack_fab_c()

# cross_section constants
bbox_layers = [LAYER.WGN_CLAD]
bbox_offsets = [3]

# Nitride Cband
xs_nc = partial(
    cross_section,
    width=WIDTH_NITRIDE_CBAND,
    layer=LAYER.WGN,
    bbox_layers=bbox_layers,
    bbox_offsets=bbox_offsets,
)
# Nitride Oband
xs_no = partial(
    cross_section,
    width=WIDTH_NITRIDE_OBAND,
    layer=LAYER.WGN,
    bbox_layers=bbox_layers,
    bbox_offsets=bbox_offsets,
)


cross_sections = dict(xs_nc=xs_nc, xs_no=xs_no, strip=xs_nc)

# LEAF cells have pins
mmi1x2_nc = partial(
    gf.components.mmi1x2,
    width=WIDTH_NITRIDE_CBAND,
    cross_section=xs_nc,
)
mmi1x2_no = partial(
    gf.components.mmi1x2,
    width=WIDTH_NITRIDE_OBAND,
    cross_section=xs_no,
)
bend_euler_nc = partial(
    gf.components.bend_euler,
    cross_section=xs_nc,
)
straight_nc = partial(
    gf.components.straight,
    cross_section=xs_nc,
)
bend_euler_no = partial(
    gf.components.bend_euler,
    cross_section=xs_no,
)
straight_no = partial(
    gf.components.straight,
    cross_section=xs_no,
)

gc_nc = partial(
    gf.components.grating_coupler_elliptical_te,
    grating_line_width=0.6,
    layer=LAYER.WGN,
    cross_section=xs_nc,
)

# HIERARCHICAL cells are made of leaf cells
mzi_nc = partial(
    gf.components.mzi,
    cross_section=xs_nc,
    splitter=mmi1x2_nc,
    straight=straight_nc,
    bend=bend_euler_nc,
)
mzi_no = partial(
    gf.components.mzi,
    cross_section=xs_no,
    splitter=mmi1x2_no,
    straight=straight_no,
    bend=bend_euler_no,
)


cells = dict(
    mmi1x2_nc=mmi1x2_nc,
    mmi1x2_no=mmi1x2_no,
    bend_euler_nc=bend_euler_nc,
    bend_euler_no=bend_euler_no,
    straight_nc=straight_nc,
    straight_no=straight_no,
    gc_nc=gc_nc,
    mzi_nc=mzi_nc,
    mzi_no=mzi_no,
)

pdk = gf.Pdk(
    name="fab_c",
    cells=cells,
    cross_sections=cross_sections,
    layers=dict(LAYER),
    layer_views=LAYER_VIEWS,
    layer_stack=LAYER_STACK,
)
pdk.activate()
LAYER_VIEWS.layer_map

{
    'LABEL_INSTANCE': (206, 0),
    'LABEL_SETTINGS': (202, 0),
    'WG': (10, 1),
    'WG_CLAD': (10, 2),
    'WGN': (34, 0),
    'WGN_CLAD': (36, 0),
    'SLAB150': (2, 0),
    'DEVREC': (68, 0),
    'PORT': (1, 10),
    'PORTE': (1, 11),
    'TEXT': (66, 0)
}
mzi = mzi_nc()
mzi_gc = gf.routing.add_fiber_single(
    component=mzi,
    grating_coupler=gc_nc,
    cross_section=xs_nc,
    optical_routing_type=1,
    straight=straight_nc,
    bend=bend_euler_nc,
)
mzi_gc.plot()
2024-03-09 00:43:53.417 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_add_fiber_single_3cf6b2d6.lyp'.

../_images/34d8e8db29248dce88857a2b4292beea41150139c7c89b350b68207b38b5907a.png
c = mzi_gc.to_3d()
c.show(show_ports=True)

ls = get_layer_stack_fab_c()

Import PDK#

Import PDK from GDS files#

To import a PDK from GDS files into gdsfactory you need:

  • GDS file with all the cells that you want to import in the PDK (or separate GDS files, where each file contains a GDS design).

Ideally you also get:

  • Klayout layer properties files, to define the Layers that you can use when creating new custom Components. This allows you to define the LayerMap that maps Layer_name to (GDS_LAYER, GDS_PuRPOSE).

  • layer_stack information (material index, thickness, z positions of each layer).

  • DRC rules. If you don’t get this you can easily build one using klayout.

GDS files are great for describing geometry thanks to the concept of References, where you store any geometry only once in memory.

For storing device metadata (settings, port locations, port widths, port angles …) there is no clear standard.

gdsfactory stores the that metadata in YAML files, and also has functions to add pins

  • Component.write_gds() saves GDS

  • Component.write_gds_metadata() save GDS + YAML metadata

# Lets generate the script that we need to have to each GDS cell into gdsfactory
import gdsfactory as gf
from gdsfactory.config import PATH
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.technology import lyp_to_dataclass

c = gf.components.mzi(cross_section="xs_nc", splitter="mmi1x2_no")
c.plot()
2024-03-09 00:43:53.834 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_9ef0c47a.lyp'.

../_images/d6a8012b1d06b3bcfbf50fa94c15840d8469fa2fe0d819380ece14ac998e190f.png

You can write GDS files only

gdspath = c.write_gds("extra/mzi.gds")
2024-03-09 00:43:53.972 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'extra/mzi.gds'

Or GDS with YAML metadata information (ports, settings, cells …)

gdspath = c.write_gds("extra/mzi.gds", with_metadata=True)
2024-03-09 00:43:53.978 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'extra/mzi.gds'
2024-03-09 00:43:53.998 | INFO     | gdsfactory.component:_write_library:2025 - Write YAML metadata to 'extra/mzi.yml'

This created a mzi.yml file that contains:

  • ports

  • cells (flat list of cells)

  • info (function name, module, changed settings, full settings, default settings)

c.metadata.keys()
2024-03-09 00:43:54.003 | WARNING  | gdsfactory.component:metadata:2663 - UserWarning: metadata is deprecated and will be removed in future versions of gdsfactory. Use component.settings for accessing component settings or component.info for component info.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:2663: UserWarning: metadata is deprecated and will be removed in future versions of gdsfactory. Use component.settings for accessing component settings or component.info for component info.
  warnings.warn(

dict_keys(['delta_length', 'length_y', 'length_x', 'bend', 'straight', 'straight_y', 'straight_x_top', 'straight_x_bot', 'extend_ports_straight_x', 'splitter', 'combiner', 'with_splitter', 'port_e1_splitter', 'port_e0_splitter', 'port_e1_combiner', 'port_e0_combiner', 'nbends', 'cross_section', 'cross_section_x_top', 'cross_section_x_bot', 'mirror_bot', 'add_optical_ports_arms', 'add_electrical_ports_bot', 'post_process', 'info'])

You can read GDS files into gdsfactory thanks to the import_gds function

import_gds reads the same GDS file from disk without losing any information

gf.clear_cache()

c = gf.import_gds(gdspath, read_metadata=True)
c.plot()
2024-03-09 00:43:54.013 | INFO     | gdsfactory.read.import_gds:import_gds:120 - Read YAML metadata from extra/mzi.yml
2024-03-09 00:43:54.064 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_9ef0c47a.lyp'.

../_images/d6a8012b1d06b3bcfbf50fa94c15840d8469fa2fe0d819380ece14ac998e190f.png
c2 = gf.import_gds(gdspath, name="mzi_sample", read_metadata=True)
c2.plot()
2024-03-09 00:43:54.229 | WARNING  | gdsfactory.cell:wrapper:161 - UserWarning: name is deprecated and will be removed soon. import_gds
2024-03-09 00:43:54.231 | INFO     | gdsfactory.read.import_gds:import_gds:120 - Read YAML metadata from extra/mzi.yml
2024-03-09 00:43:54.293 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_9ef0c47a.lyp'.
/tmp/ipykernel_2765/885810688.py:1: UserWarning: name is deprecated and will be removed soon. import_gds
  c2 = gf.import_gds(gdspath, name="mzi_sample", read_metadata=True)

../_images/d6a8012b1d06b3bcfbf50fa94c15840d8469fa2fe0d819380ece14ac998e190f.png
c2.name

'mzi_9ef0c47a'
c3 = gf.routing.add_fiber_array(c2, cross_section="xs_nc", grating_coupler="gc_nc")
c3.plot()
2024-03-09 00:43:54.516 | WARNING  | gdsfactory.component:_write_library:1980 - UserWarning: Duplicated cell names in 'mzi_add_fiber_array_75536d58':  ['bend_euler_a250f5f7']
2024-03-09 00:43:54.519 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/mzi_add_fiber_array_75536d58.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1663: UserWarning: Duplicated cell names in 'mzi_add_fiber_array_75536d58':  ['bend_euler_a250f5f7']
  gdspath = component.write_gds(logging=False)

../_images/362bf0d49f17bb82b42de59f7107bec8abadb9b672f04b3557ffd210b6219656.png
gdspath = c3.write_gds("extra/pdk.gds", with_metadata=True)
2024-03-09 00:43:54.681 | WARNING  | gdsfactory.component:_write_library:1980 - UserWarning: Duplicated cell names in 'mzi_add_fiber_array_75536d58':  ['bend_euler_a250f5f7']
2024-03-09 00:43:54.683 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'extra/pdk.gds'
/tmp/ipykernel_2765/3450131073.py:1: UserWarning: Duplicated cell names in 'mzi_add_fiber_array_75536d58':  ['bend_euler_a250f5f7']
  gdspath = c3.write_gds("extra/pdk.gds", with_metadata=True)
2024-03-09 00:43:54.718 | INFO     | gdsfactory.component:_write_library:2025 - Write YAML metadata to 'extra/pdk.yml'
gf.labels.write_labels.write_labels_klayout(gdspath, layer_label=(201, 0))
2024-03-09 00:43:54.724 | INFO     | gdsfactory.labels.write_labels:write_labels_klayout:95 - Wrote 0 labels to CSV /home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/extra/pdk.csv

PosixPath('extra/pdk.csv')

add ports from pins#

Sometimes the GDS does not have YAML metadata, therefore you need to figure out the port locations, widths and orientations.

gdsfactory provides you with functions that will add ports to the component by looking for pins shapes on a specific layers (port_markers or pins)

There are different pin standards supported to automatically add ports to components:

  • PINs towards the inside of the port (port at the outer part of the PIN)

  • PINs with half of the pin inside and half outside (port at the center of the PIN)

  • PIN with only labels (no shapes). You have to manually specify the width of the port.

Lets add pins, save a GDS and then import it back.

pdk = get_generic_pdk()
pdk.activate()

c = gf.components.straight()
c = gf.add_pins.add_pins_container(c)
c.plot()
2024-03-09 00:43:54.747 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_container_ceeea98c.lyp'.

../_images/73314d12db0c85da0a742e5be9411f9b17d635333f1f0f87d029e83221d76f18.png
gdspath = c.write_gds("extra/wg.gds")
2024-03-09 00:43:54.873 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'extra/wg.gds'
gf.clear_cache()
c2 = gf.import_gds(gdspath)
c2.plot()
2024-03-09 00:43:54.893 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_container_ceeea98c.lyp'.

../_images/44e5bf94a26ecaecf54934bb63ed52a3ae3f8fad28c97cb7719f33e80319c87a.png
c2.ports  # import_gds does not automatically add the pins

{}
c3 = gf.import_gds(gdspath)
c3 = gf.add_ports.add_ports_from_markers_inside(c3)
c3.plot()
2024-03-09 00:43:55.039 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_container_ceeea98c.lyp'.

../_images/73314d12db0c85da0a742e5be9411f9b17d635333f1f0f87d029e83221d76f18.png
c3.ports

{
    'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180, 'layer': [1, 10], 'port_type': 'optical'},
    'o2': {'name': 'o2', 'width': 0.5, 'center': [10.0, 0.0], 'orientation': 0, 'layer': [1, 10], 'port_type': 'optical'}
}

Foundries provide PDKs in different formats and commercial tools.

The easiest way to import a PDK into gdsfactory is to:

  1. have each GDS cell into a separate GDS file

  2. have one GDS file with all the cells inside

  3. Have a KLayout layermap. Makes easier to create the layermap.

With that you can easily create the PDK as as python package.

Thanks to having a gdsfactory PDK as a python package you can:

  • version control your PDK using GIT to keep track of changes and work on a team

    • write tests of your pdk components to avoid unwanted changes from one component to another.

    • ensure you maintain the quality of the PDK with continuous integration checks

    • pin the version of gdsfactory, so new updates of gdsfactory won’t affect your code

  • name your PDK version using semantic versioning. For example patches increase the last number (0.0.1 -> 0.0.2)

  • install your PDK easily pip install pdk_fab_a and easily interface with other tools

To create a Python package you can start from a customizable template (thanks to cookiecutter)

You can create a python package by running this 2 commands inside a terminal:

pip install cookiecutter
cookiecutter gh:joamatab/python

It will ask you some questions to fill in the template (name of the package being the most important)

Then you can add the information about the GDS files and the Layers inside that package

print(lyp_to_dataclass(PATH.klayout_lyp))
from gdsfactory.typings import Layer
from gdsfactory.technology.layer_map import LayerMap


class LayerMapFab(LayerMap):
    CAPACITOR: Layer = (42, 0)
    DEEPTRENCH: Layer = (4, 0)
    DEEP_ETCH: Layer = (3, 6)
    DICING: Layer = (65, 0)
    DRC_EXCLUDE: Layer = (67, 0)
    DRC_MARKER: Layer = (205, 0)
    DevRec: Layer = (68, 0)
    ERROR_PATH: Layer = (1000, 0)
    Errors: Layer = (69, 0)
    FLOORPLAN: Layer = (64, 0)
    FbrTgt: Layer = (81, 0)
    GE: Layer = (5, 0)
    GENPP: Layer = (26, 0)
    GEPPP: Layer = (29, 0)
    LABEL_INSTANCES: Layer = (206, 0)
    LABEL_OPTICAL_IO: Layer = (201, 0)
    LABEL_SETTINGS: Layer = (202, 0)
    Lumerical: Layer = (733, 0)
    M1: Layer = (41, 0)
    M1TILES: Layer = (191, 0)
    M2: Layer = (45, 0)
    M3: Layer = (49, 0)
    METALOPEN: Layer = (46, 0)
    MH: Layer = (47, 0)
    MONITOR: Layer = (101, 0)
    N: Layer = (20, 0)
    NOTILE_M1: Layer = (71, 0)
    NOTILE_M2: Layer = (72, 0)
    NOTILE_M3: Layer = (73, 0)
    NP: Layer = (22, 0)
    NPP: Layer = (24, 0)
    OXIDE_ETCH: Layer = (6, 0)
    PDPP: Layer = (27, 0)
    PP: Layer = (23, 0)
    PPP: Layer = (25, 0)
    P_210: Layer = (21, 0)
    PinRec: Layer = (1, 10)
    PinRecM: Layer = (1, 11)
    SHALLOW_ETCH: Layer = (2, 6)
    SHOW_PORTS: Layer = (1, 12)
    SILICIDE: Layer = (39, 0)
    SIM_REGION: Layer = (100, 0)
    SITILES: Layer = (190, 0)
    SLAB150: Layer = (2, 0)
    SLAB150CLAD: Layer = (2, 9)
    SLAB90: Layer = (3, 0)
    SLAB90CLAD: Layer = (3, 1)
    SOURCE: Layer = (110, 0)
    TE: Layer = (203, 0)
    TM: Layer = (204, 0)
    Text: Layer = (66, 0)
    VIA1: Layer = (44, 0)
    VIA2: Layer = (43, 0)
    VIAC: Layer = (40, 0)
    WAFER: Layer = (99999, 0)
    WGCLAD: Layer = (111, 0)
    WGN_Nitride: Layer = (34, 0)
    WGclad_material: Layer = (36, 0)
    Waveguide: Layer = (1, 0)
    XS_BOX: Layer = (300, 0)
    XS_GE: Layer = (315, 0)
    XS_M2: Layer = (399, 0)
    XS_MH: Layer = (306, 0)
    XS_N: Layer = (320, 0)
    XS_NPP: Layer = (321, 0)
    XS_OVERLAY: Layer = (311, 0)
    XS_OXIDE_M1: Layer = (305, 0)
    XS_OXIDE_M2: Layer = (307, 0)
    XS_OXIDE_M3: Layer = (311, 0)
    XS_OXIDE_MH: Layer = (317, 0)
    XS_OXIDE_ML: Layer = (309, 0)
    XS_OX_NIT_CLAD: Layer = (304, 0)
    XS_OX_SI: Layer = (302, 0)
    XS_P: Layer = (330, 0)
    XS_PDPP: Layer = (327, 0)
    XS_PPP: Layer = (331, 0)
    XS_SI: Layer = (301, 0)
    XS_SIN: Layer = (319, 0)
    XS_SIN2: Layer = (305, 0)
    XS_SI_SLAB: Layer = (313, 0)
    XS_VIA1: Layer = (308, 0)
    XS_VIA2: Layer = (310, 0)
    XS_VIAC: Layer = (303, 0)


LAYER = LayerMapFab()
# lets create a sample PDK (for demo purposes only) using GDSfactory
# if the PDK is in a commercial tool you can also do this. Make sure you save a single pdk.gds

sample_pdk_cells = gf.grid(
    [
        gf.components.straight,
        gf.components.bend_euler,
        gf.components.grating_coupler_elliptical,
    ]
)
sample_pdk_cells.write_gds("extra/pdk.gds")
sample_pdk_cells.plot()
2024-03-09 00:43:55.206 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'extra/pdk.gds'
2024-03-09 00:43:55.219 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/grid_d9610e5f.lyp'.

../_images/95be300d97cbce9a7c2692cca7c17f499348150be5e97f0c45523b73ac5760cf.png
sample_pdk_cells.get_dependencies()

[
    bend_euler: uid e87a72bb, ports ['o1', 'o2'], references [], 1 polygons,
    straight: uid 569dc9de, ports ['o1', 'o2'], references [], 1 polygons,
    grating_coupler_elliptical: uid f4d70afa, ports ['o1', 'o2'], references [], 32 polygons
]
# we write the sample PDK into a single GDS file
gf.clear_cache()
gf.write_cells.write_cells_recursively(gdspath="extra/pdk.gds", dirpath="extra/gds")
2024-03-09 00:43:55.365 | INFO     | gdsfactory.write_cells:write_cells_recursively:152 - Write 'grid_d9610e5f' to extra/gds/grid_d9610e5f.gds
2024-03-09 00:43:55.367 | INFO     | gdsfactory.write_cells:write_cells_recursively:152 - Write 'bend_euler' to extra/gds/bend_euler.gds
2024-03-09 00:43:55.368 | INFO     | gdsfactory.write_cells:write_cells_recursively:152 - Write 'grating_coupler_elliptical' to extra/gds/grating_coupler_elliptical.gds
2024-03-09 00:43:55.369 | INFO     | gdsfactory.write_cells:write_cells_recursively:152 - Write 'straight' to extra/gds/straight.gds

{
    'grid_d9610e5f': PosixPath('extra/gds/grid_d9610e5f.gds'),
    'bend_euler': PosixPath('extra/gds/bend_euler.gds'),
    'grating_coupler_elliptical': PosixPath('extra/gds/grating_coupler_elliptical.gds'),
    'straight': PosixPath('extra/gds/straight.gds')
}
print(gf.write_cells.get_import_gds_script("extra/gds"))
2024-03-09 00:43:55.378 | INFO     | gdsfactory.write_cells:get_import_gds_script:105 - Writing 4 cells from PosixPath('/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/extra/gds')
from pathlib import PosixPath
from functools import partial
import gdsfactory as gf

add_ports_optical = partial(
    gf.add_ports.add_ports_from_markers_inside, pin_layer=(1, 0), port_layer=(2, 0)
)
add_ports_electrical = partial(
    gf.add_ports.add_ports_from_markers_inside, pin_layer=(41, 0), port_layer=(1, 0)
)
add_ports = gf.compose(add_ports_optical, add_ports_electrical)


gdsdir = '/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/extra/gds'

import_gds = partial(gf.import_gds, gdsdir=gdsdir, decorator=add_ports)



@gf.cell
def bend_euler()->gf.Component:
    '''Returns bend_euler fixed cell.'''
    return import_gds('bend_euler.gds')




@gf.cell
def grating_coupler_elliptical()->gf.Component:
    '''Returns grating_coupler_elliptical fixed cell.'''
    return import_gds('grating_coupler_elliptical.gds')




@gf.cell
def grid_d9610e5f()->gf.Component:
    '''Returns grid_d9610e5f fixed cell.'''
    return import_gds('grid_d9610e5f.gds')




@gf.cell
def straight()->gf.Component:
    '''Returns straight fixed cell.'''
    return import_gds('straight.gds')

You can also include the code to plot each fix cell in the docstring.

print(gf.write_cells.get_import_gds_script("extra/gds", module="samplepdk.components"))
2024-03-09 00:43:55.384 | INFO     | gdsfactory.write_cells:get_import_gds_script:105 - Writing 4 cells from PosixPath('/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/extra/gds')
from pathlib import PosixPath
from functools import partial
import gdsfactory as gf

add_ports_optical = partial(
    gf.add_ports.add_ports_from_markers_inside, pin_layer=(1, 0), port_layer=(2, 0)
)
add_ports_electrical = partial(
    gf.add_ports.add_ports_from_markers_inside, pin_layer=(41, 0), port_layer=(1, 0)
)
add_ports = gf.compose(add_ports_optical, add_ports_electrical)


gdsdir = '/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/extra/gds'

import_gds = partial(gf.import_gds, gdsdir=gdsdir, decorator=add_ports)



@gf.cell
def bend_euler()->gf.Component:
    '''Returns bend_euler fixed cell.'''
    return import_gds('bend_euler.gds')




@gf.cell
def grating_coupler_elliptical()->gf.Component:
    '''Returns grating_coupler_elliptical fixed cell.'''
    return import_gds('grating_coupler_elliptical.gds')




@gf.cell
def grid_d9610e5f()->gf.Component:
    '''Returns grid_d9610e5f fixed cell.'''
    return import_gds('grid_d9610e5f.gds')




@gf.cell
def straight()->gf.Component:
    '''Returns straight fixed cell.'''
    return import_gds('straight.gds')

Import PDK from other python packages#

You can Write the cells to GDS and use the

Ideally you also start transitioning your legacy code Pcells into gdsfactory syntax. It’s a great way to learn the gdsfactory way!

Here is some advice:

  • Ask your foundry for the gdsfactory PDK.

  • Leverage the generic pdk cells available in gdsfactory.

  • Write tests for your cells.

  • Break the cells into small reusable functions.

  • use GIT to track changes.

  • review your code with your colleagues and other gdsfactory developers to get feedback. This is key to get better at coding gdsfactory.

  • get rid of any warnings you see.

Build your own PDK#

You can create a PDK as a python library using a cookiecutter template. For example, you can use this one.

pip install cookiecutter
cookiecutter gh:joamatab/python

Or you can fork the ubcpdk and create new PCell functions that use the correct layers for your foundry. For example.


from pydantic import BaseModel


class LayerMap(BaseModel):
    WGCORE = (3, 0)
    LABEL = (100, 0)
    DEVREC: Layer = (68, 0)
    LABEL: Layer = (10, 0)
    PORT: Layer = (1, 10)  # PinRec
    PORTE: Layer = (1, 11)  # PinRecM
    FLOORPLAN: Layer = (99, 0)

    TE: Layer = (203, 0)
    TM: Layer = (204, 0)
    TEXT: Layer = (66, 0)
    LABEL_INSTANCE: Layer = (66, 0)


LAYER = LayerMap()