Components with hierarchy

Components with hierarchy#

You can define component parametric cells (waveguides, bends, couplers) as functions with basic input parameters (width, length, radius …) and use them as arguments for composing more complex functions.

from functools import partial

import toolz
import gdsfactory as gf
from gdsfactory.typings import ComponentSpec, CrossSectionSpec

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-04-25 13:59:44.627 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_with_too_many_input_parameters.lyp'.
../_images/94ae4ac04554e608a9cd8382f683438fa2ba585dec37f8151fb639a70ce1ff14.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-04-25 13:59:44.818 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight.lyp'.
../_images/75a71cd023a47404e1a7c624f1d10a46ddd7a48d3ab2b6df4e01ff0ddd046da8.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-04-25 13:59:45.030 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_bendbend_circular.lyp'.
../_images/c40cfdf6f917184c26db61fe815881cd06b32593da10900d5d350f307a74e728.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-04-25 13:59:45.227 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_113977eb.lyp'.
../_images/031b9d68fee469ecd24a53ba434fdc73494f01b44940acd18cd3ebfbe41fb4ca.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-04-25 13:59:45.528 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_65155870.lyp'.
../_images/dc4d136aaa7adbc9f714036bcaae9aecf47e24dc778907555f1a95f47f99af4e.png
bend20 = partial(gf.components.bend_circular, radius=20)
b = bend20()
b.plot()
2024-04-25 13:59:45.729 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_circular_radius20.lyp'.
../_images/03bd0315ce342f2039e0aa3b2636868f573dffec213d877ffd96744f456d8488.png
type(bend20)
functools.partial
bend20.func.__name__
'bend_circular'
bend20.keywords
{'radius': 20}
b = bend_with_straight(bend=bend20)
print(b.info.length)
b.plot()
41.416
2024-04-25 13:59:45.953 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_with_straight_4f9bb335.lyp'.
../_images/031b9d68fee469ecd24a53ba434fdc73494f01b44940acd18cd3ebfbe41fb4ca.png
# You can still modify the bend to have any bend radius
b3 = bend20(radius=10)
b3.plot()
2024-04-25 13:59:46.146 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/bend_circular_radius10.lyp'.
../_images/a306c1a5cac3b5875802ed1af116ed7df938d516130a92556dfa421a63f1abb4.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-04-25 13:59:46.314 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/straight.lyp'.
../_images/e59fc4d32caa7fb906786f453cdc009eb049dbb4725e4011820f727d6e826485.png
straight_wide = partial(gf.components.straight, width=3)
c3 = straight_wide()
c3.plot()
2024-04-25 13:59:46.515 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/straight_width3.lyp'.
../_images/98cea844389d8dbd719199186044e548351d58fa8fdc19616804e2b3f71484a6.png
c1 = gf.components.straight(width=3)
c1.plot()
2024-04-25 13:59:46.711 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/straight_width3.lyp'.
../_images/98cea844389d8dbd719199186044e548351d58fa8fdc19616804e2b3f71484a6.png
c2 = gf.add_tapers(c1)
c2.plot()
2024-04-25 13:59:46.907 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/straight_add_tapers_componentstraight_width3.lyp'.
../_images/1150ae9a384a3d819e93c4259bd742f893d32f849900da1d456f247adf18edff.png
c2.settings
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', width=3), function='straight', module='gdsfactory.components.straight'))
c2.settings.component
ComponentSpec(settings=CellSettings(length=10.0, npoints=2, cross_section='xs_sc', width=3), function='straight', module='gdsfactory.components.straight')
c2.info
Info(length=10.0, width=3.0, route_info_type='xs_2c161fda', route_info_length=10.0, route_info_weight=10.0, route_info_xs_2c161fda_length=10.0)
c3 = gf.routing.add_fiber_array(c2, with_loopback=False)
c3.plot()
2024-04-25 13:59:47.149 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.png

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-04-25 13:59:47.351 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.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-04-25 13:59:47.558 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.png

is equivalent to

c5 = add_fiber_array(add_tapers(c1))
c5.plot()
2024-04-25 13:59:47.895 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.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-04-25 13:59:48.091 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.png

or

c7 = toolz.pipe(c1, add_tapers, add_fiber_array)
c7.plot()
2024-04-25 13:59:48.298 | INFO     | gdsfactory.technology.layer_views:to_lyp:1018 - LayerViews written to '/tmp/gdsfactory/add_tapers_add_fiber_array_89d50adc.lyp'.
../_images/098d3554f0cdfd6ad8b3f8b4d9524adf5264d75f91e706eab5a744722fe29721.png