Cell#

A @cell is a decorator for functions that return a Component. Make sure you add the @cell decorator to each function that returns a Component so you avoid having multiple components with the same name.

Why do you need to add the @cell decorator?

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

What is a decorator?

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


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


c = mzi_with_bend()
print(f"this cell {c.name!r} does NOT get automatic name")
c.plot()
this cell 'Unnamed_ce9150f4' does NOT get automatic name
2024-04-25 20:20:09.226 | WARNING  | gdsfactory.component:plot_klayout:1645 - UserWarning: Unnamed cells, 1 in 'Unnamed_ce9150f4'
../_images/255adc277956e1a2797a1e1e48c2592a2f5fde85dbbcebaef255facdf0e8c672.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
../_images/255adc277956e1a2797a1e1e48c2592a2f5fde85dbbcebaef255facdf0e8c672.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
../_images/255adc277956e1a2797a1e1e48c2592a2f5fde85dbbcebaef255facdf0e8c672.png
# See how the cells get the name from the parameters that you pass them

c = mzi_with_bend()
print(c)

print("second time you get this cell from the cache")
c = mzi_with_bend()
print(c)

print("If you call the cell with different parameters, the cell gets a different name")
c = mzi_with_bend(radius=20)
print(c)
mzi_with_bend: uid d9ba91dd, ports [], references ['mzi_1', 'bend_euler_1'], 0 polygons
second time you get this cell from the cache
mzi_with_bend: uid d9ba91dd, ports [], references ['mzi_1', 'bend_euler_1'], 0 polygons
If you call the cell with different parameters, the cell gets a different name
mzi_with_bend_radius20: uid f3f9727d, ports [], references ['mzi_1', 'bend_euler_1'], 0 polygons

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.info["area"] = width * length
    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

Settings vs Info#

Together with the GDS file that you send to the foundry you can also store the settings for each Component.

  • Component.settings are the input settings for each Cell to Generate a Component. For example, wg.settings.length will return you the input length setting that you used to create that waveguide.

  • Component.info are the derived properties that will be computed inside the Cell function. For example wg.info.area will return the computed area of that waveguide.

c.info
Info(area=10)
c.info.area
10
c.settings
CellSettings(length=10, width=1, layer=[1, 0])
c.settings.length
10

Components also have pretty print for settings c.pprint() and ports c.pprint_ports()

c.pprint()
{
'settings': {'length': 10, 'width': 1, 'layer': [1, 0]},
'function': 'wg',
'module': '__main__',
'name': 'wg',
'info': {'area': 10}
}
# you always add any relevant information `info` to the cell
c.info["polarization"] = "te"
c.info["wavelength"] = 1.55
c.info

Info(area=10, polarization='te', wavelength=1.55)

Cache#

To avoid that 2 exact cells are not references of the same cell the cell decorator has a cache where if a component has already been built it will return the component from the cache

@gf.cell
def straight(length=10, width=1):
    c = gf.Component()
    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=(1, 0))
    print(f"BUILDING {length}um long straight waveguide")
    return c

If you run the cell below multiple times it will print a message because we are deleting the CACHE every single time and every time the cell will have a different Unique Identifier (UID).

wg1 = straight()
gf.remove_from_cache(wg1)
print(wg1.uid)
BUILDING 10um long straight waveguide
2a94394e

If you run the cell below multiple times it will NOT print a message because we are hitting CACHE every single time and every time the cell will have the SAME Unique Identifier (UID) because it’s the same cell.

wg2 = straight(length=11)
# cell returns the same straight as before without having to run the function
print(wg2.uid)  # notice that they have the same uuid (unique identifier)
BUILDING 11um long straight waveguide
c1a23d1f
# Lets see which Components are in the cache
print_cache()
extrude_e296be3f
bend_euler
taper_5a9886c9
mmi1x2
extrude_c3942043
straight_length0p1
extrude_8db7eb37
straight_length7p0
extrude_deb50294
straight_length2p0
bend_euler_a459ac5d
taper_2dc0e2f5
extrude_01f29152
straight_b69bcf2d
straight_29c75779
straight_edf1f094
mzi
bend_euler_radius10p0
bend_euler_radius10
mzi_with_bend_radius10
mzi_with_bend
extrude_87499e0f
bend_euler_radius20
mzi_with_bend_radius20
straight_length11
wg3 = straight(length=12)
wg4 = straight(length=13)
BUILDING 12um long straight waveguide
BUILDING 13um long straight waveguide
print_cache()
extrude_e296be3f
bend_euler
taper_5a9886c9
mmi1x2
extrude_c3942043
straight_length0p1
extrude_8db7eb37
straight_length7p0
extrude_deb50294
straight_length2p0
bend_euler_a459ac5d
taper_2dc0e2f5
extrude_01f29152
straight_b69bcf2d
straight_29c75779
straight_edf1f094
mzi
bend_euler_radius10p0
bend_euler_radius10
mzi_with_bend_radius10
mzi_with_bend
extrude_87499e0f
bend_euler_radius20
mzi_with_bend_radius20
straight_length11
straight_length12
straight_length13

Create cells without cell decorator#

The cell decorator names cells deterministically and uniquely based on the name of the functions and its parameters.

It also uses a caching mechanisms that improves performance and guards against duplicated names.

The most common mistake new gdsfactory users make is to create cells without the cell decorator.

Avoid naming cells manually: Use cell decorator#

Naming cells manually is susceptible to name collisions

in GDS you can’t have two cells with the same name.

For example: this code will raise a duplicated cell name ValueError

import gdsfactory as gf

c1 = gf.Component("wg")
c1 << gf.components.straight(length=5)


c2 = gf.Component("wg")
c2 << gf.components.straight(length=50)


c3 = gf.Component("waveguides")
wg1 = c3 << c1
wg2 = c3 << c2
wg2.movey(10)
c3

Solution: Use the gf.cell decorator for automatic naming your components.

import gdsfactory as gf


@gf.cell
def wg(length: float = 3):
    return gf.components.straight(length=length)


print(wg(length=5))
print(wg(length=50))
wg_length5: uid a17b1bb5, ports ['o1', 'o2'], references [], 1 polygons
wg_length50: uid 157148b0, ports ['o1', 'o2'], references [], 1 polygons

Avoid Unnamed cells. Use cell decorator#

In the case of not wrapping the function with cell you will get unique names thanks to the unique identifier uuid.

This name will be different and non-deterministic for different invocations of the script.

However it will be hard for you to know where that cell came from.

c1 = gf.Component()
c2 = gf.Component()

print(c1.name)
print(c2.name)
Unnamed_acdabe01
Unnamed_71a97b38

Notice how gdsfactory raises a Warning when you save this Unnamed Components

c1.write_gds()
2024-04-25 20:20:09.904 | WARNING  | __main__:<module>:1 - UserWarning: Unnamed cells, 1 in 'Unnamed_acdabe01'
2024-04-25 20:20:09.905 | INFO     | gdsfactory.component:_write_library:2003 - Wrote to '/tmp/gdsfactory/Unnamed_acdabe01.gds'

PosixPath('/tmp/gdsfactory/Unnamed_acdabe01.gds')

Avoid Intermediate Unnamed cells. Use cell decorator#

While creating a cell, you should not create intermediate cells, because they won’t be Cached and you can end up with duplicated cell names or name conflicts, where one of the cells that has the same name as the other will be replaced.

@gf.cell
def die_bad():
    """c1 is an intermediate Unnamed cell"""
    c1 = gf.Component()
    _ = c1 << gf.components.straight(length=10)
    return gf.components.die_bbox(c1, street_width=10)


c = die_bad()
print(c.references)
c.plot()
[ComponentReference (parent Component "Unnamed_feedc5bd", ports [], origin (-5.0, 0.0), rotation 0.0, x_reflection False)]
2024-04-25 20:20:09.918 | WARNING  | gdsfactory.component:plot_klayout:1645 - UserWarning: Unnamed cells, 1 in 'die_bad'

../_images/c81e8e5499929a3516098cacff3cfa0296a7006adc344795ab0f9eedc29f8b68.png

Solution1 Don’t use intermediate cells

@gf.cell
def die_good():
    c = gf.Component()
    _ = c << gf.components.straight(length=10)
    _ = c << gf.components.die_bbox_frame(c.bbox, street_width=10)
    return c


c = die_good()
print(c.references)
c.plot()
[ComponentReference (parent Component "straight_length10", ports ['o1', 'o2'], origin (0.0, 0.0), rotation 0.0, x_reflection False), ComponentReference (parent Component "die_bbox_frame_de250f4c", ports [], origin (0.0, 0.0), rotation 0.0, x_reflection False)]

../_images/c609d1f55cef34ef3fdbae739aedaf6a9f7f34f373814d397e7fe4998bd8cd04.png

Solution2 You can flatten the cell, but you will lose the memory savings from cell references. Solution1 is more elegant.

@gf.cell
def die_flat():
    """c will be an intermediate unnamed cell"""
    c = gf.Component()
    _ = c << gf.components.straight(length=10)
    c2 = gf.components.die_bbox(c, street_width=10)
    c2 = c2.flatten()
    return c2


c = die_flat()
print(c.references)
c.plot()
[]

../_images/c81e8e5499929a3516098cacff3cfa0296a7006adc344795ab0f9eedc29f8b68.png
import gdsfactory as gf


@gf.cell
def dangerous_intermediate_cells(width=0.5):
    """Example that will show the dangers of using intermediate cells."""
    c = gf.Component("safe")

    c2 = gf.Component(
        "dangerous"
    )  # This should be forbidden as it will create duplicated cells
    _ = c2 << gf.components.hline(width=width)
    _ = c << c2

    return c


@gf.cell
def using_dangerous_intermediate_cells():
    """Example on how things can go wrong.

    Here we try to create to lines with different widths
    they end up with two duplicated cells and a name collision on the intermediate cell
    """
    c = gf.Component()
    _ = c << dangerous_intermediate_cells(width=0.5)
    r3 = c << dangerous_intermediate_cells(width=2)
    r3.movey(5)
    return c


c = using_dangerous_intermediate_cells()
c.plot_klayout()

../_images/8b71a11ba9e18924f682903a8a6bb11c12318d4937ffcbf956ed0fc5021ebbdd.png

../_images/8b71a11ba9e18924f682903a8a6bb11c12318d4937ffcbf956ed0fc5021ebbdd.png
for component in c.get_dependencies(recursive=True):
    if not component._locked:
        print(
            f"Component {component.name!r} was NOT properly locked. "
            "You need to write it into a function that has the @cell decorator."
        )
Component 'dangerous$1' was NOT properly locked. You need to write it into a function that has the @cell decorator.
Component 'dangerous' was NOT properly locked. You need to write it into a function that has the @cell decorator.