Routing with different CrossSections

Routing with different CrossSections#

When working in a technologies with multiple waveguide cross-sections, it is useful to differentiate intent layers for the different waveguide types and assign default transitions between those layers. In this way, you can easily autotransition between the different cross-section types.

Setting up your PDK#

Let’s first set up a sample PDK with the following key features:

  1. Rib and strip cross-sections with differentiated intent layers.

  2. Default transitions for each individual cross-section type (width tapers), and also a rib-to-strip transition component to switch between them.

  3. Preferred routing cross-sections defined for the all-angle router.

from functools import partial

import gdsfactory as gf
from gdsfactory.cross_section import xs_rc, strip, rib
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.read import cell_from_yaml_template
from gdsfactory.routing import all_angle
from gdsfactory.typings import CrossSectionSpec

gf.config.enable_offgrid_ports()
gf.CONF.display_type = "klayout"
generic_pdk = get_generic_pdk()

# define our rib and strip waveguide intent layers
RIB_INTENT_LAYER = (2000, 11)
STRIP_INTENT_LAYER = (2001, 11)

generic_pdk.layers.update(
    RIB_INTENT_LAYER=RIB_INTENT_LAYER, STRIP_INTENT_LAYER=STRIP_INTENT_LAYER
)

# create strip and rib cross-sections, with differentiated intent layers
strip_with_intent = partial(
    strip,
    cladding_layers=[
        "STRIP_INTENT_LAYER"
    ],  # keeping WG layer is nice for compatibility
    cladding_offsets=[0],
    gap=2,
)

rib_with_intent = partial(
    rib,
    cladding_layers=["RIB_INTENT_LAYER"],  # keeping WG layer is nice for compatibility
    cladding_offsets=[0],
    gap=5,
)


# create strip->rib transition component
@gf.cell
def strip_to_rib(width1: float = 0.5, width2: float = 0.5) -> gf.Component:
    c = gf.Component()
    taper = c << gf.c.taper_strip_to_ridge(width1=width1, width2=width2)
    c.add_port(
        "o1",
        port=taper.ports["o1"],
        layer=(1, 0),
        cross_section=strip_with_intent(width=width1),
        width=width1,
    )
    c.add_port(
        "o2",
        port=taper.ports["o2"],
        layer=(1, 0),
        cross_section=rib_with_intent(width=width2),
        width=width2,
    )
    c.absorb(taper)
    c.info.update(taper.info)
    c.add_route_info(cross_section="r2s", length=c.info["length"])
    return c


# also define a rib->strip component for transitioning the other way
@gf.cell
def rib_to_strip(width1: float = 0.5, width2: float = 0.5) -> gf.Component:
    c = gf.Component()
    taper = c << strip_to_rib(width1=width2, width2=width1)
    c.add_port("o1", port=taper.ports["o2"])
    c.add_port("o2", port=taper.ports["o1"])
    c.info.update(taper.info)
    return c


@gf.cell
def taper_single_cross_section(
    cross_section: CrossSectionSpec = "xs_sc", width1: float = 0.5, width2: float = 1.0
) -> gf.Component:
    """Single-layer taper components."""
    cs1 = gf.get_cross_section(cross_section, width=width1)
    cs2 = gf.get_cross_section(cross_section, width=width2)
    length = abs(width1 - width2) * 10
    c = gf.Component()
    ref = c << gf.components.taper_cross_section_linear(cs1, cs2, length=length)
    c.add_ports(ref.ports)
    c.info["length"] = length
    return c


taper_strip = partial(taper_single_cross_section, cross_section="xs_sc")
taper_rib = partial(taper_single_cross_section, cross_section="xs_rc")

# make a new PDK with our required layers, cross-sections, and default transitions
multi_wg_pdk = gf.Pdk(
    base_pdk=generic_pdk,
    name="multi_wg_demo",
    layers={
        "RIB_INTENT": RIB_INTENT_LAYER,
        "STRIP_INTENT": STRIP_INTENT_LAYER,
    },
    cross_sections={
        "xs_rc": rib_with_intent,
        "xs_sc": strip_with_intent,
    },
    layer_transitions={
        RIB_INTENT_LAYER: taper_rib,
        STRIP_INTENT_LAYER: taper_strip,
        (RIB_INTENT_LAYER, STRIP_INTENT_LAYER): rib_to_strip,
        (STRIP_INTENT_LAYER, RIB_INTENT_LAYER): strip_to_rib,
    },
    layer_views=generic_pdk.layer_views,
)

# activate our new PDK
multi_wg_pdk.activate()

# set to prefer rib routing when there is enough space
all_angle.LOW_LOSS_CROSS_SECTIONS.insert(0, "xs_rc")
2024-10-31 18:40:37.027 | WARNING  | gdsfactory.pdk:activate:289 - UserWarning: base_pdk is deprecated. Use base_pdks instead

Let’s quickly demonstrate our new cross-sections and transition component.

# demonstrate rib and strip waveguides in our new PDK
strip_width = 1
rib_width = 0.7

c = gf.Component()
strip_wg = c << gf.c.straight(cross_section=strip_with_intent(width=strip_width))
rib_wg = c << gf.c.straight(cross_section=rib_with_intent(width=rib_width))
taper = c << strip_to_rib(width1=strip_width, width2=rib_width)
taper.connect("o1", strip_wg.ports["o2"])
rib_wg.connect("o1", taper.ports["o2"])
c.show()
c.plot()
2024-10-31 18:40:37.035 | WARNING  | __main__:<module>:6 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.036 | WARNING  | __main__:<module>:6 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.041 | WARNING  | __main__:<module>:7 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.042 | WARNING  | __main__:<module>:7 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.049 | WARNING  | gdsfactory.pdk:get_cross_section:519 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.050 | WARNING  | gdsfactory.pdk:get_cross_section:519 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.052 | WARNING  | gdsfactory.pdk:get_cross_section:511 - UserWarning: CrossSection.copy() only modifies the attributes of the first section.
2024-10-31 18:40:37.056 | WARNING  | __main__:strip_to_rib:49 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.057 | WARNING  | __main__:strip_to_rib:49 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.058 | WARNING  | __main__:strip_to_rib:56 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.059 | WARNING  | __main__:strip_to_rib:56 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.062 | WARNING  | gdsfactory.show:show:47 - UserWarning: Unnamed cells, 1 in 'Unnamed_364b5fe1'
2024-10-31 18:40:37.063 | WARNING  | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
2024-10-31 18:40:37.395 | WARNING  | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_364b5fe1'
../_images/84f2d196a0d2e00c1b60b507eaef7f16e07350bdbc897e74535b3d72051a3af1.png

Autotransitioning with the All-Angle Router#

Now that our PDK and settings are all configured, we can see how the all-angle router will auto-transition for us between different cross sections.

Because we are using the low-loss connector by default, and the highest priority cross section is rib, we will see rib routing anywhere there is enough space to transition.

from pathlib import Path

from IPython.display import Code, display

from gdsfactory.read import cell_from_yaml_template


def show_yaml_pic(filepath):
    gf.clear_cache()
    cell_name = filepath.stem
    return display(
        Code(filename=filepath, language="yaml+jinja"),
        cell_from_yaml_template(filepath, name=cell_name)(),
    )


# load a yaml PIC, and see how it looks with our new technology
sample_dir = Path("yaml_pics")

basic_sample_fn = sample_dir / "aar_indirect.pic.yml"
show_yaml_pic(basic_sample_fn)
2024-10-31 18:40:37.563 | WARNING  | gdsfactory.read.from_yaml:from_yaml:692 - UserWarning: prefix is deprecated and will be removed soon. _from_yaml
2024-10-31 18:40:37.566 | WARNING  | gdsfactory.pdk:get_cross_section:519 - UserWarning: gap is deprecated. Pass this parameter to the routing function instead.
2024-10-31 18:40:37.567 | WARNING  | gdsfactory.pdk:get_cross_section:519 - UserWarning: gap is deprecated.
2024-10-31 18:40:37.570 | WARNING  | gdsfactory.pdk:get_cross_section:511 - UserWarning: CrossSection.copy() only modifies the attributes of the first section.
instances:
    mmi_long:
      component: mmi1x2
      settings:
        width_mmi: 4.5
        length_mmi: 10
    mmi_short:
      component: mmi1x2
      settings:
        width_mmi: 4.5
        length_mmi: 5

placements:
    mmi_short:
        rotation: 180
    mmi_long:
        port: o1
        rotation: -10  # port vectors no longer intersect
        x: mmi_short,o1
        y: mmi_short,o1
        dx: 50
        dy: 20

routes:
    optical:
        routing_strategy: get_bundle_all_angle
        links:
            mmi_short,o1: mmi_long,o1

ports:
    o1: mmi_short,o2
    o2: mmi_short,o3
2024-10-31 18:40:37.813 | WARNING  | gdsfactory.component:_write_library:1934 - UserWarning: Component aar_indirect.pic has invalid transformations. Try component.flatten_offgrid_references() first.
2024-10-31 18:40:37.815 | WARNING  | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
aar_indirect.pic: uid 59d8273a, ports ['o1', 'o2'], references ['mmi_long', 'mmi_short', 'bend_euler_1', 'bend_euler_2', 'straight_1'], 0 polygons
../_images/7def27c0e187e31b874368cec700addad48e3e88b27368e227bf3b1990e94aeb.png
c = gf.read.from_yaml(yaml_str=basic_sample_fn.read_text())
c.plot()
2024-10-31 18:40:37.961 | WARNING  | gdsfactory.component:_write_library:1934 - UserWarning: Component Unnamed_c0708b5d has invalid transformations. Try component.flatten_offgrid_references() first.
2024-10-31 18:40:37.963 | WARNING  | gdsfactory.component:plot_klayout:1646 - UserWarning: Unnamed cells, 1 in 'Unnamed_c0708b5d'
../_images/7def27c0e187e31b874368cec700addad48e3e88b27368e227bf3b1990e94aeb.png

You can see that since gap is defined in our cross-sections, the bundle router also intelligently picks the appropriate bundle spacing for the cross section used.

Notice how the strip waveguide bundles are much more tightly packed than the rib waveguide bundles in the example below.

basic_sample_fn2 = sample_dir / "aar_bundles03.pic.yml"
show_yaml_pic(basic_sample_fn2)
2024-10-31 18:40:38.197 | WARNING  | gdsfactory.routing.auto_taper:taper_to_cross_section:63 - UserWarning: No registered width taper for layer (1, 0). Skipping.
default_settings:
  n_bundle:
    value: 3
    description: "The number of routes in the bundle"

instances:
{% for i in range(n_bundle) %}
    wg_start_{{ i }}:
      component: straight
    wg_end_{{ i }}:
      component: straight
{% endfor %}

placements:
{% for i in range(n_bundle) %}
    wg_end_{{ i }}:
        rotation: 180
        y: {{ i * 4 }}
    wg_start_{{ i }}:
        port: o1
        rotation: {{ 20 - i * 5 }}  # ports need not be aligned
        x: wg_end_0,o1
        y: wg_end_0,o1
        dx: 120
        dy: {{ 80 + i * 4 }}
{% endfor %}

routes:
    optical:
        routing_strategy: get_bundle_all_angle
        settings:
          steps:
            - ds: 65
              exit_angle: 90
            - ds: 110
              exit_angle: 0
            - ds: 81.5
              exit_angle: -65
            - ds: 52
              exit_angle: -158
              cross_section: xs_sc  # explicitly using xs_sc routing
            - ds: 67.5
              exit_angle: 100
              cross_section:  # explicitly using xs_sc routing with custom width
                cross_section: xs_sc
                settings:
                  width: 1.2
        links:
{% for i in [2, 1, 0] %}
            wg_end_{{ i }},o1: wg_start_{{ i }},o1
{% endfor %}

ports:
    o1: wg_start_0,o2
    o2: wg_end_0,o2
    o3: wg_start_1,o2
    o4: wg_end_1,o2
    o5: wg_start_2,o2
    o6: wg_end_2,o2
2024-10-31 18:40:38.291 | WARNING  | gdsfactory.component:_write_library:1934 - UserWarning: Component aar_bundles03.pic has invalid transformations. Try component.flatten_offgrid_references() first.
aar_bundles03.pic: uid fe2bafe2, ports ['o1', 'o2', 'o3', 'o4', 'o5', 'o6'], references ['wg_start_0', 'wg_end_0', 'wg_start_1', 'wg_end_1', 'wg_start_2', 'wg_end_2', 'straight_1', 'bend_euler_1', 'straight_2', 'bend_euler_2', 'straight_3', 'bend_euler_3', 'straight_4', 'bend_euler_4', 'straight_5', 'bend_euler_5', 'straight_6', 'bend_euler_6', 'straight_7', 'straight_8', 'bend_euler_7', 'straight_9', 'bend_euler_8', 'straight_10', 'bend_euler_9', 'straight_11', 'bend_euler_10', 'straight_12', 'bend_euler_11', 'straight_13', 'bend_euler_12', 'straight_14', 'straight_15', 'bend_euler_13', 'straight_16', 'bend_euler_14', 'straight_17', 'bend_euler_15', 'straight_18', 'bend_euler_16', 'straight_19', 'bend_euler_17', 'straight_20', 'bend_euler_18', 'straight_21'], 0 polygons
../_images/2a1fe38a6c9dbf774e47c63849d5af6b19f386ec23057a9a50319c4d05cacc00.png
f = cell_from_yaml_template(basic_sample_fn2, name="sample_transition")
c = f()
c.plot()
2024-10-31 18:40:38.439 | WARNING  | gdsfactory.component:_write_library:1934 - UserWarning: Component sample_transition has invalid transformations. Try component.flatten_offgrid_references() first.
../_images/2a1fe38a6c9dbf774e47c63849d5af6b19f386ec23057a9a50319c4d05cacc00.png