def pad( size: Tuple[float, float] = (100.0, 100.0), layer: Layer = LAYER.M3, layers_cladding: Optional[Tuple[Layer, ...]] = None, cladding_offsets: Optional[Tuple[float, ...]] = None, ) -> Component: """Rectangular pad with 4 ports (1, 2, 3, 4) Args: width: pad width height: pad height layer: pad layer layers_cladding: cladding_offsets: """ c = Component() rect = compass(size=size, layer=layer) c_ref = c.add_ref(rect) c.add_ports(c_ref.ports) c.info.size = (float(size[0]), float(size[1])) c.info.layer = layer if layers_cladding and cladding_offsets: for layer, cladding_offset in zip(layers_cladding, cladding_offsets): c.add_ref( compass( size=(size[0] + 2 * cladding_offset, size[1] + 2 * cladding_offset), layer=layer, )) return c
def fiber_array( n: int = 8, pitch: float = 127.0, core_diameter: float = 10, cladding_diameter: float = 125, layer_core: Tuple[int, int] = gf.LAYER.WG, layer_cladding: Tuple[int, int] = gf.LAYER.WGCLAD, ) -> Component: """Returns a fiber array .. code:: pitch <-> _________ | | lid | o o o o | | | base |_________| length """ c = Component() for i in range(n): core = c.add_ref(circle(radius=core_diameter / 2, layer=layer_core)) cladding = c.add_ref( circle(radius=cladding_diameter / 2, layer=layer_cladding)) core.movex(i * pitch) cladding.movex(i * pitch) c.add_port(name=f"F{i}", width=core_diameter, orientation=0) return c
def pads_shorted( pad: ComponentFactory = pad_function, columns: int = 8, pad_spacing: float = 150.0, layer_metal: Tuple[int, int] = LAYER.M3, metal_width: float = 10, ) -> Component: """Returns a 1D array of shorted_pads Args: pad: pad function columns: number of columns pad_spacing: layer_metal: for the short metal_width: for the short """ c = Component(name="shorted_pads") pad = pad() for i in range(columns): pad_ref = c.add_ref(pad) pad_ref.movex(i * pad_spacing - columns / 2 * pad_spacing + pad_spacing / 2) short = rectangle( size=(pad_spacing * (columns - 1), metal_width), layer=layer_metal, centered=True, ) c.add_ref(short) return c
def invert( elements, border: float = 10.0, precision: float = 1e-4, num_divisions: Union[int, Int2] = (1, 1), max_points: int = 4000, layer: Layer = (1, 0), ): """Creates an inverted version of the input shapes with an additional border around the edges. adapted from phidl.geometry.invert Args: elements : Component(/Reference), list of Component(/Reference), or Polygon A Component containing the polygons to invert. border : int or float Size of the border around the inverted shape (border value is the distance from the edges of the boundary box defining the inverted shape to the border, and is applied to all 4 sides of the shape). precision : float Desired precision for rounding vertex coordinates. num_divisions : array-like[2] of int The number of divisions with which the geometry is divided into multiple rectangular regions. This allows for each region to be processed sequentially, which is more computationally efficient. max_points : int The maximum number of vertices within the resulting polygon. layer : int, array-like[2], or set Specific layer(s) to put polygon geometry on. Returns D: A Component containing the inverted version of the input shape(s) and the corresponding border(s). """ Temp = Component() if type(elements) is not list: elements = [elements] for e in elements: if isinstance(e, Component): Temp.add_ref(e) else: Temp.add(e) gds_layer, gds_datatype = pg._parse_layer(layer) # Build the rectangle around the Component D R = gf.c.rectangle(size=(Temp.xsize + 2 * border, Temp.ysize + 2 * border), centered=True) R.center = Temp.center D = boolean( A=R, B=Temp, operation="A-B", precision=precision, num_divisions=num_divisions, max_points=max_points, layer=layer, ) return D
def cdsem_uturn( width: float = 0.5, radius: float = 10.0, wg_length: float = LINE_LENGTH, straight: ComponentFactory = straight, bend90: ComponentFactory = bend_circular, layer: Tuple[int, int] = LAYER.WG, layers_cladding: List[Tuple[int, int]] = None, cross_section: CrossSectionFactory = strip, pixel_size: float = 1.0, ) -> Component: """ Args: width: of the line cladding_offset: radius: bend radius wg_length """ c = Component() r = radius cross_section = partial(cross_section, width=width, layer=layer) if wg_length is None: wg_length = 2 * r bend90 = bend90(cross_section=cross_section, radius=r) wg = straight( cross_section=cross_section, length=wg_length, ) # Add the U-turn on straight layer b1 = c.add_ref(bend90) b2 = c.add_ref(bend90) b2.connect("o2", b1.ports["o1"]) wg1 = c.add_ref(wg) wg1.connect("o1", b1.ports["o2"]) wg2 = c.add_ref(wg) wg2.connect("o1", b2.ports["o1"]) label = c << manhattan_text( text=str(int(width * 1e3)), size=pixel_size, layer=layer) label.ymax = b2.ymin - 5 label.x = 0 b1.rotate(90) b2.rotate(90) wg1.rotate(90) wg2.rotate(90) label.rotate(90) return c
def fiber( core_diameter: float = 10, cladding_diameter: float = 125, layer_core: Tuple[int, int] = gf.LAYER.WG, layer_cladding: Tuple[int, int] = gf.LAYER.WGCLAD, ) -> Component: """Returns a fiber.""" c = Component() c.add_ref(circle(radius=core_diameter / 2, layer=layer_core)) c.add_ref(circle(radius=cladding_diameter / 2, layer=layer_cladding)) c.add_port(name="F0", width=core_diameter, orientation=0) return c
def cdsem_bend180( width: float = 0.5, radius: float = 10.0, wg_length: float = LINE_LENGTH, straight: ComponentFactory = straight_function, bend90: ComponentFactory = bend_circular, cross_section: CrossSectionFactory = strip, text: ComponentFactory = text_rectangular_mini, ) -> Component: """ Args: width: of the line cladding_offset: radius: bend radius wg_length """ c = Component() r = radius cross_section = partial(cross_section, width=width) if wg_length is None: wg_length = 2 * r bend90 = bend90(cross_section=cross_section, radius=r) wg = straight( cross_section=cross_section, length=wg_length, ) # Add the U-turn on straight layer b1 = c.add_ref(bend90) b2 = c.add_ref(bend90) b2.connect("o2", b1.ports["o1"]) wg1 = c.add_ref(wg) wg1.connect("o1", b1.ports["o2"]) wg2 = c.add_ref(wg) wg2.connect("o1", b2.ports["o1"]) label = c << text(text=str(int(width * 1e3))) label.ymax = b2.ymin - 5 label.x = 0 b1.rotate(90) b2.rotate(90) wg1.rotate(90) wg2.rotate(90) label.rotate(90) return c
def awg( arms: int = 10, outputs: int = 3, free_propagation_region_input_function=free_propagation_region_input, free_propagation_region_output_function=free_propagation_region_output, fpr_spacing: float = 50.0, ) -> Component: """Returns a basic Arrayed Waveguide grating. Args: arms: number of arms outputs: number of outputs free_propagation_region_input_function: for input free_propagation_region_output_function: for output fpr_spacing: x separation between input/output FPR """ c = Component() fpr_in = free_propagation_region_input_function( inputs=1, outputs=arms, ) fpr_out = free_propagation_region_output_function( inputs=outputs, outputs=arms, ) fpr_in_ref = c.add_ref(fpr_in) fpr_out_ref = c.add_ref(fpr_out) fpr_in_ref.rotate(90) fpr_out_ref.rotate(90) fpr_out_ref.x += fpr_spacing routes = gf.routing.get_bundle( fpr_in_ref.get_ports_list(prefix="E"), fpr_out_ref.get_ports_list(prefix="E") ) c.lengths = [] for route in routes: c.add(route.references) c.lengths.append(route.length) c.add_port("o1", port=fpr_in_ref.ports["o1"]) for i, port in enumerate(fpr_out_ref.get_ports_list(prefix="W")): c.add_port(f"E{i}", port=port) c.delta_length = np.mean(np.diff(c.lengths)) return c
def add_grating_couplers( component: Component, grating_coupler: ComponentFactory = grating_coupler_te, layer_label: Tuple[int, int] = (200, 0), gc_port_name: str = "o1", get_input_labels_function: Callable[..., List[Label]] = get_input_labels, select_ports: Callable = select_ports_optical, component_name: Optional[str] = None, ) -> Component: """Returns new component with grating couplers and labels. Args: component: to add grating_couplers grating_coupler: grating_coupler function layer_label: for label gc_port_name: where to add label get_input_labels_function: function to get label select_ports: for selecting optical_ports """ c = Component() c.component = component component_name = component_name or component.info_child.name c.add_ref(component) grating_coupler = ( grating_coupler() if callable(grating_coupler) else grating_coupler ) io_gratings = [] optical_ports = select_ports(component.ports) optical_ports = list(optical_ports.values()) for port in optical_ports: gc_ref = grating_coupler.ref() gc_port = gc_ref.ports[gc_port_name] gc_ref.connect(gc_port, port) io_gratings.append(gc_ref) c.add(gc_ref) labels = get_input_labels_function( io_gratings, list(component.ports.values()), component_name=component_name, layer_label=layer_label, gc_port_name=gc_port_name, ) c.add(labels) c.copy_child_info(component) return c
def add_text( component: ComponentOrFactory, text: str = "", text_offset: Float2 = (0, 0), text_anchor: Anchor = "cc", text_factory: ComponentFactory = text_rectangular_multi_layer, ) -> Component: """Returns component inside a new component with text geometry. Args: component: text: text string. text_offset: relative to component anchor. Defaults to center (cc). text_anchor: relative to component (ce cw nc ne nw sc se sw center cc). text_factory: function to add text labels. """ component = component() if callable(component) else component component_new = Component() component_new.component = component ref = component_new.add_ref(component) t = component_new << text_factory(text) t.move((np.array(text_offset) + getattr(ref.size_info, text_anchor))) component_new.add_ports(ref.ports) component_new.copy_child_info(component) return component_new
def straight_array(n: int = 4, spacing: float = 4.0, straigth: ComponentOrFactory = straight_function, **kwargs) -> Component: """Array of straights connected with grating couplers. useful to align the 4 corners of the chip Args: n: number of straights spacing: edge to edge straight spacing straigth: straigth straight Component or library **kwargs """ c = Component() wg = straigth(**kwargs) if callable(straigth) else straigth for i in range(n): wref = c.add_ref(wg) wref.y += i * (spacing + wg.info.width) c.add_ports(wref.ports, prefix=str(i)) c.auto_rename_ports() return c
def gdsdiff( component1: Union[Path, Component, str], component2: Union[Path, Component, str], name: str = "TOP", xor: bool = True, ) -> Component: """Compare two Components. Args: component1: Component or path to gds file component2: Component or path to gds file name: name of the top cell xor: makes boolean operation Returns: Component with both cells (xor, common and diffs) """ if isinstance(component1, pathlib.Path): component1 = str(component1) if isinstance(component2, pathlib.Path): component2 = str(component2) if isinstance(component1, str): component1 = import_gds(component1, flatten=True) if isinstance(component2, str): component2 = import_gds(component2, flatten=True) top = Component(name=f"{name}_diffs") if component1.name.startswith("Unnamed"): component1.name = f"{name}_old" if component2.name.startswith("Unnamed"): component2.name = f"{name}_new" ref1 = top << component1 ref2 = top << component2 ref1.xmin = 0 ref1.ymin = 0 ref2.xmin = 0 ref2.ymin = 0 if xor: diff = xor_polygons(ref1, ref2, hash_geometry=False) diff.name = f"{name}_xor" top.add_ref(diff) return top
def cdsem_straight_all( straight: ComponentFactory = straight, cross_section: CrossSectionFactory = strip, layer: Tuple[int, int] = LAYER.WG, layers_cladding: List[Tuple[int, int]] = None, widths: Tuple[float, ...] = (0.4, 0.45, 0.5, 0.6, 0.8, 1.0), pixel_size: float = 1.0, length: float = LINE_LENGTH, spacing: float = 4.5, ) -> Component: c = Component() for width in widths: cross_section = partial(cross_section, width=width, layer=layer) c.add_ref(straight(length=length, cross_section=cross_section)) c.distribute(direction="y", spacing=spacing) return c
def pads_shorted( width: int = 100, n_pads: int = 8, pad_spacing: int = 150, layer: Tuple[int, int] = LAYER.M1, ) -> Component: c = Component(name="shorted_pads") pad = rectangle(size=(width, width), layer=layer, centered=True) for i in range(n_pads): pad_ref = c.add_ref(pad) pad_ref.movex(i * pad_spacing - n_pads / 2 * pad_spacing + pad_spacing / 2) short = rectangle(size=(pad_spacing * (n_pads - 1), 10), layer=layer, centered=True) c.add_ref(short) return c
def bend_euler_s(**kwargs) -> Component: """Sbend made of euler bends.""" c = Component() b = bend_euler(**kwargs) b1 = c.add_ref(b) b2 = c.add_ref(b) b2.mirror() b2.connect("o1", b1.ports["o2"]) c.add_port("o1", port=b1.ports["o1"]) c.add_port("o2", port=b2.ports["o2"]) return c
def add_pins_to_references( component: Component, function: Callable = add_pins_triangle, ) -> Component: """Add pins to Component references. Args: component: component to add pins to references: function: function to add pins """ c = Component() c.add_ref(component) references = component.references for reference in references: function(component=c, reference=reference) return c
def cdsem_straight_density( width: float = 0.372, trench_width: float = 0.304, x: float = LINE_LENGTH, y: float = 50.0, margin: float = 2.0, label: str = "", straight: ComponentFactory = straight, layer: Tuple[int, int] = LAYER.WG, layers_cladding: Optional[Tuple[Layer, ...]] = None, cross_section: CrossSectionFactory = strip, pixel_size: float = 1.0, ) -> Component: """Horizontal grating etch lines Args: width: width trench_width: trench_width """ c = Component() period = width + trench_width n_o_lines = int((y - 2 * margin) / period) length = x - 2 * margin cross_section = partial(cross_section, width=width, layer=layer) tooth = straight(length=length, cross_section=cross_section) for i in range(n_o_lines): tooth_ref = c.add_ref(tooth) tooth_ref.movey((-n_o_lines / 2 + 0.5 + i) * period) c.absorb(tooth_ref) marker_label = manhattan_text( text=f"{label}", size=pixel_size, layer=layer, ) _marker_label = c.add_ref(marker_label) _marker_label.move((length + 3, 10.0)) c.absorb(_marker_label) return c
def add_frame( component: Component = rectangle, width: float = 10.0, spacing: float = 10.0, layer: Layer = (1, 0), ) -> Component: """Returns component with a frame around it. Args: component: Component to frame width: of the frame spacing: of component to frame """ c = Component() component = component() if callable(component) else component cref = c.add_ref(component) cref.move(-c.size_info.center) y = ( max([component.size_info.height, component.size_info.width]) / 2 + spacing + width / 2 ) x = y w = width rh = rectangle(size=(2 * y + w, w), layer=layer, centered=True) rtop = c.add_ref(rh) rbot = c.add_ref(rh) rtop.movey(+y) rbot.movey(-y) rv = rectangle(size=(w, 2 * y), layer=layer, centered=True) rl = c.add_ref(rv) rr = c.add_ref(rv) rl.movex(-x) rr.movex(+x) c.absorb(cref) return c
def fanout_component( component: ComponentOrFactory, port_names: Tuple[str, ...], pitch: Tuple[float, float] = (0.0, 20.0), dx: float = 20.0, sort_ports: bool = True, **kwargs, ) -> Component: """Returns component with Sbend fanout routes. Args: component: to fanout ports port_names: list of port names pitch: target port spacing for new component dx: how far the fanout in x direction kwargs: for get_route_sbend """ c = Component() comp = component() if callable(component) else component ref = c.add_ref(comp) ref.movey(-comp.y) for port_name in port_names: if port_name not in ref.ports: raise ValueError(f"{port_name} not in {list(ref.ports.keys())}") ports1 = [p for p in ref.ports.values() if p.name in port_names] port = ports1[0] port_extended_x = port.get_extended_midpoint(dx)[0] port_settings = port.settings.copy() port_settings.pop("name") port_settings.update(midpoint=(port_extended_x, 0)) port_settings.update(orientation=(port.angle + 180) % 360) ports2 = port_array(n=len(ports1), pitch=pitch, **port_settings) if sort_ports: ports1, ports2 = sort_ports_function(ports1, ports2) for i, (p1, p2) in enumerate(zip(ports1, ports2)): route = get_route_sbend(port1=p1, port2=p2, **kwargs) c.add(route.references) c.add_port(f"new_{i}", port=flip(p2)) for port in ref.ports.values(): if port.name not in port_names: c.add_port(port.name, port=port) return c
def gdsdiff( component1: Union[Path, Component, str], component2: Union[Path, Component, str], name: str = "TOP", xor: bool = True, ) -> Component: """Compare two Components. Args: component1: Component or path to gds file (reference) component2: Component or path to gds file (run) name: name of the top cell xor: makes boolean operation Returns: Component with both cells (xor, common and diffs) """ if isinstance(component1, (str, pathlib.Path)): component1 = import_gds(str(component1), flatten=True, name=f"{name}_old") if isinstance(component2, (str, pathlib.Path)): component2 = import_gds(str(component2), flatten=True, name=f"{name}_new") component1 = component1.copy() component2 = component2.copy() component1.name = f"{name}_old" component2.name = f"{name}_new" top = Component(name=f"{name}_diffs") ref1 = top << component1 ref2 = top << component2 if xor: diff = xor_polygons(ref1, ref2, hash_geometry=False) diff.name = f"{name}_xor" top.add_ref(diff) return top
def move( component: Component, origin=(0, 0), destination=None, axis: Optional[str] = None, ) -> Component: """Return container that contains a reference to the original component.""" component_new = Component() component_new.component = component ref = component_new.add_ref(component) ref.move(origin=origin, destination=destination, axis=axis) component_new.add_ports(ref.ports) component_new.copy_child_info(component) return component_new
def mirror(component: Component, p1: Float2 = (0, 1), p2: Float2 = (0, 0)) -> Component: """Returns mirrored component inside a new component. Args: p1: first point to define mirror axis p2: second point to define mirror axis """ component_new = Component() component_new.component = component ref = component_new.add_ref(component) ref.mirror(p1=p1, p2=p2) component_new.add_ports(ref.ports) component_new.copy_child_info(component) return component_new
def add_termination( component: Component, ports: Optional[List[Port]] = None, terminator: ComponentFactory = terminator_function, port_name: Optional[str] = None, port_type: str = "optical", **kwargs ) -> Component: """Returns component with all or some ports terminated Args: component: ports: optional list of ports to terminate (defaults to all) terminator: factory for the terminator port_name: for the terminator to connect to the component ports port_type: of the ports that you want to terminate **kwargs: for the ports you want to terminate (orientation, width) """ terminator = terminator() if callable(terminator) else terminator port_name = port_name or terminator.get_ports_list()[0].name c = Component() c.add_ref(component) c.component = component ports_all = component.get_ports_list() ports = ports or component.get_ports_list(port_type=port_type, **kwargs) for port in ports_all: if port in ports: t_ref = c.add_ref(terminator) t_ref.connect(port_name, port) else: c.add_port(port.name, port=port) c.copy_child_info(component) return c
def loop_mirror(component: ComponentFactory = mmi1x2, bend90: ComponentFactory = bend_euler) -> Component: """Returns Sagnac loop_mirror.""" c = Component() component = gf.call_if_func(component) bend90 = gf.call_if_func(bend90) cref = c.add_ref(component) routes = route_manhattan( cref.ports["o3"], cref.ports["o2"], straight=gf.components.straight, bend=bend90, ) c.add(routes.references) c.add_port(name="o1", port=cref.ports["o1"]) c.absorb(cref) return c
def bend_straight_bend(straight_length: float = 10.0, angle: int = 90, p: float = 0.5, with_arc_floorplan: bool = True, npoints: int = 720, direction: str = "ccw", with_cladding_box: bool = True, cross_section: CrossSectionOrFactory = strip, **kwargs) -> Component: """Sbend made of 2 euler bends and straight section in between. Args: straight_length: angle: total angle of the curve p: Proportion of the curve that is an Euler curve with_arc_floorplan: If False: `radius` is the minimum radius of curvature If True: The curve scales such that the endpoints match a bend_circular with parameters `radius` and `angle` npoints: Number of points used per 360 degrees direction: cw (clock-wise) or ccw (counter clock-wise) with_cladding_box: to avoid DRC acute angle errors in cladding cross_section: kwargs: cross_section settings """ c = Component() b = bend_euler(angle=angle, p=p, with_arc_floorplan=with_arc_floorplan, npoints=npoints, direction=direction, with_cladding_box=with_cladding_box, cross_section=cross_section, **kwargs) b1 = c.add_ref(b) b2 = c.add_ref(b) s = c << straight( length=straight_length, cross_section=cross_section, **kwargs) s.connect("o1", b1.ports["o2"]) b2.mirror() b2.connect("o1", s.ports["o2"]) c.add_port("o1", port=b1.ports["o1"]) c.add_port("o2", port=b2.ports["o2"]) return c
def align_wafer( width: float = 10.0, spacing: float = 10.0, cross_length: float = 80.0, layer: Tuple[int, int] = (1, 0), layer_cladding: Optional[Tuple[int, int]] = None, square_corner: str = "bottom_left", ) -> Component: """Returns cross inside a frame to align wafer.""" c = Component() cross = gf.components.cross(length=cross_length, width=width, layer=layer) c.add_ref(cross) b = cross_length / 2 + spacing + width / 2 w = width rh = rectangle(size=(2 * b + w, w), layer=layer, centered=True) rtop = c.add_ref(rh) rbot = c.add_ref(rh) rtop.movey(+b) rbot.movey(-b) rv = rectangle(size=(w, 2 * b), layer=layer, centered=True) rl = c.add_ref(rv) rr = c.add_ref(rv) rl.movex(-b) rr.movex(+b) wsq = (cross_length + 2 * spacing) / 4 square_mark = c << rectangle(size=(wsq, wsq), layer=layer, centered=True) a = width / 2 + wsq / 2 + spacing corner_to_position = { "bottom_left": (-a, -a), "bottom_right": (a, -a), "top_right": (a, a), "top_left": (-a, a), } square_mark.move(corner_to_position[square_corner]) if layer_cladding: rc_tile_excl = rectangle( size=(2 * (b + spacing), 2 * (b + spacing)), layer=layer_cladding, centered=True, ) c.add_ref(rc_tile_excl) return c
def rotate( component: ComponentOrFactory, angle: int = 90, ) -> Component: """Returns rotated component inside a new component. Most times you just need to place a reference and rotate it. This rotate function just encapsulates the rotated reference into a new component. Args: component: angle: in degrees """ component = component() if callable(component) else component component_new = Component() component_new.component = component ref = component_new.add_ref(component) ref.rotate(angle) component_new.add_ports(ref.ports) component_new.copy_child_info(component) return component_new
def pcm_optical( widths: Tuple[float, ...] = (0.4, 0.45, 0.5, 0.6, 0.8, 1.0), dense_lines_width: float = 0.3, dense_lines_width_difference: float = 20e-3, dense_lines_gap: float = 0.3, dense_lines_labels: Tuple[str, ...] = ("DL", "DM", "DH"), straight: ComponentFactory = straight, bend90: ComponentFactory = bend_circular, layer: Tuple[int, int] = LAYER.WG, layers_cladding: List[Tuple[int, int]] = None, cross_section: CrossSectionFactory = strip, pixel_size: float = 1.0, ) -> Component: """column with all optical PCMs Args: widths: for straight """ c = Component() _c1 = cdsem_straight_all( straight=straight, layer=layer, layers_cladding=layers_cladding, widths=widths, cross_section=cross_section, pixel_size=pixel_size, ) all_devices = [_c1] all_devices += [ cdsem_uturn( width=width, straight=straight, bend90=bend90, layer=layer, layers_cladding=layers_cladding, cross_section=cross_section, pixel_size=pixel_size, ) for width in widths ] density_params = [ ( dense_lines_width - dense_lines_width_difference, dense_lines_gap - dense_lines_width_difference, dense_lines_labels[0], ), (dense_lines_width, dense_lines_gap, dense_lines_labels[1]), ( dense_lines_width + dense_lines_width_difference, dense_lines_gap + dense_lines_width_difference, dense_lines_labels[2], ), ] all_devices += [ cdsem_straight_density( width=w, trench_width=t, label=lbl, straight=straight, layer=layer, layers_cladding=layers_cladding, cross_section=cross_section, pixel_size=pixel_size, ) for w, t, lbl in density_params ] [c.add_ref(d) for d in all_devices] c.align(elements="all", alignment="xmin") c.distribute(elements="all", direction="y", spacing=5, separation=True) return c
def array_with_fanout( component: ComponentOrFactory = pad, columns: int = 3, pitch: float = 150.0, waveguide_pitch: float = 10.0, start_straight_length: float = 5.0, end_straight_length: float = 40.0, radius: float = 5.0, component_port_name: str = "e4", bend: ComponentFactory = bend_euler, bend_port_name1: Optional[str] = None, bend_port_name2: Optional[str] = None, cross_section: CrossSectionFactory = strip, **kwargs, ) -> Component: """Returns an array of components in X axis with fanout waveguides facing west Args: component: to replicate. columns: number of components. pitch: for waveguides. waveguide_pitch: for output waveguides. start_straight_length: length of the start of the straight end_straight_length: lenght of the straight at the end radius: bend radius component_port_name: bend: bend_port_name1: bend_port_name2: cross_section: cross_section definition kwargs: cross_section settings """ c = Component() component = component() if callable(component) else component bend = bend(radius=radius, cross_section=cross_section, **kwargs) bend_ports = bend.get_ports_list() bend_ports = sort_ports_x(bend_ports) bend_ports.reverse() bend_port_name1 = bend_port_name1 or bend_ports[0].name bend_port_name2 = bend_port_name2 or bend_ports[1].name for col in range(columns): ref = component.ref() ref.x = col * pitch c.add(ref) ylength = col * waveguide_pitch + start_straight_length xlength = col * pitch + end_straight_length straight_ref = c << straight( length=ylength, cross_section=cross_section, **kwargs) port_s1, port_s2 = straight_ref.get_ports_list() straight_ref.connect(port_s2.name, ref.ports[component_port_name]) bend_ref = c.add_ref(bend) bend_ref.connect(bend_port_name1, straight_ref.ports[port_s1.name]) straightx_ref = c << straight( length=xlength, cross_section=cross_section, **kwargs) straightx_ref.connect(port_s2.name, bend_ref.ports[bend_port_name2]) c.add_port(f"W_{col}", port=straightx_ref.ports[port_s1.name]) auto_rename_ports(c) return c
def add_grating_couplers_with_loopback_fiber_single( component: Component, grating_coupler: ComponentFactory = grating_coupler_te, layer_label: Tuple[int, int] = (200, 0), gc_port_name: str = "o1", get_input_labels_function: Callable[..., List[Label]] = get_input_labels, get_input_label_text_loopback_function: Callable = get_input_label_text_loopback, select_ports: Callable = select_ports_optical, with_loopback: bool = True, cross_section: CrossSectionFactory = strip, component_name: Optional[str] = None, fiber_spacing: float = 50.0, loopback_xspacing: float = 5.0, straight: ComponentFactory = straight_function, rotation: int = 90, ) -> Component: """ Returns component with all ports terminated with grating couplers Args: component: grating_coupler: layer_label: gc_port_name: get_input_label_text_loopback_function: with_loopback: adds a reference loopback rotation: 90 for North South devices, 0 for East-West """ c = Component() c.component = component c.add_ref(component) grating_coupler = ( grating_coupler() if callable(grating_coupler) else grating_coupler ) component_name = component_name or component.info_child.name io_gratings = [] optical_ports = select_ports(component.ports) optical_ports = list(optical_ports.values()) for port in optical_ports: gc_ref = grating_coupler.ref() gc_port = gc_ref.ports[gc_port_name] gc_ref.connect(gc_port, port) io_gratings.append(gc_ref) c.add(gc_ref) labels = get_input_labels_function( io_gratings, list(component.ports.values()), component_name=component_name, layer_label=layer_label, gc_port_name=gc_port_name, ) c.add(labels) p2 = optical_ports[0] p1 = optical_ports[-1] if with_loopback: if rotation in [0, 180]: length = abs(p2.x - p1.x) wg = c << straight(length=length, cross_section=cross_section) wg.rotate(rotation) wg.xmin = p2.x wg.ymin = c.ymax + grating_coupler.ysize / 2 + loopback_xspacing else: length = abs(p2.y - p1.y) wg = c << straight(length=length, cross_section=cross_section) wg.rotate(rotation) wg.ymin = p1.y wg.xmin = c.xmax + grating_coupler.ysize / 2 + loopback_xspacing gci = c << grating_coupler gco = c << grating_coupler gci.connect(gc_port_name, wg.ports["o1"]) gco.connect(gc_port_name, wg.ports["o2"]) port = wg.ports["o2"] text = get_input_label_text_loopback_function( port=port, gc=grating_coupler, gc_index=0, component_name=component_name ) c.add_label( text=text, position=port.midpoint, anchor="o", layer=layer_label, ) port = wg.ports["o1"] text = get_input_label_text_loopback_function( port=port, gc=grating_coupler, gc_index=1, component_name=component_name ) c.add_label( text=text, position=port.midpoint, anchor="o", layer=layer_label, ) c.copy_child_info(component) return c