Best practices#

Use @cell decorator#

  • Do not name leave cells Unnamed.

Unnamed cells are going to get different names every time you run and is going to be hard to know where they come from.

import gdsfactory as gf

gf.CONF.display_type = "klayout"

c = gf.Component()
print(c.name)
Unnamed_5ff65bf2
  • Do not name cells manually. Manually defining names can create duplicated cells.

c1 = gf.Component("my_cell")
c2 = gf.Component("my_cell")
c1.add_ref(gf.components.straight(length=10))
c2.add_ref(gf.components.straight(length=11))

c3 = gf.Component("im_going_to_have_duplicated_cell_names")
_ = c3.add_ref(c1)
_ = c3.add_ref(c2)
c3.plot()
../_images/3767a7ce5302eabdd5f555ed45ea88d26ca59b2385ccd13c4ca5d39eed90d576.png

Solution: Use the cell decorator to name cells

@gf.cell
def my_pcell(length=10):
    c = gf.Component()
    ref = c1.add_ref(gf.components.straight(length=length))
    c.add_ports(ref.ports)
    return c


print(my_pcell(length=11).name)
print(my_pcell(length=12).name)
my_pcell_length11
my_pcell_length12

Keep cell functions simple#

As you make functions made of other functions one can start passing a lot of arguments to the function. This makes the code hard to write, read and maintain.

  • Avoid complicated functions with many parameters

@gf.cell
def mzi_with_bend_overly_complicated(
    mzi_delta_length: float = 10.0,
    mzi_length_y: float = 2.0,
    mzi_length_x: float | None = 0.1,
    bend_radius: float = 10,
    bend_cross_section="xs_sc",
):
    """Returns MZI interferometer with bend."""
    c = gf.Component()
    mzi1 = c.add_ref(
        gf.components.mzi(
            delta_length=mzi_delta_length,
            length_y=mzi_length_y,
            length_x=mzi_length_x,
        )
    )
    bend1 = c.add_ref(
        gf.components.bend_euler(radius=bend_radius, cross_section=bend_cross_section)
    )
    bend1.connect("o1", mzi1.ports["o2"])
    c.add_port("o1", port=mzi1.ports["o1"])
    c.add_port("o2", port=bend1.ports["o2"])
    return c


c = mzi_with_bend_overly_complicated(bend_radius=100)
c.plot()
../_images/cd1ee9583a96a0d2bbc1fd0822d35b47130807d16aaedf1382fd2ede7ce78a49.png

Solution:

  • leverage functools.partial to customize the default parameters of a function

from functools import partial


@gf.cell
def mzi_with_bend(mzi=gf.components.mzi, bend=gf.components.bend_euler):
    """Returns MZI interferometer with bend."""
    c = gf.Component()
    mzi1 = c.add_ref(mzi())
    bend1 = c.add_ref(bend())
    bend1.connect("o1", mzi1.ports["o2"])
    c.add_port("o1", port=mzi1.ports["o1"])
    c.add_port("o2", port=bend1.ports["o2"])
    return c


bend_big = partial(gf.components.bend_euler, radius=100)
c = mzi_with_bend(bend=bend_big)
c.plot()
../_images/cd1ee9583a96a0d2bbc1fd0822d35b47130807d16aaedf1382fd2ede7ce78a49.png

Use array of references#

  • Array of references are more memory efficient and faster to create

@gf.cell
def pad_array_slow(
    cols: int = 30,
    rows: int = 30,
    spacing: tuple[float, float] = (200, 200),
    pad=gf.components.pad,
):
    """Returns a grid of pads"""
    xspacing, yspacing = spacing
    c = gf.Component()
    for col in range(cols):
        for row in range(rows):
            c.add_ref(pad()).movex(col * xspacing).movey(row * yspacing)
    return c


c = pad_array_slow()
c.plot()
../_images/ec8e9e73c2d2b7a3d87d5b09eb6586173ecbd7fe6efbdcb426e45518e0572431.png
@gf.cell
def pad_array_fast(
    cols: int = 30,
    rows: int = 30,
    spacing: tuple[float, float] = (200, 200),
    pad=gf.components.pad,
):
    """Returns a grid of pads"""
    c = gf.Component()
    c.add_array(pad(), columns=cols, rows=rows, spacing=spacing)
    return c


c = pad_array_fast()
c.plot()
../_images/5796af3ab8bd254ef9ca5574edd3bcfc432e211adbd7517ab1d37b0fa31a3d75.png