Cell#

Problem:

In GDS format

  • each component must have a unique name. Ideally the name is also consistent from different run times, 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. Ideally they will be references to the same component. See References tutorial. That way we only have to store that component in memory once and all the references are just pointers to that component.

Solution: The decorator @gf.cell for Parametric cell functions:

  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.

Also, thanks to the @cell decorator, GDS cells in gdsfactory include an metadata dictionary where you can access all component settings:

  • changed settings used to create the component

  • default settings in function signature

  • full full settings

  • name

  • function_name

  • module

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

Make sure that your components get good names by adding the @cell decorator to that each function that returns a Component.

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

import gdsfactory as gf

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

it’s equivalent to

Lets see how it works.

[1]:
import gdsfactory as gf


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
2022-06-28 17:00:08.376 | INFO     | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.11.4
this cell 'Unnamed_3445acc0' does NOT get automatic name
/home/runner/work/gdsfactory/gdsfactory/gdsfactory/component.py:1054: UserWarning: Component 'Unnamed_3445acc0' contains 1 Unnamed cells
  warnings.warn(
../_images/notebooks_03_cells_autoname_and_cache_1_3.png
[1]:
Unnamed_3445acc0: uid 0, ports [], aliases [], 0 polygons, 2 references
[2]:
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
this cell 'mzi_with_bend_radius10' gets automatic name thanks to the `cell` decorator
../_images/notebooks_03_cells_autoname_and_cache_2_1.png
[2]:
mzi_with_bend_radius10: uid 22, ports [], aliases [], 0 polygons, 2 references
[3]:
@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
this cell 'mzi_with_bend_radius10' gets automatic name thanks to the `cell` decorator
../_images/notebooks_03_cells_autoname_and_cache_3_1.png
[3]:
mzi_with_bend_radius10: uid 22, ports [], aliases [], 0 polygons, 2 references
[4]:
import gdsfactory as gf

# gf.CONF.plotter = 'holoviews'


@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", midpoint=[0, width / 2], width=width, orientation=180, layer=layer
    )
    c.add_port(
        name="o2", midpoint=[length, width / 2], width=width, orientation=0, layer=layer
    )
    return c

See how the cells get the name from the parameters that you pass them

[5]:
c = wg()
print(c)

# The second time you will get this cell from the cache
c = wg()
print(c)

# If you call the cell with different parameters, the cell gets a different name
c = wg(width=0.5)
print(c)
BUILDING waveguide
wg: uid 23, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references
wg: uid 23, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references
BUILDING waveguide
wg_width0p5: uid 24, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references

Sometimes when you are changing the inside code of the function, you need to use cache=False to ignore the cache.

[6]:
c = wg(cache=False)
BUILDING waveguide
[7]:
c.metadata.changed
[7]:
{}
[8]:
c.metadata.default
[8]:
{'length': 10, 'width': 1, 'layer': [1, 0]}
[9]:
c.metadata.full
[9]:
{'length': 10, 'width': 1, 'layer': [1, 0]}
[10]:
c.pprint()
name: wg
settings:
  changed: {}
  child: null
  default:
    layer:
    - 1
    - 0
    length: 10
    width: 1
  full:
    layer:
    - 1
    - 0
    length: 10
    width: 1
  function_name: wg
  info: {}
  info_version: 2
  module: __main__
  name: wg
version: 0.0.1

thanks to gf.cell you can also add any metadata info relevant to the cell

[11]:
c = wg(length=3, info=dict(polarization="te", wavelength=1.55))
BUILDING waveguide
[12]:
c.pprint()
name: wg_length3
settings:
  changed:
    length: 3
  child: null
  default:
    layer:
    - 1
    - 0
    length: 10
    width: 1
  full:
    layer:
    - 1
    - 0
    length: 3
    width: 1
  function_name: wg
  info:
    polarization: te
    wavelength: 1.55
  info_version: 2
  module: __main__
  name: wg_length3
version: 0.0.1

[13]:
print(c.metadata.info.wavelength)
1.55

Metadata#

Together with the GDS file that you send to the foundry you can also store metadata in YAML for each cell containing all the settings that we used to build the GDS.

the metadata will consists of all the parameters that were passed to the component function as well as derived properties

  • settings: includes all component metadata:

    • changed: changed settings.

    • child: child settings.

    • default: includes the default cell function settings.

    • full: full settings.

    • function_name: from the cell function.

    • info: meatada in Component.info dict.

    • module: python module where you can find the cell function.

    • name: for the component

  • ports: port name, width, orientation

[14]:
dir(c.metadata)
[14]:
['changed',
 'child',
 'default',
 'full',
 'function_name',
 'info',
 'info_version',
 'module',
 'name']

How can you have add two different references to a cell with the same parameters?

[15]:
import gdsfactory as gf

c = gf.Component("problem")
R1 = gf.components.rectangle(
    size=(4, 2), layer=(2, 0)
)  # Creates a rectangle (same Unique ID uid)
R2 = gf.components.rectangle(size=(4, 2), layer=(3, 0))
# Try Create a new rectangle that we want to change (but has the same name so we will get R1 from the cache)

r1r = c << R1  # Add the first rectangle to c
r2r = c << R2  # Add the second rectangle to c
r2r.move((4, 2))
c
../_images/notebooks_03_cells_autoname_and_cache_20_0.png
[15]:
problem: uid 27, ports [], aliases [], 0 polygons, 2 references
[16]:
print(R1 == R2)
print(R1)
print(R2)
False
rectangle_layer2__0_size4__2: uid 28, ports ['e1', 'e2', 'e3', 'e4'], aliases [], 0 polygons, 1 references
rectangle_layer3__0_size4__2: uid 30, ports ['e1', 'e2', 'e3', 'e4'], aliases [], 0 polygons, 1 references
[17]:
# lets do it cleaner with references
import gdsfactory as gf

c = gf.Component("solution")
R = gf.components.rectangle(size=(4, 2), layer=(2, 0))

r1 = c << R  # Add the first rectangle reference to c
r2 = c << R  # Add the second rectangle reference to c

r2.rotate(45)
c
../_images/notebooks_03_cells_autoname_and_cache_22_0.png
[17]:
solution: uid 32, ports [], aliases [], 0 polygons, 2 references
[18]:
import gdsfactory as gf

c = gf.components.straight()
c.show(show_ports=True)
c
../_images/notebooks_03_cells_autoname_and_cache_23_0.png
[18]:
straight: uid 33, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references

We can even show ports of all references with component.show(show_subports=True)

[19]:
c = gf.components.mzi_phase_shifter(length_x=50)
c
../_images/notebooks_03_cells_autoname_and_cache_25_0.png
[19]:
mzi_26978741: uid 36, ports ['o1', 'o2', 'e1', 'e2'], aliases [], 0 polygons, 20 references

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

[20]:
@gf.cell
def wg(length=10, width=1):
    c = gf.Component()
    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=(1, 0))
    print("BUILDING waveguide")
    return c
[21]:
gf.clear_cache()

wg1 = wg()  # cell builds a straight
print(wg1)
BUILDING waveguide
wg: uid 54, ports [], aliases [], 1 polygons, 0 references
[22]:
wg2 = wg()
# cell returns the same straight as before without having to run the function
print(wg2)  # notice that they have the same uuid (unique identifier)
wg: uid 54, ports [], aliases [], 1 polygons, 0 references
[23]:
wg2
../_images/notebooks_03_cells_autoname_and_cache_30_0.png
[23]:
wg: uid 54, ports [], aliases [], 1 polygons, 0 references
[24]:
from gdsfactory.cell import print_cache

Lets say that you change the code of the straight function in a jupyter notebook like this one. (I mostly use Vim/VsCode/Pycharm for creating new cells in python)

[25]:
print_cache()
wg
[26]:
wg3 = wg()
wg4 = wg(length=11)
BUILDING waveguide
[27]:
print_cache()
wg
wg_length11
[28]:
gf.clear_cache()

To enable nice notebook tutorials, every time we show a cell in Matplotlib or Klayout, you can clear the cache,

in case you want to develop cells in jupyter notebooks or an IPython kernel

[29]:
print_cache()  # cache is now empty

Validate argument types#

By default, also @cell validates arguments based on their type annotations. To make sure you pass the correct arguments to the cell function it runs a validator that checks the type annotations for the function.

For example this will be correct

import gdsfactory as gf

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


component = straigth_waveguide(length=3)

While this will raise an error, because you are passing a length that is a string, so it cannot convert it to a float

component = straigth_waveguide(length='long')
ValidationError: 1 validation error for StraigthWaveguide
length
  value is not a valid float (type=type_error.float)

by default @cell validates all arguments using pydantic

[30]:
@gf.cell
def straigth_waveguide(length: float):
    print(type(length))
    return gf.components.straight(length=length)


# It will also convert an `int` to a `float`
c = straigth_waveguide(length=3)
<class 'float'>