def containerize(component: Component, function: Callable, **kwargs):
    """Returns a containerize component after applying a function.
    This is an alternative of using the @container decorator.
    However I recommend using the decorator when possible

    Args:
        component: to containerize
        function: that applies to component
        **kwargs: for the function

    .. code::

        import pp

        def add_label(component, text='hi'):
            return component.add_label(text)

        c = pp.c.waveguide()
        cc = pp.containerize(c, function=add_label, text='hi')

    """
    c = Component()
    component_type = f"{component.name}_{function.__name__}"
    name = get_component_name(component_type, **kwargs)
    if len(name) > MAX_NAME_LENGTH:
        c.name_long = name
        name = f"{component_type}_{hashlib.md5(name.encode()).hexdigest()[:8]}"
    c.name = name
    c << component
    function(c, **kwargs)
    return c
Exemple #2
0
def rotate(component, angle=90):
    """ returns rotated component
    """
    c = Component(
        settings=component.get_settings(),
        test_protocol=component.test_protocol,
        data_analysis_protocol=component.data_analysis_protocol,
    )
    cr = c.add_ref(component)
    cr.rotate(angle)
    c.ports = cr.ports
    c.name = component.name + "_r"
    return c
Exemple #3
0
def _arbitrary_straight_waveguide(length, windows):
    """
    Args:
        length: length
        windows: [(y_start, y_stop, layer), ...]
    """
    md5 = hashlib.md5()
    for e in windows:
        md5.update(str(e).encode())

    component = Component()
    component.name = "ARB_SW_L{}_HASH{}".format(length, md5.hexdigest())
    y_min, y_max, layer0 = windows[0]
    y_min, y_max = min(y_min, y_max), max(y_min, y_max)

    # Add one port on each side centered at y=0
    for y_start, y_stop, layer in windows:
        w = abs(y_stop - y_start)
        y = (y_stop + y_start) / 2
        _wg = hline(length=length, width=w, layer=layer).ref()
        _wg.movey(y)
        component.add(_wg)
        component.absorb(_wg)
        y_min = min(y_stop, y_start, y_min)
        y_max = max(y_stop, y_start, y_max)
    width = y_max - y_min

    component.add_port(name="W0",
                       midpoint=[0, 0],
                       width=width,
                       orientation=180,
                       layer=layer0)
    component.add_port(name="E0",
                       midpoint=[length, 0],
                       width=width,
                       orientation=0,
                       layer=layer0)

    return component
def test_connect_bundle():

    xs_top = [-100, -90, -80, 0, 10, 20, 40, 50, 80, 90, 100, 105, 110, 115]

    pitch = 127.0
    N = len(xs_top)
    xs_bottom = [(i - N / 2) * pitch for i in range(N)]

    top_ports = [
        Port("top_{}".format(i), (xs_top[i], 0), 0.5, 270) for i in range(N)
    ]

    bottom_ports = [
        Port("bottom_{}".format(i), (xs_bottom[i], -400), 0.5, 90)
        for i in range(N)
    ]

    top_cell = Component(name="connect_bundle")
    elements = connect_bundle(top_ports, bottom_ports)
    for e in elements:
        top_cell.add(e)
    top_cell.name = "connect_bundle"

    return top_cell
Exemple #5
0
def component_sequence(
    sequence: List[str],
    string_to_device_in_out_ports: Dict[str, Union[Tuple[Component, str, str],
                                                   Tuple[None, str, str]]],
    ports_map: Dict[str, Tuple[str, str]] = {},
    input_port_name: str = "in",
    output_port_name: str = "out",
    start_orientation: float = 0.0,
    name_prefix: None = None,
) -> Component:
    """
    This generates a component from a sequence and a dictionnary to interprete each
    symbol in the sequence.

    Args:
        sequence: a string or a list of symbols
        string_to_device_in_out_ports: maps symbols to (device, input, output)
        ports_map: (optional) extra port mapping using the convention
            {port_name: (alias_name, port_name)}

    Returns:
        component containing the sequence of sub-components
    instantiated and connected together in the sequence order

    """
    # Remove all None devices from the sequence
    sequence = sequence[:]
    to_rm = []
    for i, d in enumerate(sequence):
        _name_device, _ = _parse_component_name(d)
        _device, _, _ = string_to_device_in_out_ports[_name_device]
        if _device is None:
            to_rm += [i]

    while to_rm:
        sequence.pop(to_rm.pop())

    # To generate unique aliases for each instance
    counters = {
        k: count(start=1)
        for k in string_to_device_in_out_ports.keys()
    }

    def _next_id(name):
        return "{}{}".format(name, next(counters[name]))

    component = Component()

    # Add first device and input port
    name_start_device, do_flip = _parse_component_name(sequence[0])
    _input_device, input_port, prev_port = string_to_device_in_out_ports[
        name_start_device]

    prev_device = component.add_ref(_input_device,
                                    alias=_next_id(name_start_device))

    if do_flip:
        prev_device = _flip_ref(prev_device, input_port)

    prev_device.rotate(angle=start_orientation)

    component.add_port(name=input_port_name,
                       port=prev_device.ports[input_port])

    # Generate and connect all elements from the sequence
    for s in sequence[1:]:
        s, do_flip = _parse_component_name(s)

        _device, input_port, next_port = string_to_device_in_out_ports[s]

        device = component.add_ref(_device, alias=_next_id(s))

        if do_flip:
            device = _flip_ref(device, input_port)

        device.connect(input_port, prev_device.ports[prev_port])
        prev_device = device
        prev_port = next_port

    # Deal with edge case where the sequence contains only one component
    if len(sequence) == 1:
        device = prev_device
        next_port = prev_port

    # Add output port
    try:
        component.add_port(name=output_port_name, port=device.ports[next_port])
    except BaseException:
        print(sequence)
        raise

    # Add any extra port specified in ports_map
    for name, (alias, alias_port_name) in ports_map.items():
        component.add_port(name=name,
                           port=component[alias].ports[alias_port_name])

    if name_prefix is not None:
        _md5 = hashlib.md5()
        _md5.update(str(string_to_device_in_out_ports).encode())
        _md5.update(str(sequence).encode())
        component.name = "{}_{}".format(name_prefix, _md5.hexdigest())
    return component
def add_io_optical(c,
                   grating_coupler=grating_coupler_te,
                   gc_port_name="W0",
                   component_name=None,
                   **kwargs):
    """ returns component with optical IO (tapers, south routes and grating_couplers)

    Args:
        component: to connect
        optical_io_spacing: SPACING_GC
        grating_coupler: grating coupler instance, function or list of functions
        bend_factory: bend_circular
        straight_factory: waveguide
        fanout_length: None,  # if None, automatic calculation of fanout length
        max_y0_optical: None
        with_align_ports: True, adds loopback structures
        waveguide_separation=4.0
        bend_radius: BEND_RADIUS
        list_port_labels: None, adds TM labels to port indices in this list
        connected_port_list_ids: None # only for type 0 optical routing
        nb_optical_ports_lines: 1
        force_manhattan: False
        excluded_ports:
        grating_indices: None
        routing_waveguide: None
        routing_method: connect_strip
        gc_port_name: W0
        optical_routing_type: None: autoselection, 0: no extension
        gc_rotation=-90
        layer_label=LAYER.LABEL
        input_port_indexes=[0]
        component_name: for the label

    """
    if not c.ports:
        return c
    cc = Component(
        settings=c.get_settings(),
        test_protocol=c.test_protocol,
        data_analysis_protocol=c.data_analysis_protocol,
    )
    cc.function_name = "add_io_optical"

    if isinstance(grating_coupler, list):
        gc = grating_coupler[0]
    else:
        gc = grating_coupler
    gc = pp.call_if_func(gc)

    if "polarization" in gc.settings:
        gc.polarization = gc.settings["polarization"]

    cc.name = "{}_{}".format(c.name, gc.polarization)

    port_width_gc = list(gc.ports.values())[0].width
    port_width_component = list(c.ports.values())[0].width

    if port_width_component != port_width_gc:
        c = add_tapers(
            c,
            taper(length=10, width1=port_width_gc,
                  width2=port_width_component))

    elements, io_gratings_lines, _ = _get_optical_io_elements(
        component=c,
        grating_coupler=grating_coupler,
        gc_port_name=gc_port_name,
        component_name=component_name,
        **kwargs)
    if len(elements) == 0:
        return c

    cc.add(elements)
    for io_gratings in io_gratings_lines:
        cc.add(io_gratings)
    cc.add(c.ref())
    cc.move(origin=io_gratings_lines[0][0].ports[gc_port_name],
            destination=(0, 0))

    return cc
def import_gds(
    gdspath: Union[str, Path],
    cellname: None = None,
    flatten: bool = False,
    snap_to_grid_nm: Optional[int] = None,
) -> Component:
    """Returns a Componenent from a GDS file.

    Adapted from phidl/geometry.py

    Args:
        gdspath: path of GDS file
        cellname: cell of the name to import (None) imports top cell
        flatten: if True returns flattened (no hierarchy)
        snap_to_grid_nm: snap to different nm grid (does not snap if False)

    """
    gdsii_lib = gdspy.GdsLibrary()
    gdsii_lib.read_gds(gdspath)
    top_level_cells = gdsii_lib.top_level()
    cellnames = [c.name for c in top_level_cells]

    if cellname is not None:
        if cellname not in gdsii_lib.cells:
            raise ValueError(
                f"cell {cellname} is not in file {gdspath} with cells {cellnames}"
            )
        topcell = gdsii_lib.cells[cellname]
    elif cellname is None and len(top_level_cells) == 1:
        topcell = top_level_cells[0]
    elif cellname is None and len(top_level_cells) > 1:
        raise ValueError(
            f"import_gds() There are multiple top-level cells in {gdspath}, "
            f"you must specify `cellname` to select of one of them among {cellnames}"
        )

    if not flatten:
        D_list = []
        c2dmap = {}
        for cell in gdsii_lib.cells.values():
            D = Component(name=cell.name)
            D.polygons = cell.polygons
            D.references = cell.references
            D.name = cell.name
            for label in cell.labels:
                rotation = label.rotation
                if rotation is None:
                    rotation = 0
                label_ref = D.add_label(
                    text=label.text,
                    position=np.asfarray(label.position),
                    magnification=label.magnification,
                    rotation=rotation * 180 / np.pi,
                    layer=(label.layer, label.texttype),
                )
                label_ref.anchor = label.anchor
            c2dmap.update({cell: D})
            D_list += [D]

        for D in D_list:
            # First convert each reference so it points to the right Device
            converted_references = []
            for e in D.references:
                ref_device = c2dmap[e.ref_cell]
                if isinstance(e, gdspy.CellReference):
                    dr = DeviceReference(
                        device=ref_device,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
                elif isinstance(e, gdspy.CellArray):
                    dr = CellArray(
                        device=ref_device,
                        columns=e.columns,
                        rows=e.rows,
                        spacing=e.spacing,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
            D.references = converted_references

            # Next convert each Polygon
            # temp_polygons = list(D.polygons)
            # D.polygons = []
            # for p in temp_polygons:
            #     D.add_polygon(p)

            # Next convert each Polygon
            temp_polygons = list(D.polygons)
            D.polygons = []
            for p in temp_polygons:
                if snap_to_grid_nm:
                    points_on_grid = pp.drc.snap_to_grid(
                        p.polygons[0], nm=snap_to_grid_nm
                    )
                    p = gdspy.Polygon(
                        points_on_grid, layer=p.layers[0], datatype=p.datatypes[0]
                    )
                D.add_polygon(p)
        topdevice = c2dmap[topcell]
        return topdevice
    if flatten:
        D = pp.Component()
        polygons = topcell.get_polygons(by_spec=True)

        for layer_in_gds, polys in polygons.items():
            D.add_polygon(polys, layer=layer_in_gds)
        return D