def test_facing_ports( data_regression: DataRegressionFixture, check: bool = True, ): dy = 200.0 xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650] pitch = 10.0 N = len(xs1) xs2 = [-20 + i * pitch for i in range(N // 2)] xs2 += [400 + i * pitch for i in range(N // 2)] a1 = 90 a2 = a1 + 180 ports1 = [Port(f"top_{i}", (xs1[i], 0), 0.5, a1) for i in range(N)] ports2 = [Port(f"bottom_{i}", (xs2[i], dy), 0.5, a2) for i in range(N)] c = gf.Component("test_facing_ports") routes = get_bundle(ports1, ports2) lengths = {} for i, route in enumerate(routes): c.add(route.references) lengths[i] = route.length if check: data_regression.check(lengths) difftest(c) return c
def test_get_bundle_u_indirect(data_regression: DataRegressionFixture, angle, check: bool = True, dy=-200): xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240] axis = "X" if angle in [0, 180] else "Y" pitch = 10.0 N = len(xs1) xs2 = [50 + i * pitch for i in range(N)] a1 = angle a2 = a1 + 180 if axis == "X": ports1 = [ Port("top_{}".format(i), (0, xs1[i]), 0.5, a1) for i in range(N) ] ports2 = [ Port("bottom_{}".format(i), (dy, xs2[i]), 0.5, a2) for i in range(N) ] else: ports1 = [ Port("top_{}".format(i), (xs1[i], 0), 0.5, a1) for i in range(N) ] ports2 = [ Port("bottom_{}".format(i), (xs2[i], dy), 0.5, a2) for i in range(N) ] c = gf.Component(f"test_get_bundle_u_indirect_{angle}_{dy}") routes = get_bundle(ports1, ports2, bend=gf.components.bend_circular) lengths = {} for i, route in enumerate(routes): c.add(route.references) lengths[i] = route.length if check: data_regression.check(lengths) difftest(c) return c
def add_electrical_pads_top_dc( component: Component, spacing: Float2 = (0.0, 100.0), pad_array: ComponentFactory = pad_array_function, select_ports: Callable = select_ports_electrical, **kwargs, ) -> Component: """connects component electrical ports with pad array at the top Args: component: spacing: component to pad spacing pad_array: select_ports: function to select_ports **kwargs: route settings """ c = Component() cref = c << component ports = select_ports(cref.ports) ports_component = list(ports.values()) ports_component = [port.copy() for port in ports_component] for port in ports_component: port.orientation = 90 pads = c << pad_array(columns=len(ports)) pads.x = cref.x + spacing[0] pads.ymin = cref.ymax + spacing[1] ports_pads = list(pads.ports.values()) ports_component = sort_ports_x(ports_component) ports_pads = sort_ports_x(ports_pads) routes = get_bundle(ports_component, ports_pads, bend=wire_corner, **kwargs) for route in routes: c.add(route.references) c.add_ports(cref.ports) for port in ports_component: c.ports.pop(port.name) c.copy_child_info(component) return c
def test_get_bundle_udirect(data_regression: DataRegressionFixture, check: bool = True, dy=200, angle=270): xs1 = [-100, -90, -80, -55, -35, 24, 0] + [200, 210, 240] axis = "X" if angle in [0, 180] else "Y" pitch = 10.0 N = len(xs1) xs2 = [70 + i * pitch for i in range(N)] if axis == "X": ports1 = [Port(f"top_{i}", (0, xs1[i]), 0.5, angle) for i in range(N)] ports2 = [ Port(f"bottom_{i}", (dy, xs2[i]), 0.5, angle) for i in range(N) ] else: ports1 = [Port(f"top_{i}", (xs1[i], 0), 0.5, angle) for i in range(N)] ports2 = [ Port(f"bottom_{i}", (xs2[i], dy), 0.5, angle) for i in range(N) ] c = gf.Component(name="test_get_bundle_udirect") routes = get_bundle(ports1, ports2, bend=gf.components.bend_circular) lengths = {} for i, route in enumerate(routes): c.add(route.references) lengths[i] = route.length if check: data_regression.check(lengths) difftest(c) return c
def test_get_bundle_u_indirect(angle=0): """ FIXME: start_straight_length is getting ignored in this case """ dy = -200 xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240] axis = "X" if angle in [0, 180] else "Y" pitch = 10.0 N = len(xs1) xs2 = [50 + i * pitch for i in range(N)] a1 = angle a2 = a1 + 180 if axis == "X": ports1 = [Port("top_{}".format(i), (0, xs1[i]), 0.5, a1) for i in range(N)] ports2 = [Port("bot_{}".format(i), (dy, xs2[i]), 0.5, a2) for i in range(N)] else: ports1 = [Port("top_{}".format(i), (xs1[i], 0), 0.5, a1) for i in range(N)] ports2 = [Port("bot_{}".format(i), (xs2[i], dy), 0.5, a2) for i in range(N)] c = gf.Component(f"test_get_bundle_u_indirect_{angle}_{dy}") routes = get_bundle( ports1, ports2, bend=gf.components.bend_circular, end_straight_length=5, start_straight_length=1, ) for route in routes: c.add(route.references) return c
def test_get_bundle(data_regression: DataRegressionFixture, check: bool = True): 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(f"top_{i}", (xs_top[i], 0), 0.5, 270) for i in range(N)] bottom_ports = [ Port(f"bottom_{i}", (xs_bottom[i], -400), 0.5, 90) for i in range(N) ] c = gf.Component("test_get_bundle") routes = get_bundle(top_ports, bottom_ports) lengths = {} for i, route in enumerate(routes): c.add(route.references) lengths[i] = route.length if check: data_regression.check(lengths) difftest(c) return c
def test_connect_corner(config: str, data_regression: DataRegressionFixture, check: bool = True, N=6) -> Component: d = 10.0 sep = 5.0 c = Component(name=f"test_connect_corner_{config}") if config in ["A", "B"]: a = 100.0 ports_A_TR = [ Port("A_TR_{}".format(i), (d, a / 2 + i * sep), 0.5, 0) for i in range(N) ] ports_A_TL = [ Port("A_TL_{}".format(i), (-d, a / 2 + i * sep), 0.5, 180) for i in range(N) ] ports_A_BR = [ Port("A_BR_{}".format(i), (d, -a / 2 - i * sep), 0.5, 0) for i in range(N) ] ports_A_BL = [ Port("A_BL_{}".format(i), (-d, -a / 2 - i * sep), 0.5, 180) for i in range(N) ] ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL] ports_B_TR = [ Port("B_TR_{}".format(i), (a / 2 + i * sep, d), 0.5, 90) for i in range(N) ] ports_B_TL = [ Port("B_TL_{}".format(i), (-a / 2 - i * sep, d), 0.5, 90) for i in range(N) ] ports_B_BR = [ Port("B_BR_{}".format(i), (a / 2 + i * sep, -d), 0.5, 270) for i in range(N) ] ports_B_BL = [ Port("B_BL_{}".format(i), (-a / 2 - i * sep, -d), 0.5, 270) for i in range(N) ] ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL] elif config in ["C", "D"]: a = N * sep + 2 * d ports_A_TR = [ Port("A_TR_{}".format(i), (a, d + i * sep), 0.5, 0) for i in range(N) ] ports_A_TL = [ Port("A_TL_{}".format(i), (-a, d + i * sep), 0.5, 180) for i in range(N) ] ports_A_BR = [ Port("A_BR_{}".format(i), (a, -d - i * sep), 0.5, 0) for i in range(N) ] ports_A_BL = [ Port("A_BL_{}".format(i), (-a, -d - i * sep), 0.5, 180) for i in range(N) ] ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL] ports_B_TR = [ Port("B_TR_{}".format(i), (d + i * sep, a), 0.5, 90) for i in range(N) ] ports_B_TL = [ Port("B_TL_{}".format(i), (-d - i * sep, a), 0.5, 90) for i in range(N) ] ports_B_BR = [ Port("B_BR_{}".format(i), (d + i * sep, -a), 0.5, 270) for i in range(N) ] ports_B_BL = [ Port("B_BL_{}".format(i), (-d - i * sep, -a), 0.5, 270) for i in range(N) ] ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL] lengths = {} i = 0 if config in ["A", "C"]: for ports1, ports2 in zip(ports_A, ports_B): routes = get_bundle(ports1, ports2) for route in routes: c.add(route.references) lengths[i] = route.length i += 1 elif config in ["B", "D"]: for ports1, ports2 in zip(ports_A, ports_B): routes = get_bundle(ports2, ports1) for route in routes: c.add(route.references) lengths[i] = route.length i += 1 if check: data_regression.check(lengths) difftest(c) return c
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