Source code for ubcpdk.cells.containers

"""This module contains cells that contain other cells."""

from functools import partial
from typing import Any

import gdsfactory as gf
from gdsfactory.component import Component, ComponentReference
from gdsfactory.typings import (
    AngleInDegrees,
    CellSpec,
    ComponentSpec,
    CrossSectionSpec,
    Float2,
    LayerSpec,
)

from ubcpdk.config import CONFIG
from ubcpdk.tech import LAYER

gc = "ebeam_gc_te1550"


def clean_name(name: str) -> str:
    return name.replace("_", ".")


def add_label_electrical(component: Component, text: str, port_name: str = "e2"):
    """Adds labels for electrical port.

    Returns same component so it needs to be used as a decorator.
    """
    if port_name not in component.ports:
        port_names = [port.name for port in component.ports]
        raise ValueError(f"No port {port_name!r} in {port_names}")

    component.add_label(
        text=text, position=component.ports[port_name].dcenter, layer=LAYER.TEXT
    )
    return component


def get_input_label_text(
    gc: ComponentReference,
    component_name: str | None = None,
    username: str = CONFIG.username,
) -> str:
    """Return label for port and a grating coupler.

    Args:
        gc: grating coupler reference.
        component_name: optional component name.
        username: for the label.
    """
    polarization = gc.info.get("polarization")
    wavelength = gc.info.get("wavelength")

    gc_cell_name = gc.name.lower()
    if polarization is None:
        if "te" in gc_cell_name:
            polarization = "te"
        else:
            polarization = "tm"

    if wavelength is None:
        if "1310" in gc_cell_name:
            wavelength = 1.310
        else:
            wavelength = 1.550

    assert polarization.upper() in [
        "TE",
        "TM",
    ], f"Not valid polarization {polarization.upper()!r} in [TE, TM]"
    assert (
        isinstance(wavelength, int | float) and 1.0 < wavelength < 2.0
    ), f"{wavelength} is Not valid 1000 < wavelength < 2000"

    name = component_name
    name = clean_name(name)
    # return f"opt_{polarization.upper()}_{int(wavelength * 1000.0)}_device_{username}-{name}-{gc_index}-{port.name}"
    return f"opt_in_{polarization.upper()}_{int(wavelength * 1000.0)}_device_{username}-{name}"


[docs] @gf.cell def add_fiber_array( component: ComponentSpec = "ring_single", component_name: str | None = None, gc_port_name: str = "o1", with_loopback: bool = False, fanout_length: float | None = 0, grating_coupler: ComponentSpec = gc, cross_section: CrossSectionSpec = "strip", straight: ComponentSpec = "straight", taper: ComponentSpec | None = None, mirror_grating_coupler: bool = True, gc_rotation: float = +90, **kwargs, ) -> Component: """Returns component with grating couplers and labels on each port. Routes all component ports south. Can add align_ports loopback reference structure on the edges. Args: component: to connect. component_name: for the label. gc_port_name: grating coupler input port name 'o1'. with_loopback: True, adds loopback structures. fanout_length: None # if None, automatic calculation of fanout length. grating_coupler: grating coupler instance, function or list of functions. cross_section: spec. straight: straight component. taper: taper component. kwargs: cross_section settings. """ c = gf.Component() component = gf.get_component(component) component_with_grating_coupler = gf.routing.add_fiber_array( straight=straight, bend="bend_euler", component=component, component_name=component_name, grating_coupler=grating_coupler, gc_port_name=gc_port_name, with_loopback=with_loopback, fanout_length=fanout_length, cross_section=cross_section, taper=taper, mirror_grating_coupler=mirror_grating_coupler, gc_rotation=gc_rotation, **kwargs, ) component_with_grating_coupler.name = component.name + "_with_gc" ref = c << component_with_grating_coupler ref.rotate(-90) c.add_ports(ref.ports) c.copy_child_info(component) component_name = component_name or component.name grating_coupler = gf.get_component(grating_coupler) label = get_input_label_text(gc=grating_coupler, component_name=component_name) c.add_label(position=c.ports["o1"].dcenter, text=label, layer=LAYER.TEXT) return c
# @gf.cell # def add_fiber_array_bottom( # component: ComponentSpec = "straight", # grating_coupler=gc, # gc_port_name: str = "o1", # component_name: str | None = None, # cross_section: CrossSectionSpec = "strip", # gc_rotation: float = +90, # radius_loopback: float = 10, # mirror_grating_coupler: bool = True, # **kwargs, # ) -> Component: # """Returns component with south routes and grating_couplers. # You can also use pads or other terminations instead of grating couplers. # Args: # component: component spec to connect to grating couplers. # grating_coupler: spec for route terminations. # gc_port_name: grating coupler input port name. # component_name: optional for the label. # cross_section: cross_section function. # gc_rotation: fiber coupler rotation in degrees. Defaults to -90. # radius_loopback: optional radius of the loopback bend. Defaults to the cross_section. # kwargs: additional arguments. # Keyword Args: # bend: bend spec. # straight: straight spec. # fanout_length: if None, automatic calculation of fanout length. # max_y0_optical: in um. # with_loopback: True, adds loopback structures. # with_loopback_inside: True, adds loopback structures inside the component. # straight_separation: from edge to edge. # list_port_labels: None, adds TM labels to port indices in this list. # connected_port_list_ids: names of ports only for type 0 optical routing. # nb_optical_ports_lines: number of grating coupler lines. # force_manhattan: False # excluded_ports: list of port names to exclude when adding gratings. # grating_indices: list of grating coupler indices. # routing_straight: function to route. # routing_method: route_single. # optical_routing_type: None: auto, 0: no extension, 1: standard, 2: check. # input_port_indexes: to connect. # pitch: in um. # radius: optional radius of the bend. Defaults to the cross_section. # route_backwards: route from component to grating coupler or vice-versa. # .. plot:: # :include-source: # import gdsfactory as gf # c = gf.components.crossing() # cc = gf.routing.add_fiber_array( # component=c, # optical_routing_type=2, # grating_coupler=gf.components.grating_coupler_elliptical_te, # with_loopback=False # ) # cc.plot() # """ # return gf.routing.add_fiber_array( # component=component, # grating_coupler=grating_coupler, # gc_port_name=gc_port_name, # gc_rotation=gc_rotation, # component_name=component_name, # cross_section=cross_section, # radius_loopback=radius_loopback, # mirror_grating_coupler=mirror_grating_coupler, # **kwargs, # ) # @gf.cell # def add_fiber_single( # component: ComponentSpec = "straight", # grating_coupler=gc, # gc_port_name: str = "o1", # component_name: str | None = None, # cross_section: CrossSectionSpec = "strip", # taper: ComponentSpec | None = None, # input_port_names: list[str] | tuple[str, ...] | None = None, # pitch: float = 70, # with_loopback: bool = True, # loopback_spacing: float = 100.0, # **kwargs, # ) -> Component: # """Returns component with south routes and grating_couplers. # You can also use pads or other terminations instead of grating couplers. # Args: # component: component spec to connect to grating couplers. # grating_coupler: spec for route terminations. # gc_port_name: grating coupler input port name. # component_name: optional for the label. # cross_section: cross_section function. # taper: taper spec. # input_port_names: list of input port names to connect to grating couplers. # pitch: spacing between fibers. # with_loopback: adds loopback structures. # loopback_spacing: spacing between loopback and test structure. # kwargs: additional arguments. # Keyword Args: # bend: bend spec. # straight: straight spec. # fanout_length: if None, automatic calculation of fanout length. # max_y0_optical: in um. # with_loopback: True, adds loopback structures. # straight_separation: from edge to edge. # list_port_labels: None, adds TM labels to port indices in this list. # connected_port_list_ids: names of ports only for type 0 optical routing. # nb_optical_ports_lines: number of grating coupler lines. # force_manhattan: False # excluded_ports: list of port names to exclude when adding gratings. # grating_indices: list of grating coupler indices. # routing_straight: function to route. # routing_method: route_single. # optical_routing_type: None: auto, 0: no extension, 1: standard, 2: check. # gc_rotation: fiber coupler rotation in degrees. Defaults to -90. # input_port_indexes: to connect. # .. plot:: # :include-source: # import gdsfactory as gf # c = gf.components.crossing() # cc = gf.routing.add_fiber_array( # component=c, # optical_routing_type=2, # grating_coupler=gf.components.grating_coupler_elliptical_te, # with_loopback=False # ) # cc.plot() # """ # return gf.routing.add_fiber_single( # component=component, # grating_coupler=grating_coupler, # gc_port_name=gc_port_name, # component_name=component_name, # cross_section=cross_section, # taper=taper, # input_port_names=input_port_names, # pitch=pitch, # with_loopback=with_loopback, # loopback_spacing=loopback_spacing, # **kwargs, # ) # @gf.cell # def add_pads_top( # component: ComponentSpec = "straight_metal", # port_names: Strs | None = None, # component_name: str | None = None, # cross_section: CrossSectionSpec = "metal_routing", # pad_port_name: str = "e1", # pad: ComponentSpec = "pad", # bend: ComponentSpec = "wire_corner", # straight_separation: float = 15.0, # pad_pitch: float = 100.0, # taper: ComponentSpec | None = None, # port_type: str = "electrical", # allow_width_mismatch: bool = True, # fanout_length: float | None = 80, # route_width: float | list[float] | None = 0, # **kwargs, # ) -> Component: # """Returns new component with ports connected top pads. # Args: # component: component spec to connect to. # port_names: list of port names to connect to pads. # component_name: optional for the label. # cross_section: cross_section function. # pad_port_name: pad port name. # pad: pad function. # bend: bend function. # straight_separation: from edge to edge. # pad_pitch: spacing between pads. # taper: taper function. # port_type: port type. # allow_width_mismatch: if True, allows width mismatch. # fanout_length: length of the fanout. # route_width: width of the route. # kwargs: additional arguments. # .. plot:: # :include-source: # import gdsfactory as gf # c = gf.c.nxn( # xsize=600, # ysize=200, # north=2, # south=3, # wg_width=10, # layer="M3", # port_type="electrical", # ) # cc = gf.routing.add_pads_top(component=c, port_names=("e1", "e4"), fanout_length=50) # cc.plot() # """ # return gf.routing.add_pads_top( # component=component, # port_names=port_names, # component_name=component_name, # cross_section=cross_section, # pad_port_name=pad_port_name, # pad=pad, # bend=bend, # straight_separation=straight_separation, # pad_pitch=pad_pitch, # taper=taper, # port_type=port_type, # allow_width_mismatch=allow_width_mismatch, # fanout_length=fanout_length, # route_width=route_width, # **kwargs, # )
[docs] @gf.cell def pack_doe( doe: ComponentSpec, settings: dict[str, tuple[Any, ...]], do_permutations: bool = False, function: CellSpec | None = None, **kwargs, ) -> Component: """Packs a component DOE (Design of Experiment) using pack. Args: doe: function to return Components. settings: component settings. do_permutations: for each setting. function: to apply (add padding, grating couplers). kwargs: for pack. Keyword Args: spacing: Minimum distance between adjacent shapes. aspect_ratio: (width, height) ratio of the rectangular bin. max_size: Limits the size into which the shapes will be packed. sort_by_area: Pre-sorts the shapes by area. density: Values closer to 1 pack tighter but require more computation. precision: Desired precision for rounding vertex coordinates. text: Optional function to add text labels. text_prefix: for labels. For example. 'A' for 'A1', 'A2'... text_offsets: relative to component size info anchor. Defaults to center. text_anchors: relative to component (ce cw nc ne nw sc se sw center cc). name_prefix: for each packed component (avoids the Unnamed cells warning). Note that the suffix contains a uuid so the name will not be deterministic. rotation: for each component in degrees. h_mirror: horizontal mirror in y axis (x, 1) (1, 0). This is the most common. v_mirror: vertical mirror using x axis (1, y) (0, y). """ return gf.components.pack_doe( doe=doe, settings=settings, do_permutations=do_permutations, function=function, **kwargs, )
[docs] @gf.cell def pack_doe_grid( doe: ComponentSpec, settings: dict[str, tuple[Any, ...]], do_permutations: bool = False, function: CellSpec | None = None, with_text: bool = False, **kwargs, ) -> Component: """Packs a component DOE (Design of Experiment) using grid. Args: doe: function to return Components. settings: component settings. do_permutations: for each setting. function: to apply to component (add padding, grating couplers). with_text: includes text label. kwargs: for grid. Keyword Args: spacing: between adjacent elements on the grid, can be a tuple for different distances in height and width. separation: If True, guarantees elements are separated with fixed spacing if False, elements are spaced evenly along a grid. shape: x, y shape of the grid (see np.reshape). If no shape and the list is 1D, if np.reshape were run with (1, -1). align_x: {'x', 'xmin', 'xmax'} for x (column) alignment along. align_y: {'y', 'ymin', 'ymax'} for y (row) alignment along. edge_x: {'x', 'xmin', 'xmax'} for x (column) (ignored if separation = True). edge_y: {'y', 'ymin', 'ymax'} for y (row) (ignored if separation = True). rotation: for each component in degrees. h_mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror. v_mirror: vertical mirror using x axis (1, y) (0, y). """ return gf.components.pack_doe_grid( doe=doe, settings=settings, do_permutations=do_permutations, function=function, with_text=with_text, **kwargs, )
[docs] @gf.cell def add_fiber_array_pads_rf( component: ComponentSpec = "ring_single_heater", username: str = CONFIG.username, orientation: float = 0, pad_yspacing: float = 50, component_name: str | None = None, **kwargs, ) -> Component: """Returns fiber array with label and electrical pads. Args: component: to add fiber array and pads. username: for the label. orientation: for adding pads. pad_yspacing: for adding pads. component_name: for the label. kwargs: for add_fiber_array. """ c0 = gf.get_component(component) component_name = component_name or c0.name component_name = clean_name(component_name) text = f"elec_{username}-{component_name}_G" c1 = add_pads_rf(component=c0, orientation=orientation, spacing=(0, pad_yspacing)) c1.name = text add_label_electrical(component=c1, text=text) return add_fiber_array(component=c1, component_name=component_name, **kwargs)
[docs] @gf.cell def pad_array( pad: ComponentSpec = "pad", columns: int = 6, rows: int = 1, column_pitch: float = 125.0, row_pitch: float = 125.0, port_orientation: AngleInDegrees = 270, size: Float2 | None = None, layer: LayerSpec | None = None, centered_ports: bool = False, auto_rename_ports: bool = False, ) -> gf.Component: """Returns 2D array of pads. Args: pad: pad element. columns: number of columns. rows: number of rows. column_pitch: x pitch. row_pitch: y pitch. port_orientation: port orientation in deg. None for low speed DC ports. size: pad size. layer: pad layer. centered_ports: True add ports to center. False add ports to the edge. auto_rename_ports: True to auto rename ports. """ return gf.c.pad_array( pad=pad, columns=columns, rows=rows, column_pitch=column_pitch, row_pitch=row_pitch, port_orientation=port_orientation, size=size, layer=layer, centered_ports=centered_ports, auto_rename_ports=auto_rename_ports, )
add_pads_rf = partial( gf.routing.add_electrical_pads_top, component="ring_single_heater", pad_array="pad_array", )
[docs] @gf.cell def add_pads( component: ComponentSpec = "ring_single_heater", username: str = CONFIG.username, label_port_name="l_e1", **kwargs, ) -> Component: """Returns fiber array with label and electrical pads. Args: component: to add fiber array and pads. username: for the label. kwargs: for add_fiber_array. """ c0 = gf.get_component(component).copy() text = f"elec_{username}-{clean_name(c0.name)}_G" c0 = add_label_electrical(c0, text=text, port_name=label_port_name) return add_pads_rf(component=c0, **kwargs)
if __name__ == "__main__": from ubcpdk import PDK PDK.activate() # c = add_fiber_array("ring_double") # c =gf.get_component(gc) # c = pack_doe() c = add_pads_rf() c.pprint_ports() c.show()