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
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
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
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
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, )
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
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
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