Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set up smarter routing #109

Open
reed-foster opened this issue Nov 1, 2024 · 0 comments
Open

set up smarter routing #109

reed-foster opened this issue Nov 1, 2024 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@reed-foster
Copy link
Contributor

potential starting point

def autoroute(exp_ports, pad_ports, workspace_size, exp_bbox, width, spacing, pad_offset, layer):
    """
    automatically routes an experiment to a set of pads
   
    Two step process. First step partially routes any experiment ports which are orthogonal to their
    designated pad array port so that they are facing each other.  Then, sorts pairs of ports based
    on the minimum horizontal (or vertical distance) between ports facing each other. Finally, 
    routes the sorted pairs.
    exp_ports       - list of ports in experiment geometry
    pad_ports       - list of ports to connect to pad array
    workspace_size  - side length of workspace area
    exp_bbox        - bounding box of experiment
    width           - width of traces (in microns)
    spacing         - min spacing between traces (in microns)
    pad_offset      - extra spacing from pads
    layer           - gds layer
    """

    D = Device('autorouted_traces')

    if len(exp_ports) != len(pad_ports):
        raise ValueError("invalid port lists for autorouter, lengths must match")
    num_ports = len(exp_ports)

    # find the pad with the minimum distance to experiment port 0
    # we'll connect these two and connect pairs of ports sequentially around the pad array
    # so there is no overlap
    min_dist, min_pad = max(workspace_size), -1
    for i in range(num_ports):
        norm = np.linalg.norm(exp_ports[0].center - pad_ports[i].center)
        if norm < min_dist:
            min_dist, min_pad = norm, i

    # group the pairs or ports based on whether or not they are orthogonal and which direction the
    # experiment port is facing
    pairs = [(exp_ports[i], pad_ports[(min_pad + i) % num_ports]) for i in range(num_ports)]
    # split pairs into four groups based on face of experiment (N, S, E, W)
    grouped_pairs = [[], [], [], []]
    orthogonal_pairs = [[], [], [], []]
    paths = [[], [], [], []]
    for port_pair in pairs:
        ep_n = port_pair[0].normal[1] - port_pair[0].center
        pp_n = port_pair[1].normal[1] - port_pair[1].center
        if abs(np.dot(ep_n, (1,0))) < 1e-9:
            q = 0 if np.dot(ep_n, (0,1)) > 0 else 1
        else:
            q = 2 if np.dot(ep_n, (1,0)) > 0 else 3
        if abs(np.dot(pp_n, ep_n)) < 1e-9:
            orthogonal_pairs[q].append(port_pair)
        else:
            grouped_pairs[q].append(port_pair)
            paths[q].append(None)
  
    # first create partial paths for orthogonal pairs and create new port at the end of partial path
    for q, quadrant in enumerate(orthogonal_pairs):
        # keep track of height/separation from experiment bbox on both halves of experiment face
        # halves are based on x/y coordinate of pad_p
        # since orthogonal ports are sorted based on how close they are to the edge of the bbox
        # processing them in sorted order and incrementing height appropriately will prevent collisions
        height = [0, 0]
        for port_pair in sorted(quadrant, key = lambda p: abs(p[0].x - p[1].x) if q < 2 else abs(p[0].y - p[1].y)):
            exp_p = port_pair[0]
            pad_p = port_pair[1]
            start = np.array([exp_p.x, exp_p.y])
            if q < 2:
                # select direction based on x coordinate of pad_p
                direction = 0 if pad_p.x < 0 else 1
                height[direction] += spacing
                start += (0, height[direction] if q == 0 else -height[direction])
                end = (exp_bbox[0][0] if pad_p.x < 0 else exp_bbox[1][0], start[1])
                new_q = 3 if pad_p.x < 0 else 2
            else:
                # select direction based on y coordinate of pad_p
                direction = 0 if pad_p.y < 0 else 1
                height[direction] += spacing
                start += (height[direction] if q == 2 else -height[direction], 0)
                end = (start[0], exp_bbox[0][1] if pad_p.y < 0 else exp_bbox[1][1])
                new_q = 1 if pad_p.y < 0 else 0
            path = Path((exp_p.center, start, end))
            new_port = Port(name=exp_p.name, midpoint=end, width=exp_p.width,
                        orientation=(pad_p.orientation + 180) % 360)
            grouped_pairs[new_q].append((new_port, pad_p))
            paths[new_q].append(path)
    
    # split each quadrant of pairs into sections which can be routed independently
    sectioned_pairs = [[], [], [], []]
    sectioned_paths = [[], [], [], []]
    for q, quadrant in enumerate(grouped_pairs):
        last_direction = None
        for p, port_pair in sorted(enumerate(quadrant), key = lambda a: a[1][0].x if q < 2 else a[1][0].y):
            # sorted based on exp_port x coord (-x to +x) for top and bottom sides
            # based on exp_port y coord (-y to +y) for left and right sides
            direction = np.sign(port_pair[1].x - port_pair[0].x) if q < 2 else np.sign(port_pair[1].y - port_pair[0].y)
            new_section = False
            if direction != last_direction or last_direction is None:
                new_section = True
            else:
                pad_port = port_pair[1]
                exp_port = port_pair[0]
                prev_pad_port = sectioned_pairs[q][-1][-1][1]
                prev_exp_port = sectioned_pairs[q][-1][-1][0]
                if direction == 1:
                    # moving rightwards/upwards
                    if q < 2 and exp_port.x > prev_pad_port.x + prev_pad_port.width/3:
                        new_section = True
                    elif q >= 2 and exp_port.y > prev_pad_port.y + prev_pad_port.width/3:
                        new_section = True
                    if q < 2 and pad_port.x + prev_pad_port.width/3 < prev_exp_port.x:
                        new_section = True
                    elif q >= 2 and pad_port.y + prev_pad_port.width/3 < prev_exp_port.y:
                        new_section = True
                elif direction == -1:
                    # moving leftwards/downwards
                    if q < 2 and exp_port.x < prev_pad_port.x - prev_pad_port.width/3:
                        new_section = True
                    elif q >= 2 and exp_port.y < prev_pad_port.y - prev_pad_port.width/3:
                        new_section = True
                    if q < 2 and pad_port.x - prev_pad_port.width/3 > prev_exp_port.x:
                        new_section = True
                    elif q >= 2 and pad_port.y - prev_pad_port.width/3 > prev_exp_port.y:
                        new_section = True
                else:
                    new_section = True
            if new_section:
                sectioned_pairs[q].append([])
                sectioned_paths[q].append([])
            sectioned_pairs[q][-1].append(port_pair)
            sectioned_paths[q][-1].append(paths[q][p])
            last_direction = direction

    # now all ports face each other and the automatic routing will be straightforward
    for q, quadrant in enumerate(sectioned_pairs):
        for s, section in enumerate(quadrant):
            pad_dist = pad_offset
            direction = np.sign(section[0][1].x - section[0][0].x) if q < 2 else np.sign(section[0][1].y - section[0][0].y)
            for p, port_pair in sorted(enumerate(section), key = lambda a: direction*(a[1][0].x if q < 2 else a[1][0].y)):
                exp_p = port_pair[0]
                pad_p = port_pair[1]
                if q < 2:
                    # ports are vertically aligned
                    if abs(exp_p.x - pad_p.x) < pad_p.width/3:
                        # ports are close enough to route together with a straight
                        new_path = Path((exp_p.center, (exp_p.x, pad_p.y)))
                        new_port = Port(name=pad_p.name, midpoint=(exp_p.x, pad_p.y),
                                        width=pad_p.width, orientation=(exp_p.orientation + 180) % 360)
                    else:
                        direction = np.sign(port_pair[1].x - port_pair[0].x)
                        start = np.array((exp_p.x, pad_p.y))
                        start += (0, -pad_dist if q == 0 else pad_dist)
                        if direction < 0:
                            # routing leftwards
                            end_x = max(min(pad_p.x, exp_p.x - 5*width), pad_p.x - pad_p.width/3)
                        else:
                            end_x = min(max(pad_p.x, exp_p.x + 5*width), pad_p.x + pad_p.width/3)
                        mid = np.array((end_x, start[1]))
                        end = (end_x, pad_p.y)
                        pad_dist += spacing
                        new_path = Path((exp_p.center, start, mid, end))
                        new_port = Port(name=pad_p.name, midpoint=end,
                                        width=pad_p.width, orientation=(exp_p.orientation + 180) % 360)

                else:
                    # ports are horizontally aligned
                    if abs(exp_p.y - pad_p.y) < pad_p.width/3:
                        # ports are close enough to route together with a straight
                        new_path = Path((exp_p.center, (pad_p.x, exp_p.y)))
                        new_port = Port(name=pad_p.name, midpoint=(pad_p.x, exp_p.y),
                                        width=pad_p.width, orientation=(exp_p.orientation + 180) % 360)
                    else:
                        direction = np.sign(port_pair[1].y - port_pair[0].y)
                        start = np.array((pad_p.x, exp_p.y))
                        start += (-pad_dist if q == 2 else pad_dist, 0)
                        if direction < 0:
                            # routing leftwards
                            end_y = max(min(pad_p.y, exp_p.y - 5*width), pad_p.y - pad_p.width/3)
                        else:
                            end_y = min(max(pad_p.y, exp_p.y + 5*width), pad_p.y + pad_p.width/3)
                        mid = np.array((start[0], end_y))
                        end = (pad_p.x, end_y)
                        pad_dist += spacing
                        new_path = Path((exp_p.center, start, mid, end))
                        new_port = Port(name=pad_p.name, midpoint=end,
                                        width=pad_p.width, orientation=(exp_p.orientation + 180) % 360)
                if sectioned_paths[q][s][p] is not None:
                    sectioned_paths[q][s][p].append(new_path)
                else:
                    sectioned_paths[q][s][p] = new_path
                sectioned_pairs[q][s][p] = (exp_p, new_port)
    # perform routing along path and add final ports to connect to pad array
    for q, quadrant in enumerate(sectioned_pairs):
        for s, section in enumerate(quadrant):
            for p, port_pair in enumerate(section):
                try:
                    route = D << pr.route_smooth(port1=port_pair[0], port2=port_pair[1], radius=1.5*width, width=width,
                                                 path_type='manual', manual_path=sectioned_paths[q][s][p], layer=layer)
                except ValueError as e:
                    traceback.print_exc()
                    print('An error occurred with phidl.routing.route_smooth(), try increasing the size of the workspace')
                    print(sectioned_paths[q][s][p].points)
                    raise ValueError(e)
                # add taper
                pad_p = pad_ports[port_pair[1].name]
                final_w = port_pair[1].width + 2*(abs(route.ports[2].x - pad_p.x) + abs(route.ports[2].y - pad_p.y))
                ht = Device("chopped_hyper_taper")
                taper = ht << qg.hyper_taper(length=2*width, wide_section=final_w + width, narrow_section=width, layer=layer)
                cut = ht << pg.straight(size=(pad_p.width + width, 2*width))
                conn = ht << pg.connector(width=pad_p.width)
                taper.connect(taper.ports[1], route.ports[2])
                conn.connect(conn.ports[1], pad_p)
                cut.connect(cut.ports[1], conn.ports[1])
                D << pg.boolean(A=taper, B=cut, operation='and', precision=1e-6, layer=layer)
    D = pg.union(D)
    # create ports in final device so pg.outline doesn't block them
    for n, port in enumerate(exp_ports):
        D.add_port(name=n, midpoint=port.midpoint, width=port.width,
                   orientation=(port.orientation + 180) % 360)
    for n, port in enumerate(pad_ports):
        o = port.orientation
        dx = -2*width if o == 0 else 2*width if o == 180 else 0
        dy = -2*width if o == 90 else 2*width if o == 270 else 0
        D.add_port(name=num_ports + n, midpoint=(port.x + dx, port.y + dy), width=port.width + width,
                   orientation=(port.orientation + 180) % 360)
    return D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant