def add_electrical_pads_top(component: Component, **kwargs) -> Component: """connects component electrical ports with pad array at the top Args: component: pad: pad element spacing: pad array (x, y) spacing width: pad width height: pad height layer: pad layer """ c = Component(f"{component.name}_e") ports = component.get_ports_list(port_type="dc") c << component pads = c << pad_array(n=len(ports), port_list=["S"], **kwargs) pads.x = component.x pads.y = component.ymax + 100 ports_pads = list(pads.ports.values()) for p1, p2 in zip(ports_pads, ports): c.add(connect_electrical_shortest_path(p1, p2)) c.ports = component.ports for port in ports: c.ports.pop(port.name) return c
def add_labels( component: Component, port_type: str = "dc", get_label_function: Callable = get_input_label_electrical, layer_label: Layer = pp.LAYER.LABEL, gc: Optional[Component] = None, ) -> Component: """Add labels a particular type of ports Args: component: to add labels to port_type: type of port ('dc', 'optical', 'electrical') get_label_function: function to get label layer_label: layer_label Returns: original component with labels """ ports = component.get_ports_list(port_type=port_type) for i, port in enumerate(ports): label = get_label_function( port=port, gc=gc, gc_index=i, component_name=component.name, layer_label=layer_label, ) component.add(label) return component
def add_electrical_pads_top( component: Component, component_top_to_pad_bottom_distance: float = 100.0, route_filter=connect_elec_waypoints, **kwargs, ) -> Component: """connects component electrical ports with pad array at the top Args: component: pad: pad element spacing: pad array (x, y) spacing width: pad width height: pad height layer: pad layer """ c = Component(f"{component.name}_e") ports = component.get_ports_list(port_type="dc") # for port in ports: # print(port.name) # print(len(ports)) c << component pads = c << pad_array(n=len(ports), port_list=["S"], **kwargs) pads.x = component.x pads.ymin = component.ymax + component_top_to_pad_bottom_distance ports_pads = list(pads.ports.values()) ports_pads.sort(key=lambda p: p.x) ports.sort(key=lambda p: p.x) for p1, p2 in zip(ports_pads, ports): c.add(connect_electrical_shortest_path(p1, p2)) c.ports = component.ports.copy() for port in ports: c.ports.pop(port.name) return c
def add_fiber_single( component: Component, grating_coupler: Callable = grating_coupler_te, layer_label: Tuple[int, int] = LAYER.LABEL, optical_io_spacing: int = 50, bend_factory: Callable = bend_circular, straight_factory: Callable = waveguide, taper_factory: Callable = taper, taper_length: float = 10.0, route_filter: Callable = connect_strip_way_points, min_input2output_spacing: int = 127, optical_routing_type: int = 2, with_align_ports: bool = True, component_name: Optional[str] = None, gc_port_name: str = "W0", **kwargs, ) -> Component: r"""returns component with grating ports and labels on each port can add align_ports reference structure Args: component: to connect grating_coupler: grating coupler instance, function or list of functions layer_label: LAYER.LABEL optical_io_spacing: SPACING_GC 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_method: connect_strip gc_port_name: W0 optical_routing_type: None: autoselection, 0: no extension gc_rotation: -90 component_name: name of component taper_factory: taper .. code:: fiber ______ /| | | / | | | W0| | | | \ | | | | \|_|_|_ | xmin = 0 .. plot:: :include-source: import pp from pp.routing import add_fiber_array c = pp.c.crossing() cc = add_fiber_array(c) pp.plotgds(cc) """ component = component() if callable(component) else component gc = grating_coupler = (grating_coupler() if callable(grating_coupler) else grating_coupler) gc_port_to_edge = abs(gc.xmax - gc.ports[gc_port_name].midpoint[0]) port_width_gc = grating_coupler.ports[gc_port_name].width optical_ports = component.get_ports_list(port_type="optical") port_width_component = optical_ports[0].width if port_width_component != port_width_gc: component = add_tapers( component, taper_factory(length=taper_length, width1=port_width_gc, width2=port_width_component), ) component_name = component_name or component.name name = f"{component_name}_{grating_coupler.name}" elements, grating_couplers = route_fiber_single( component, component_name=component_name, optical_io_spacing=optical_io_spacing, bend_factory=bend_factory, straight_factory=straight_factory, route_filter=route_filter, grating_coupler=grating_coupler, layer_label=layer_label, optical_routing_type=optical_routing_type, min_input2output_spacing=min_input2output_spacing, gc_port_name=gc_port_name, **kwargs, ) c = Component(name=name) cr = c << component cr.rotate(90) for e in elements: c.add(e) for gc in grating_couplers: c.add(gc) for pname, p in component.ports.items(): if p.port_type != "optical": c.add_port(pname, port=p) if isinstance(grating_coupler, list): grating_couplers = [call_if_func(g) for g in grating_coupler] grating_coupler = grating_couplers[0] else: grating_coupler = call_if_func(grating_coupler) grating_couplers = [grating_coupler] if with_align_ports: length = c.ysize - 2 * gc_port_to_edge wg = c << straight_factory(length=length) wg.rotate(90) wg.xmax = (c.xmin - optical_io_spacing if abs(c.xmin) > abs(optical_io_spacing) else c.xmin - optical_io_spacing) wg.ymin = c.ymin + gc_port_to_edge gci = c << grating_coupler gco = c << grating_coupler gci.connect(gc_port_name, wg.ports["W0"]) gco.connect(gc_port_name, wg.ports["E0"]) gds_layer_label, gds_datatype_label = pd._parse_layer(layer_label) port = wg.ports["E0"] text = get_optical_text(port, grating_coupler, 0, component_name=f"loopback_{component.name}") label = pd.Label( text=text, position=port.midpoint, anchor="o", layer=gds_layer_label, texttype=gds_datatype_label, ) c.add(label) port = wg.ports["W0"] text = get_optical_text(port, grating_coupler, 1, component_name=f"loopback_{component.name}") label = pd.Label( text=text, position=port.midpoint, anchor="o", layer=gds_layer_label, texttype=gds_datatype_label, ) c.add(label) return c
def route_fiber_single( component: Component, optical_io_spacing: int = 50, grating_coupler: Component = grating_coupler_te, min_input2output_spacing: int = 230, optical_routing_type: int = 1, optical_port_labels: Optional[List[str]] = None, excluded_ports: Optional[List[str]] = None, **kwargs ) -> Tuple[List[Union[ComponentReference, Label]], List[ComponentReference]]: """Returns routes with grating couplers for single fiber input/output. Args: component: to add grating couplers optical_io_spacing: between grating couplers grating_coupler: min_input2output_spacing: so opposite fibers do not touch optical_routing_type: 0, 1, 2 optical_port_labels: port labels that need connection excluded_ports: ports excluded from routing Returns: elements: list of routes ComponentReference grating_couplers: list of grating_couplers ComponentReferences """ component = component.copy() component_copy = component.copy() if optical_port_labels is None: optical_ports = component.get_ports_list(port_type="optical") else: optical_ports = [component.ports[lbl] for lbl in optical_port_labels] excluded_ports = excluded_ports or [] optical_ports = [p for p in optical_ports if p.name not in excluded_ports] N = len(optical_ports) if isinstance(grating_coupler, list): grating_couplers = [pp.call_if_func(g) for g in grating_coupler] grating_coupler = grating_couplers[0] else: grating_coupler = pp.call_if_func(grating_coupler) grating_couplers = [grating_coupler] * N gc_port2center = getattr(grating_coupler, "port2center", grating_coupler.xsize / 2) if component.xsize + 2 * gc_port2center < min_input2output_spacing: fanout_length = (pp.drc.snap_to_grid( min_input2output_spacing - component.xsize - 2 * gc_port2center, 10) / 2) else: fanout_length = None """ _________ | |_E1 W0_| | | |_E0 |_________| rotate +90 deg and route West ports to South E1 E0 _|___|_ | | | | | | | | | | | | |_______| | W0 """ # route west ports to south component = component.rotate(90) west_ports = component.get_ports_dict(prefix="W") north_ports = { p.name: p for p in component.ports.values() if not p.name.startswith("W") } component.ports = west_ports elements_south, gratings_south, _ = route_fiber_array( component=component, with_align_ports=False, optical_io_spacing=optical_io_spacing, fanout_length=fanout_length, grating_coupler=grating_couplers[0], optical_routing_type=optical_routing_type, **kwargs) # route north ports component = component_copy.rotate(-90) north_ports = { p.name: p for p in component.ports.values() if not p.name.startswith("W") } component.ports = north_ports elements_north, gratings_north, _ = route_fiber_array( component=component, with_align_ports=False, optical_io_spacing=optical_io_spacing, fanout_length=fanout_length, grating_coupler=grating_couplers[1:], **kwargs) for e in elements_north: elements_south.append(e.rotate(180)) if len(gratings_north) > 0: for io in gratings_north[0]: gratings_south.append(io.rotate(180)) return elements_south, gratings_south
def route_south( component: Component, bend_radius: float = conf.tech.bend_radius, optical_routing_type: int = 1, excluded_ports: List[str] = None, waveguide_separation: float = 4.0, io_gratings_lines: Optional[List[List[ComponentReference]]] = None, route_filter: Callable = connect_strip_way_points, gc_port_name: str = "E0", ) -> Union[Tuple[List[Any], List[Port]], Tuple[List[ComponentReference], List[Port]]]: """ Args: component: component to route bend_radius optical_routing_type: routing heuristic `1` or `2` `1` uses the component size info to estimate the box size. `2` only looks at the optical port positions to estimate the size excluded_ports=[]: list of port names to NOT route waveguide_separation io_gratings_lines: list of ports to which the ports produced by this function will be connected. Supplying this information helps avoiding waveguide collisions routing_method: routing method to connect the waveguides gc_port_name: grating port name Returns: list of elements, list of ports Works well if the component looks rougly like a rectangular box with north ports on the north of the box south ports on the south of the box east ports on the east of the box west ports on the west of the box """ excluded_ports = excluded_ports or [] assert optical_routing_type in [ 1, 2, ], f"optical_routing_type = {optical_routing_type}, not supported " optical_ports = component.get_ports_list(port_type="optical") optical_ports = [p for p in optical_ports if p.name not in excluded_ports] csi = component.size_info elements = [] # Handle empty list gracefully if not optical_ports: return [], [] conn_params = {"bend_radius": bend_radius} route_filter_params = { "bend_radius": bend_radius, "wg_width": optical_ports[0].width, } def routing_method(p1, p2, **kwargs): way_points = get_waypoints_connect_strip(p1, p2, **kwargs) return route_filter(way_points, **route_filter_params) # Used to avoid crossing between waveguides in special cases # This could happen when abs(x_port - x_grating) <= 2 * bend_radius delta_gr_min = 2 * bend_radius + 1 sep = waveguide_separation # Get lists of optical ports by orientation direction_ports = direction_ports_from_list_ports(optical_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 def get_index_port_closest_to_x(x, list_ports): return np.array([abs(x - p.ports[gc_port_name].x) for p in list_ports]).argmin() def gen_port_from_port(x, y, p): new_p = pd.Port(name=p.name, midpoint=(x, y), orientation=90.0, width=p.width) return new_p R = bend_radius west_ports.reverse() y0 = min([p.y for p in ordered_ports]) - R - 0.5 ports_to_route = [] i = 0 optical_xs_tmp = [p.x for p in ordered_ports] x_optical_min = min(optical_xs_tmp) x_optical_max = max(optical_xs_tmp) """ ``x`` is the x-coord of the waypoint where the current component port is connected. x starts as close as possible to the component. For each new port, the distance is increased by the separation. The starting x depends on the heuristic chosen : ``1`` or ``2`` """ # Set starting ``x`` on the west side if optical_routing_type == 1: # use component size to know how far to route x = csi.west - R - 1 elif optical_routing_type == 2: # use optical port to know how far to route x = x_optical_min - R - 1 else: raise ValueError("Invalid optical routing type") # First route the ports facing west for p in west_ports: """ In case we have to connect these ports to a line of grating, Ensure that the port is aligned with the grating port or has enough space for manhattan routing (at least two bend radius) """ if io_gratings_lines: i_grating = get_index_port_closest_to_x(x, io_gratings_lines[-1]) x_gr = io_gratings_lines[-1][i_grating].ports[gc_port_name].x if abs(x - x_gr) < delta_gr_min: if x > x_gr: x = x_gr elif x < x_gr: x = x_gr - delta_gr_min tmp_port = gen_port_from_port(x, y0, p) ports_to_route.append(tmp_port) elements += [routing_method(p, tmp_port, **conn_params)] x -= sep i += 1 start_straight = 0.5 # First-half of north ports # This ensures that north ports are routed above the top west one north_start.reverse() # We need them from left to right if len(north_start) > 0: y_max = max([p.y for p in west_ports + north_start]) for p in north_start: tmp_port = gen_port_from_port(x, y0, p) elements += [ routing_method( p, tmp_port, start_straight=start_straight + y_max - p.y, **conn_params, ) ] ports_to_route.append(tmp_port) x -= sep start_straight += sep # Set starting ``x`` on the east side if optical_routing_type == 1: # use component size to know how far to route x = csi.east + R + 1 elif optical_routing_type == 2: # use optical port to know how far to route x = x_optical_max + R + 1 else: raise ValueError( f"Invalid optical routing type. Got {optical_routing_type}, only (1, 2 supported) " ) i = 0 # Route the east ports start_straight = 0.5 for p in east_ports: """ In case we have to connect these ports to a line of grating, Ensure that the port is aligned with the grating port or has enough space for manhattan routing (at least two bend radius) """ if io_gratings_lines: i_grating = get_index_port_closest_to_x(x, io_gratings_lines[-1]) x_gr = io_gratings_lines[-1][i_grating].ports[gc_port_name].x if abs(x - x_gr) < delta_gr_min: if x < x_gr: x = x_gr elif x > x_gr: x = x_gr + delta_gr_min tmp_port = gen_port_from_port(x, y0, p) elements += [ routing_method(p, tmp_port, start_straight=start_straight, **conn_params) ] ports_to_route.append(tmp_port) x += sep i += 1 # Route the remaining north ports start_straight = 0.5 if len(north_finish) > 0: y_max = max([p.y for p in east_ports + north_finish]) for p in north_finish: tmp_port = gen_port_from_port(x, y0, p) ports_to_route.append(tmp_port) elements += [ routing_method( p, tmp_port, start_straight=start_straight + y_max - p.y, **conn_params, ) ] x += sep start_straight += sep # Add south ports ports = [flip(p) for p in ports_to_route] + south_ports return elements, ports