Beispiel #1
0
def connect_loopback(port0: Port,
                     port1: Port,
                     a: float,
                     b: float,
                     y_bot_align_route: float,
                     cross_section: CrossSectionFactory = strip,
                     **kwargs) -> ComponentReference:
    p0 = port0.position
    p1 = port1.position
    points = [
        p0,
        p0 + (0, a),
        p0 + (b, a),
        p0 + (b, y_bot_align_route),
        p1 + (-b, y_bot_align_route),
        p1 + (-b, a),
        p1 + (0, a),
        p1,
    ]

    bend90 = gf.components.bend_euler(cross_section=cross_section, **kwargs)
    return round_corners(points=points,
                         bend=bend90,
                         straight=gf.components.straight,
                         **kwargs).references
Beispiel #2
0
def get_route_from_steps(port1: Port,
                         port2: Port,
                         steps: Optional[List[Dict[str, float]]] = None,
                         bend: ComponentOrFactory = bend_euler,
                         straight: ComponentOrFactory = straight_function,
                         taper: Optional[ComponentOrFactory] = taper_function,
                         cross_section: CrossSectionFactory = strip,
                         **kwargs) -> Route:
    """Returns a route formed by the given waypoints steps
    bends instead of corners and optionally tapers in straight sections.
    Tapering to wider straights reduces the optical loss.
    `get_route_from_steps` is a manual version of `get_route`
    and a more concise and convenient version of `get_route_from_waypoints`

    Args:
        port1: start port
        port2: end port
        steps: changes that define the route [{'dx': 5}, {'dy': 10}]
        bend: function that returns bends
        straight: function that returns straight waveguides
        taper: function that returns tapers
        cross_section
        **kwargs: cross_section settings

    .. plot::
        :include-source:

        import gdsfactory as gf

        c = gf.Component("get_route_from_steps_sample")
        w = gf.components.straight()
        left = c << w
        right = c << w
        right.move((100, 80))

        obstacle = gf.components.rectangle(size=(100, 10), port_type=None)
        obstacle1 = c << obstacle
        obstacle2 = c << obstacle
        obstacle1.ymin = 40
        obstacle2.xmin = 25

        p1 = left.ports['o2']
        p2 = right.ports['o2']
        route = gf.routing.get_route_from_steps(
            port1=p1,
            port2=p2,
            steps=[
                {"x": 20},
                {"y": 20},
                {"x": 120},
                {"y": 80},
            ],
        )
        c.add(route.references)
        c.plot()
        c.show()

    """
    x, y = port1.midpoint
    x2, y2 = port2.midpoint

    waypoints = [(x, y)]
    steps = steps or []

    for d in steps:
        x = d["x"] if "x" in d else x
        x += d.get("dx", 0)
        y = d["y"] if "y" in d else y
        y += d.get("dy", 0)
        waypoints += [(x, y)]

    waypoints += [(x2, y2)]

    x = cross_section(**kwargs)
    auto_widen = x.info.get("auto_widen", False)
    width1 = x.info.get("width")
    width2 = x.info.get("width_wide") if auto_widen else width1
    taper_length = x.info.get("taper_length")
    waypoints = np.array(waypoints)

    if auto_widen:
        taper = (taper(
            length=taper_length,
            width1=width1,
            width2=width2,
            cross_section=cross_section,
            **kwargs,
        ) if callable(taper) else taper)
    else:
        taper = None

    return round_corners(
        points=waypoints,
        bend=bend,
        straight=straight,
        taper=taper,
        cross_section=cross_section,
        **kwargs,
    )
def add_grating_couplers_with_loopback_fiber_array(
    component: Component,
    grating_coupler: ComponentFactory = grating_coupler_te,
    excluded_ports: None = None,
    grating_separation: float = 127.0,
    bend_radius_loopback: Optional[float] = None,
    gc_port_name: str = "o1",
    gc_rotation: int = -90,
    straight_separation: float = 5.0,
    bend: ComponentFactory = bend_euler,
    straight: ComponentFactory = straight_function,
    layer_label: Tuple[int, int] = (200, 0),
    layer_label_loopback: Optional[Tuple[int, int]] = None,
    component_name: Optional[str] = None,
    with_loopback: bool = True,
    nlabels_loopback: int = 2,
    get_input_labels_function: Callable = get_input_labels,
    cross_section: CrossSectionFactory = strip,
    select_ports: Callable = select_ports_optical,
    **kwargs,
) -> Component:
    """Returns a component with grating_couplers and loopback.

    Args:
        component:
        grating_coupler:
        excluded_ports:
        grating_separation:
        bend_radius_loopback:
        gc_port_name:
        gc_rotation:
        straight_separation:
        bend:
        straight:
        layer_label:
        component_name:
        with_loopback: If True, add compact loopback alignment ports
        nlabels_loopback: number of ports to label (0: no labels, 1: first port, 2: both ports)
        cross_section:
        **kwargs: cross_section settings
    """
    x = cross_section(**kwargs)
    bend_radius_loopback = bend_radius_loopback or x.info["radius"]
    excluded_ports = excluded_ports or []
    gc = grating_coupler() if callable(grating_coupler) else grating_coupler

    direction = "S"
    component_name = component_name or component.info_child.name
    c = Component()
    c.component = component

    c.add_ref(component)

    # Find grating port name if not specified
    if gc_port_name is None:
        gc_port_name = list(gc.ports.values())[0].name

    # List the optical ports to connect
    optical_ports = select_ports(component.ports)
    optical_ports = list(optical_ports.values())
    optical_ports = [p for p in optical_ports if p.name not in excluded_ports]
    optical_ports = direction_ports_from_list_ports(optical_ports)[direction]

    # Check if the ports are equally spaced
    grating_separation_extracted = check_ports_have_equal_spacing(optical_ports)
    if grating_separation_extracted != grating_separation:
        raise ValueError(
            "Grating separation must be {}. Got {}".format(
                grating_separation, grating_separation_extracted
            )
        )

    # Add grating references
    references = []
    for port in optical_ports:
        gc_ref = c.add_ref(gc)
        gc_ref.connect(gc.ports[gc_port_name].name, port)
        references += [gc_ref]

    labels = get_input_labels_function(
        io_gratings=references,
        ordered_ports=optical_ports,
        component_name=component_name,
        layer_label=layer_label,
        gc_port_name=gc_port_name,
    )
    c.add(labels)

    if with_loopback:
        y0 = references[0].ports[gc_port_name].y
        xs = [p.x for p in optical_ports]
        x0 = min(xs) - grating_separation
        x1 = max(xs) + grating_separation

        gca1, gca2 = [
            gc.ref(position=(x, y0), rotation=gc_rotation, port_id=gc_port_name)
            for x in [x0, x1]
        ]

        gsi = gc.size_info
        port0 = gca1.ports[gc_port_name]
        port1 = gca2.ports[gc_port_name]
        p0 = port0.position
        p1 = port1.position
        a = bend_radius_loopback + 0.5
        b = max(2 * a, grating_separation / 2)
        y_bot_align_route = -gsi.width - straight_separation

        points = np.array(
            [
                p0,
                p0 + (0, a),
                p0 + (b, a),
                p0 + (b, y_bot_align_route),
                p1 + (-b, y_bot_align_route),
                p1 + (-b, a),
                p1 + (0, a),
                p1,
            ]
        )
        bend90 = bend(
            radius=bend_radius_loopback, cross_section=cross_section, **kwargs
        )
        loopback_route = round_corners(
            points=points,
            bend=bend90,
            straight=straight,
            cross_section=cross_section,
            **kwargs,
        )
        c.add([gca1, gca2])
        c.add(loopback_route.references)

        component_name_loopback = f"loopback_{component_name}"
        if nlabels_loopback == 1:
            io_gratings_loopback = [gca1]
            ordered_ports_loopback = [port0]
        if nlabels_loopback == 2:
            io_gratings_loopback = [gca1, gca2]
            ordered_ports_loopback = [port0, port1]
        if nlabels_loopback == 0:
            pass
        elif 0 < nlabels_loopback <= 2:
            c.add(
                get_input_labels_function(
                    io_gratings=io_gratings_loopback,
                    ordered_ports=ordered_ports_loopback,
                    component_name=component_name_loopback,
                    layer_label=layer_label_loopback or layer_label,
                    gc_port_name=gc_port_name,
                )
            )
        else:
            raise ValueError(
                f"Invalid nlabels_loopback = {nlabels_loopback}, "
                "valid (0: no labels, 1: first port, 2: both ports2)"
            )
    c.copy_child_info(component)
    return c
def get_bundle_from_waypoints(
    ports1: List[Port],
    ports2: List[Port],
    waypoints: Coordinates,
    straight: Callable = straight_function,
    taper: Callable = taper_function,
    bend: Callable = bend_euler,
    sort_ports: bool = True,
    cross_section: CrossSectionFactory = strip,
    separation: Optional[float] = None,
    on_route_error: Callable = get_route_error,
    **kwargs,
) -> List[Route]:
    """Returns list of routes that connect bundle of ports with bundle of routes
    where routes follow a list of waypoints.

    Args:
        ports1: list of ports
        ports2: list of ports
        waypoints: list of points defining a route
        straight: function that returns straights
        taper: function that returns tapers
        bend: function that returns bends
        sort_ports: sorts ports
        cross_section: cross_section
        separation: center to center, defaults to ports1 separation
        **kwargs: cross_section settings

    """
    if len(ports2) != len(ports1):
        raise ValueError(
            f"Number of start ports should match number of end ports.\
        Got {len(ports1)} {len(ports2)}"
        )

    for p in ports1:
        p.angle = int(p.angle) % 360

    for p in ports2:
        p.angle = int(p.angle) % 360

    start_angle = ports1[0].orientation
    end_angle = ports2[0].orientation
    waypoints = [ports1[0].midpoint] + list(waypoints) + [ports2[0].midpoint]

    # Sort the ports such that the bundle connect the correct corresponding ports.
    angles_to_sorttypes = {
        (0, 180): ("Y", "Y"),
        (0, 90): ("Y", "X"),
        (0, 0): ("Y", "-Y"),
        (0, 270): ("Y", "-X"),
        (90, 0): ("X", "Y"),
        (90, 90): ("X", "-X"),
        (90, 180): ("X", "-Y"),
        (90, 270): ("X", "X"),
        (180, 90): ("Y", "-X"),
        (180, 0): ("Y", "Y"),
        (180, 270): ("Y", "X"),
        (180, 180): ("Y", "-Y"),
        (270, 90): ("X", "X"),
        (270, 270): ("X", "-X"),
        (270, 0): ("X", "-Y"),
        (270, 180): ("X", "Y"),
    }

    dict_sorts = {
        "X": lambda p: p.x,
        "Y": lambda p: p.y,
        "-X": lambda p: -p.x,
        "-Y": lambda p: -p.y,
    }
    key = (start_angle, end_angle)
    sp_st, ep_st = angles_to_sorttypes[key]
    start_port_sort = dict_sorts[sp_st]
    end_port_sort = dict_sorts[ep_st]

    if sort_ports:
        ports1.sort(key=start_port_sort)
        ports2.sort(key=end_port_sort)

    try:
        routes = _generate_manhattan_bundle_waypoints(
            ports1=ports1,
            ports2=ports2,
            waypoints=list(waypoints),
            separation=separation,
            **kwargs,
        )
    except RouteError:
        return [on_route_error(waypoints)]

    x = cross_section(**kwargs)
    bends90 = [bend(cross_section=cross_section, **kwargs) for p in ports1]

    if taper and x.info.get("auto_widen", True):
        if callable(taper):
            taper = taper(
                length=x.info.get("taper_length", 0.0),
                width1=ports1[0].width,
                width2=x.info.get("width_wide"),
                layer=ports1[0].layer,
            )
        else:
            # In this case the taper is a fixed cell
            taper = taper
    else:
        taper = None
    connections = [
        round_corners(
            points=pts,
            bend=bend90,
            straight=straight,
            taper=taper,
            cross_section=cross_section,
            **kwargs,
        )
        for pts, bend90 in zip(routes, bends90)
    ]
    return connections
Beispiel #5
0
def gen_loopback(
    start_port: Port,
    end_port: Port,
    gc: ComponentFactory,
    grating_separation: float = 127.0,
    gc_rotation: int = -90,
    gc_port_name: str = "o1",
    bend_radius_loopback: float = 10.0,
    bend: ComponentFactory = gf.components.bend_euler,
    straight: ComponentFactory = gf.components.straight,
    y_bot_align_route: None = None,
) -> List[ComponentReference]:
    """Returns loopback (grating coupler align reference) references
    from a start_port and end_port

    Input grating generated on the left of start_port
    Output grating generated on the right of end_port

    .. code::

        __________________________________________
        | separation  |            |              |
        |             |            |              |
       GC          start_port  end_port          GC

    """

    gc = gc() if callable(gc) else gc

    if hasattr(start_port, "y"):
        y0 = start_port.y
    else:
        y0 = start_port[1]

    if hasattr(start_port, "x"):
        x0 = start_port.x - grating_separation
    else:
        x0 = start_port[0] - grating_separation

    if hasattr(end_port, "x"):
        x1 = end_port.x + grating_separation
    else:
        x1 = end_port[0] + grating_separation

    gca1, gca2 = [
        gc.ref(position=(x, y0), rotation=gc_rotation, port_id=gc_port_name)
        for x in [x0, x1]
    ]

    gsi = gc.size_info
    p0 = gca1.ports[gc_port_name].position
    p1 = gca2.ports[gc_port_name].position
    bend90 = bend(radius=bend_radius_loopback)

    if hasattr(bend90, "dx"):
        a = abs(bend90.info.dy)
    else:
        a = bend_radius_loopback + 0.5
    b = max(2 * a, grating_separation / 2)
    y_bot_align_route = (y_bot_align_route if y_bot_align_route is not None
                         else -gsi.width - 5.0)

    points = [
        p0,
        p0 + (0, a),
        p0 + (b, a),
        p0 + (b, y_bot_align_route),
        p1 + (-b, y_bot_align_route),
        p1 + (-b, a),
        p1 + (0, a),
        p1,
    ]
    route = round_corners(points=points, bend=bend90, straight=straight)
    elements = [gca1, gca2]
    elements.extend(route.references)
    return elements
Beispiel #6
0
def spiral_inner_io(N: int = 6,
                    x_straight_inner_right: float = 150.0,
                    x_straight_inner_left: float = 50.0,
                    y_straight_inner_top: float = 50.0,
                    y_straight_inner_bottom: float = 10.0,
                    grating_spacing: float = 127.0,
                    waveguide_spacing: float = 3.0,
                    bend90_function: ComponentFactory = bend_euler,
                    bend180_function: ComponentFactory = bend_euler180,
                    straight: ComponentFactory = straight_function,
                    length: Optional[float] = None,
                    cross_section: CrossSectionFactory = strip,
                    cross_section_bend: Optional[CrossSectionFactory] = None,
                    **kwargs) -> Component:
    """Returns Spiral with ports inside the spiral loop.
    You can add grating couplers inside .

    Args:
        N: number of loops
        x_straight_inner_right:
        x_straight_inner_left:
        y_straight_inner_top:
        y_straight_inner_bottom:
        grating_spacing: defaults to 127 for fiber array
        waveguide_spacing: center to center spacing
        bend90_function
        bend180_function
        straight: straight function
        length: spiral target length (um), overrides x_straight_inner_left
            to match the length by a simple 1D interpolation
        cross_section:
        cross_section_bend: for the bends
        kwargs: cross_section settings

    """
    dx = dy = waveguide_spacing
    x = cross_section(**kwargs)
    width = x.info.get("width")
    layer = x.info.get("layer")
    cross_section_bend = cross_section_bend or cross_section

    if length:
        x_straight_inner_left = get_straight_length(
            length=length,
            spiral_function=spiral_inner_io,
            N=N,
            x_straight_inner_right=x_straight_inner_right,
            x_straight_inner_left=x_straight_inner_left,
            y_straight_inner_top=y_straight_inner_top,
            y_straight_inner_bottom=y_straight_inner_bottom,
            grating_spacing=grating_spacing,
            waveguide_spacing=waveguide_spacing,
        )

    _bend180 = gf.call_if_func(bend180_function,
                               cross_section=cross_section_bend,
                               **kwargs)
    _bend90 = gf.call_if_func(bend90_function,
                              cross_section=cross_section_bend,
                              **kwargs)

    rx, ry = get_bend_port_distances(_bend90)
    _, rx180 = get_bend_port_distances(
        _bend180)  # rx180, second arg since we rotate

    component = Component()

    p1 = gf.Port(
        name="o1",
        midpoint=(0, y_straight_inner_top),
        orientation=270,
        width=width,
        layer=layer,
        cross_section=cross_section_bend,
    )
    p2 = gf.Port(
        name="o2",
        midpoint=(grating_spacing, y_straight_inner_top),
        orientation=270,
        width=width,
        layer=layer,
        cross_section=cross_section_bend,
    )

    component.add_port(name="o1", port=p1)
    component.add_port(name="o2", port=p2)

    # Create manhattan path going from west grating to westest port of bend 180
    _pt = np.array(p1.position)
    pts_w = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i + 1) * dy
        x2 = grating_spacing + 2 * rx + x_straight_inner_right + (2 * i +
                                                                  1) * dx
        y3 = -y_straight_inner_bottom - ry - (2 * i + 3) * dy
        x4 = -x_straight_inner_left - (2 * i + 1) * dx
        if i == N - 1:
            x4 = x4 - rx180 + dx

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_w += [_pt1, _pt2, _pt3, _pt4, _pt5]

    route_west = round_corners(pts_w,
                               bend=_bend90,
                               straight=straight,
                               cross_section=cross_section,
                               **kwargs)
    component.add(route_west.references)

    # Add loop back
    bend180_ref = _bend180.ref(port_id="o2",
                               position=route_west.ports[1],
                               rotation=90)
    component.add(bend180_ref)

    # Create manhattan path going from east grating to eastest port of bend 180
    _pt = np.array(p2.position)
    pts_e = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i) * dy
        x2 = grating_spacing + 2 * rx + x_straight_inner_right + 2 * i * dx
        y3 = -y_straight_inner_bottom - ry - (2 * i + 2) * dy
        x4 = -x_straight_inner_left - (2 * i) * dx

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_e += [_pt1, _pt2, _pt3, _pt4, _pt5]

    route_east = round_corners(pts_e,
                               bend=_bend90,
                               straight=straight,
                               cross_section=cross_section,
                               **kwargs)
    component.add(route_east.references)

    length = route_east.length + route_west.length + _bend180.info.length
    component.info.length = snap_to_grid(length + 2 * y_straight_inner_top)
    return component
Beispiel #7
0
def get_route_from_waypoints(
    waypoints: Coordinates,
    bend: Callable = bend_euler,
    straight: Callable = straight_function,
    taper_factory: Optional[Callable] = taper_function,
    route_filter=None,
    cross_section: CrossSectionFactory = strip,
    **kwargs,
) -> Route:
    """Returns a route formed by the given waypoints with
    bends instead of corners and optionally tapers in straight sections.
    Tapering to wider straights reduces the optical loss.
    `get_route_from_waypoints` is a manual version of `get_route`
    Not that `get_route_from_steps` is a  more concise and convenient version of `get_route_from_waypoints` also available in gf.routing

    Args:
        waypoints: Coordinates that define the route
        bend: function that returns bends
        straight: function that returns straight waveguides
        taper_factory: function that returns tapers
        route_filter: FIXME, keep it here. Find a way to remove it.
        cross_section:
        **kwargs: cross_section settings

    .. plot::
        :include-source:

        import gdsfactory as gf

        c = gf.Component('waypoints_sample')

        w = gf.components.straight()
        left = c << w
        right = c << w
        right.move((100, 80))

        obstacle = gf.components.rectangle(size=(100, 10))
        obstacle1 = c << obstacle
        obstacle2 = c << obstacle
        obstacle1.ymin=40
        obstacle2.xmin=25


        p0x, p0y = left.ports['E0'].midpoint
        p1x, p1y = right.ports['E0'].midpoint
        o = 10 # vertical offset to overcome bottom obstacle
        ytop = 20


        routes = gf.routing.get_route_from_waypoints(
            [
                (p0x, p0y),
                (p0x + o, p0y),
                (p0x + o, ytop),
                (p1x + o, ytop),
                (p1x + o, p1y),
                (p1x, p1y),
            ],
        )
        c.add(routes.references)
        c.show()
    """

    x = cross_section(**kwargs)
    auto_widen = x.info.get("auto_widen", False)
    width1 = x.info.get("width")
    width2 = x.info.get("width_wide") if auto_widen else width1
    taper_length = x.info.get("taper_length")
    waypoints = np.array(waypoints)

    if auto_widen:
        taper = (taper_factory(
            length=taper_length,
            width1=width1,
            width2=width2,
            cross_section=cross_section,
            **kwargs,
        ) if callable(taper_factory) else taper_factory)
    else:
        taper = None

    return round_corners(
        points=waypoints,
        bend=bend,
        straight=straight,
        taper=taper,
        cross_section=cross_section,
        **kwargs,
    )
Beispiel #8
0
def delay_snake(
    wg_width: float = 0.5,
    wg_width_wide: float = 2.0,
    total_length: float = 1600.0,
    L0: float = 5.0,
    taper_length: float = 10.0,
    n: int = 2,
    taper: ComponentFactory = taper_function,
    bend: ComponentFactory = bend_euler,
    straight: ComponentFactory = straight_function,
    **kwargs
) -> Component:
    """Snake input facing west
    Snake output facing east

    Args:
        wg_width
        wg_width_wide: for the wide
        total_length:
        L0: initial offset
        taper_length: length of the taper
        n: number of loops
        taper: taper library
        bend
        straight

    .. code::

       | L0 |    L2        |

            ->-------------|
                           | pi * radius
       |-------------------|
       |
       |------------------->

       |        DL         |


    """
    epsilon = 0.1
    bend90 = bend(width=wg_width, **kwargs)
    dy = bend90.info.dy
    DL = (total_length + L0 - n * (pi * dy + epsilon)) / (2 * n + 1)
    L2 = DL - L0
    assert (
        L2 > 0
    ), "Snake is too short: either reduce L0, increase the total length,\
    or decrease n"

    y = 0
    path = [(0, y), (L2, y)]
    for _i in range(n):
        y -= 2 * dy + epsilon
        path += [(L2, y), (-L0, y)]
        y -= 2 * dy + epsilon
        path += [(-L0, y), (L2, y)]

    path = [(round(_x, 3), round(_y, 3)) for _x, _y in path]

    component = gf.Component()
    if taper:
        _taper = taper(
            width1=wg_width, width2=wg_width_wide, length=taper_length, **kwargs
        )
    route = round_corners(
        points=path,
        bend=bend90,
        straight=straight,
        taper=_taper,
        width_wide=wg_width_wide,
        **kwargs
    )
    component.add(route.references)
    component.add_port("o1", port=route.ports[0])
    component.add_port("o2", port=route.ports[1])
    return component
Beispiel #9
0
def route_fiber_array(
    component: Component,
    fiber_spacing: float = TECH.fiber_array_spacing,
    grating_coupler: ComponentOrFactory = grating_coupler_te,
    bend: ComponentFactory = bend_euler,
    straight: ComponentFactory = straight,
    taper_factory: ComponentFactory = taper,
    fanout_length: Optional[float] = None,
    max_y0_optical: None = None,
    with_loopback: bool = True,
    nlabels_loopback: int = 2,
    straight_separation: float = 6.0,
    straight_to_grating_spacing: float = 5.0,
    optical_routing_type: Optional[int] = None,
    connected_port_names: None = None,
    nb_optical_ports_lines: int = 1,
    force_manhattan: bool = False,
    excluded_ports: List[Any] = None,
    grating_indices: None = None,
    route_filter: Callable = get_route_from_waypoints,
    gc_port_name: str = "o1",
    gc_rotation: int = -90,
    layer_label: Optional[Tuple[int, int]] = (66, 0),
    layer_label_loopback: Optional[Tuple[int, int]] = None,
    component_name: Optional[str] = None,
    x_grating_offset: int = 0,
    optical_port_labels: Optional[Tuple[str, ...]] = None,
    get_input_label_text_loopback_function:
    Callable = get_input_label_text_loopback,
    get_input_label_text_function: Callable = get_input_label_text,
    get_input_labels_function: Optional[Callable] = get_input_labels,
    select_ports: Callable = select_ports_optical,
    cross_section: CrossSectionFactory = strip,
    **kwargs,
) -> Tuple[List[Union[ComponentReference, Label]],
           List[List[ComponentReference]], List[Port]]:
    """Returns component I/O elements for adding grating couplers with a fiber array
    Many components are fine with the defaults.

    Args:
        component: The component to connect.
        fiber_spacing: the wanted spacing between the optical I/O
        grating_coupler: grating coupler instance, function or list of functions
        bend:  for bends
        straight: straight
        fanout_length: target distance between gratings and the southest component port.
            If None, automatically calculated.
        max_y0_optical: Maximum y coordinate at which the intermediate optical ports can be set.
            Usually fine to leave at None.
        with_loopback: If True, add compact loopback alignment ports
        nlabels_loopback: number of labels of align ports (0: no labels, 1: first port, 2: both ports2)
        straight_separation: min separation between routing straights
        straight_to_grating_spacing: from align ports
        optical_routing_type: There are three options for optical routing
           * ``0`` is very basic but can be more compact.
            Can also be used in combination with ``connected_port_names``
            or to route some components which otherwise fail with type ``1``.
           * ``1`` is the standard routing.
           * ``2`` uses the optical ports as a guideline for the component's physical size
            (instead of using the actual component size).
            Useful where the component is large due to metal tracks
           * ``None: leads to an automatic decision based on size and number
           of I/O of the component.
        connected_port_names: only for type 0 optical routing.
            Can specify which ports goes to which grating assuming the gratings are ordered from left to right.
            e.g ['N0', 'W1','W0','E0','E1', 'N1' ] or [4,1,7,3]
        nb_optical_ports_lines: number of lines with I/O grating couplers. One line by default.
            WARNING: Only works properly if:
            - nb_optical_ports_lines divides the total number of ports
            - the components have an equal number of inputs and outputs
        force_manhattan: sometimes port linker defaults to an S-bend due to lack of space to do manhattan.
            Force manhattan offsets all the ports to replace the s-bend by a straight link.
            This fails if multiple ports have the same issue
        excluded_ports: ports excluded from routing
        grating_indices: allows to fine skip some grating slots
            e.g [0,1,4,5] will put two gratings separated by the pitch.
            Then there will be two empty grating slots, and after that an additional two gratings.
        route_filter: straight and bend factories
        gc_port_name: grating_coupler port name, where to route straights
        gc_rotation: grating_coupler rotation (deg)
        layer_label: for TM labels
        component_name: name of component
        x_grating_offset: x offset
        optical_port_labels: port labels to route_to_fiber_array
        select_ports: function to select ports for which to add grating couplers
        get_input_label_text_loopback_function: function to get input labels for grating couplers
        get_input_label_text_function

    Returns:
        elements, io_grating_lines, list of ports
    """

    x = cross_section(**kwargs)
    radius = x.info["radius"]

    assert isinstance(
        radius,
        (int,
         float)), f"radius = {radius} {type (radius)} needs to be int or float"

    component_name = component_name or component.name
    excluded_ports = excluded_ports or []
    if optical_port_labels is None:
        optical_ports = list(select_ports(component.ports).values())
    else:
        optical_ports = [component.ports[lbl] for lbl in optical_port_labels]

    optical_ports = [p for p in optical_ports if p.name not in excluded_ports]
    N = len(optical_ports)
    # optical_ports_labels = [p.name for p in optical_ports]
    # print(optical_ports_labels)
    if N == 0:
        return [], [], 0

    elements = []

    # grating_coupler can either be a component/function
    # or a list of components/functions

    if isinstance(grating_coupler, list):
        grating_couplers = [gf.call_if_func(g) for g in grating_coupler]
        grating_coupler = grating_couplers[0]
    else:
        grating_coupler = gf.call_if_func(grating_coupler)
        grating_couplers = [grating_coupler] * N

    assert (gc_port_name in grating_coupler.ports
            ), f"{gc_port_name} not in {list(grating_coupler.ports.keys())}"

    if gc_port_name not in grating_coupler.ports:
        raise ValueError(
            f"{gc_port_name} not in {list(grating_coupler.ports.keys())}")

    # Now:
    # - grating_coupler is a single grating coupler
    # - grating_couplers is a list of grating couplers
    # Define the route filter to apply to connection methods

    bend90 = bend(cross_section=cross_section, **
                  kwargs) if callable(bend) else bend

    dy = abs(bend90.info.dy)

    # `delta_gr_min` Used to avoid crossing between straights in special cases
    # This could happen when abs(x_port - x_grating) <= 2 * radius

    dy = bend90.info.dy
    delta_gr_min = 2 * dy + 1

    offset = (N - 1) * fiber_spacing / 2.0

    # Get the center along x axis
    x_c = round(sum([p.x for p in optical_ports]) / N, 1)
    y_min = component.ymin  # min([p.y for p in optical_ports])

    # Sort the list of optical ports:
    direction_ports = direction_ports_from_list_ports(optical_ports)
    sep = straight_separation

    K = len(optical_ports)
    K = K + 1 if K % 2 else K

    # Set routing type if not specified
    pxs = [p.x for p in optical_ports]
    is_big_component = ((K > 2)
                        or (max(pxs) - min(pxs) > fiber_spacing - delta_gr_min)
                        or (component.xsize > fiber_spacing))
    if optical_routing_type is None:
        if not is_big_component:
            optical_routing_type = 0
        else:
            optical_routing_type = 1

    # choose the default length if the default fanout distance is not set
    def has_p(side):
        return len(direction_ports[side]) > 0

    list_ew_ports_on_sides = [has_p(side) for side in ["E", "W"]]
    list_ns_ports_on_sides = [has_p(side) for side in ["N", "S"]]

    has_ew_ports = any(list_ew_ports_on_sides)
    has_ns_ports = any(list_ns_ports_on_sides)

    is_one_sided_horizontal = False
    for side1, side2 in [("E", "W"), ("W", "E")]:
        if len(direction_ports[side1]) >= 2:
            if all([
                    len(direction_ports[side]) == 0
                    for side in ["N", "S", side2]
            ]):
                is_one_sided_horizontal = True

    # Compute fanout length if not specified
    if fanout_length is None:
        fanout_length = dy + 1.0
        # We need 3 bends in that case to connect the most bottom port to the
        # grating couplers
        if has_ew_ports and is_big_component:
            # print('big')
            fanout_length = max(fanout_length, 3 * dy + 1.0)

        if has_ns_ports or is_one_sided_horizontal:
            # print('one sided')
            fanout_length = max(fanout_length, 2 * dy + 1.0)

        if has_ew_ports and not is_big_component:
            # print('ew_ports')
            fanout_length = max(fanout_length, dy + 1.0)

    fanout_length += dy

    # use x for grating coupler since we rotate it
    y0_optical = y_min - fanout_length - grating_coupler.ports[gc_port_name].x
    y0_optical += -K / 2 * sep
    y0_optical = round(y0_optical, 1)

    if max_y0_optical is not None:
        y0_optical = round(min(max_y0_optical, y0_optical), 1)
    """
     - First connect half of the north ports going from middle of list
    down to first elements
     - then connect west ports (top to bottom)
     - then connect south ports (left to right)
     - then east ports (bottom to top)
     - then second half of the north ports (right to left)

    """
    ports = []
    north_ports = direction_ports["N"]
    north_start = north_ports[0:len(north_ports) // 2]
    north_finish = north_ports[len(north_ports) // 2:]

    west_ports = direction_ports["W"]
    west_ports.reverse()
    east_ports = direction_ports["E"]
    south_ports = direction_ports["S"]
    north_finish.reverse()  # Sort right to left
    north_start.reverse()  # Sort right to left
    ordered_ports = north_start + west_ports + south_ports + east_ports + north_finish

    nb_ports_per_line = N // nb_optical_ports_lines
    grating_coupler_si = grating_coupler.size_info
    y_gr_gap = (K / (nb_optical_ports_lines) + 1) * sep
    gr_coupler_y_sep = grating_coupler_si.height + y_gr_gap + dy

    offset = (nb_ports_per_line - 1) * fiber_spacing / 2 - x_grating_offset
    io_gratings_lines = [
    ]  # [[gr11, gr12, gr13...], [gr21, gr22, gr23...] ...]

    if grating_indices is None:
        grating_indices = list(range(nb_ports_per_line))
    else:
        assert len(grating_indices) == nb_ports_per_line

    for j in range(nb_optical_ports_lines):
        io_gratings = [
            gc.ref(
                position=(
                    x_c - offset + i * fiber_spacing,
                    y0_optical - j * gr_coupler_y_sep,
                ),
                rotation=gc_rotation,
                port_id=gc_port_name,
            ) for i, gc in zip(grating_indices, grating_couplers)
        ]

        io_gratings_lines += [io_gratings[:]]
        ports += [grating.ports[gc_port_name] for grating in io_gratings]

    if optical_routing_type == 0:
        """
        Basic optical routing, typically fine for small components
        No heuristic to avoid collisions between connectors.

        If specified ports to connect in a specific order
        (i.e if connected_port_names is not None and not empty)
        then grab these ports
        """
        if connected_port_names:
            ordered_ports = [component.ports[i] for i in connected_port_names]

        for io_gratings in io_gratings_lines:
            for i in range(N):
                p0 = io_gratings[i].ports[gc_port_name]
                p1 = ordered_ports[i]
                waypoints = generate_manhattan_waypoints(
                    input_port=p0,
                    output_port=p1,
                    bend=bend90,
                    straight=straight,
                    cross_section=cross_section,
                    **kwargs,
                )
                route = route_filter(
                    waypoints=waypoints,
                    bend=bend90,
                    straight=straight,
                    cross_section=cross_section,
                    **kwargs,
                )
                elements.extend(route.references)

    elif optical_routing_type in [1, 2]:
        route = route_south(
            component=component,
            optical_routing_type=optical_routing_type,
            excluded_ports=excluded_ports,
            straight_separation=straight_separation,
            io_gratings_lines=io_gratings_lines,
            gc_port_name=gc_port_name,
            bend=bend,
            straight=straight,
            taper_factory=taper_factory,
            select_ports=select_ports,
            cross_section=cross_section,
            **kwargs,
        )
        elems = route.references
        to_route = route.ports
        elements.extend(elems)

        if force_manhattan:
            """
            1) find the min x_distance between each grating port and
            each component port.
            2) If abs(min distance) < 2* bend radius
                then offset io_gratings by -min_distance
            """
            min_dist = 2 * dy + 10.0
            min_dist_threshold = 2 * dy + 1.0
            for io_gratings in io_gratings_lines:
                for gr in io_gratings:
                    for p in to_route:
                        dist = gr.x - p.x
                        if abs(dist) < abs(min_dist):
                            min_dist = dist
                if abs(min_dist) < min_dist_threshold:
                    for gr in io_gratings:
                        gr.movex(-min_dist)

        # If the array of gratings is too close, adjust its location
        gc_ports_tmp = []
        for io_gratings in io_gratings_lines:
            gc_ports_tmp += [gc.ports[gc_port_name] for gc in io_gratings]
        min_y = get_min_spacing(to_route, gc_ports_tmp, sep=sep, radius=dy)
        delta_y = abs(to_route[0].y - gc_ports_tmp[0].y)

        if min_y > delta_y:
            for io_gratings in io_gratings_lines:
                for gr in io_gratings:
                    gr.translate(0, delta_y - min_y)

        # If we add align ports, we need enough space for the bends
        end_straight_offset = (straight_separation + 5 if with_loopback else
                               x.info.get("min_length", 0.1))
        if len(io_gratings_lines) == 1:
            io_gratings = io_gratings_lines[0]
            gc_ports = [gc.ports[gc_port_name] for gc in io_gratings]
            routes = get_bundle(
                ports1=to_route,
                ports2=gc_ports,
                separation=sep,
                end_straight_offset=end_straight_offset,
                straight=straight,
                bend=bend90,
                cross_section=cross_section,
                **kwargs,
            )
            elements.extend([route.references for route in routes])

        else:
            for io_gratings in io_gratings_lines:
                gc_ports = [gc.ports[gc_port_name] for gc in io_gratings]
                nb_gc_ports = len(io_gratings)
                nb_ports_to_route = len(to_route)
                n0 = nb_ports_to_route / 2
                dn = nb_gc_ports / 2
                routes = get_bundle(
                    ports1=to_route[n0 - dn:n0 + dn],
                    ports2=gc_ports,
                    separation=sep,
                    end_straight_offset=end_straight_offset,
                    bend=bend90,
                    straight=straight,
                    radius=radius,
                    cross_section=cross_section,
                    **kwargs,
                )
                elements.extend([route.references for route in routes])
                del to_route[n0 - dn:n0 + dn]

    if with_loopback:
        gca1, gca2 = [
            grating_coupler.ref(
                position=(
                    x_c - offset + ii * fiber_spacing,
                    io_gratings_lines[-1][0].ports[gc_port_name].y,
                ),
                rotation=gc_rotation,
                port_id=gc_port_name,
            ) for ii in [grating_indices[0] - 1, grating_indices[-1] + 1]
        ]
        port0 = gca1.ports[gc_port_name]
        port1 = gca2.ports[gc_port_name]
        ports.append(port0)
        ports.append(port1)

        p0 = port0.position
        p1 = port1.position

        dy = bend90.info.dy
        dx = max(2 * dy, fiber_spacing / 2)

        gc_east = max([gci.size_info.east for gci in grating_couplers])
        y_bot_align_route = gc_east + straight_to_grating_spacing

        points = [
            p0,
            p0 + (0, dy),
            p0 + (dx, dy),
            p0 + (dx, -y_bot_align_route),
            p1 + (-dx, -y_bot_align_route),
            p1 + (-dx, dy),
            p1 + (0, dy),
            p1,
        ]
        elements.extend([gca1, gca2])

        route = round_corners(
            points=points,
            straight=straight,
            bend=bend90,
            cross_section=cross_section,
            **kwargs,
        )
        elements.extend(route.references)
        if nlabels_loopback == 1:
            io_gratings_loopback = [gca1]
            ordered_ports_loopback = [port0]
        if nlabels_loopback == 2:
            io_gratings_loopback = [gca1, gca2]
            ordered_ports_loopback = [port0, port1]
        elif nlabels_loopback > 2:
            raise ValueError(
                f"Invalid nlabels_loopback = {nlabels_loopback}, "
                "valid (0: no labels, 1: first port, 2: both ports2)")
        if nlabels_loopback > 0 and get_input_labels_function:
            elements.extend(
                get_input_labels_function(
                    io_gratings=io_gratings_loopback,
                    ordered_ports=ordered_ports_loopback,
                    component_name=component_name,
                    layer_label=layer_label_loopback or layer_label,
                    gc_port_name=gc_port_name,
                    get_input_label_text_function=
                    get_input_label_text_loopback_function,
                ))

    if get_input_labels_function:
        elements.extend(
            get_input_labels_function(
                io_gratings=io_gratings,
                ordered_ports=ordered_ports,
                component_name=component_name,
                layer_label=layer_label,
                gc_port_name=gc_port_name,
                get_input_label_text_function=get_input_label_text_function,
            ))

    return elements, io_gratings_lines, ports
Beispiel #10
0
def spiral_external_io(
    N: int = 6,
    x_inner_length_cutback: float = 300.0,
    x_inner_offset: float = 0.0,
    y_straight_inner_top: float = 0.0,
    xspacing: float = 3.0,
    yspacing: float = 3.0,
    bend: ComponentFactory = bend_euler,
    length: Optional[float] = None,
    cross_section: CrossSectionFactory = gf.cross_section.strip,
    **kwargs
) -> Component:
    """Returns a Spiral with input and output ports outside the spiral

    Args:
        N: number of loops
        x_inner_length_cutback:
        x_inner_offset:
        y_straight_inner_top:
        xspacing: center to center x-spacing
        yspacing: center to center y-spacing
        bend: function
        length: length in um, it is the approximates total length
        cross_section:
        kwargs: cross_section settings

    """

    if length:
        x_inner_length_cutback = length / (4 * (N - 1))

    y_straight_inner_top += 5
    x_inner_length_cutback += x_inner_offset
    _bend180 = bend(angle=180, cross_section=cross_section, **kwargs)
    _bend90 = bend(angle=90, cross_section=cross_section, **kwargs)

    bend_radius = _bend90.info.radius
    rx, ry = get_bend_port_distances(_bend90)
    _, rx180 = get_bend_port_distances(_bend180)  # rx180, second arg since we rotate

    component = Component()
    inner_loop_spacing = 2 * bend_radius + 5.0

    # Create manhattan path going from west grating to westest port of bend 180
    x_inner_length = x_inner_length_cutback + 5.0 + xspacing

    y_inner_bend = y_straight_inner_top - bend_radius - 5.0
    x_inner_loop = x_inner_length - 5.0
    p1 = (x_inner_loop, y_inner_bend)
    p2 = (x_inner_loop + inner_loop_spacing, y_inner_bend)

    _pt = np.array(p1)
    pts_w = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i + 1) * yspacing
        x2 = inner_loop_spacing + 2 * rx + x_inner_length + (2 * i + 1) * xspacing
        y3 = -ry - (2 * i + 2) * yspacing
        x4 = -(2 * i + 1) * xspacing
        if i == N - 1:
            x4 = x4 - rx180 + xspacing

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_w += [_pt1, _pt2, _pt3, _pt4, _pt5]

    pts_w = pts_w[:-2]

    # Create manhattan path going from east grating to eastest port of bend 180
    _pt = np.array(p2)
    pts_e = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i) * yspacing
        x2 = inner_loop_spacing + 2 * rx + x_inner_length + 2 * i * xspacing
        y3 = -ry - (2 * i + 1) * yspacing
        x4 = -2 * i * xspacing

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_e += [_pt1, _pt2, _pt3, _pt4, _pt5]

    pts_e = pts_e[:-2]

    # Join the two bits of paths and extrude the spiral geometry
    route = round_corners(
        pts_w[::-1] + pts_e,
        bend=bend,
        cross_section=cross_section,
        **kwargs,
    )

    component.add(route.references)
    component.add_port("o2", port=route.ports[0])
    component.add_port("o1", port=route.ports[1])

    length = route.length
    component.info.length = length
    return component