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 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 contact( size: Tuple[float, float] = (11.0, 11.0), layers: Tuple[Layer, ...] = (LAYER.M1, LAYER.M2, LAYER.M3), vias: Optional[Tuple[Optional[ComponentOrFactory], ...]] = (via1, via2), layer_port: Optional[Layer] = None, ) -> Component: """Rectangular contact Args: size: of the layers layers: layers on which to draw rectangles vias: vias to use to fill the rectangles layer_port: if None asumes port is on the last layer """ width, height = size a = width / 2 b = height / 2 layer_port = layer_port or layers[-1] c = Component() c.height = height c.info.size = (float(size[0]), float(size[1])) c.info.layer = layer_port for layer in layers: ref = c << compass(size=(width, height), layer=layer) if layer == layer_port: c.add_ports(ref.ports) vias = vias or [] for via in vias: if via is not None: via = via() if callable(via) else via w, h = via.info["size"] g = via.info["enclosure"] pitch_x, pitch_y = via.info["spacing"] nb_vias_x = (width - w - 2 * g) / pitch_x + 1 nb_vias_y = (height - h - 2 * g) / pitch_y + 1 nb_vias_x = int(floor(nb_vias_x)) or 1 nb_vias_y = int(floor(nb_vias_y)) or 1 ref = c.add_array( via, columns=nb_vias_x, rows=nb_vias_y, spacing=(pitch_x, pitch_y) ) cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2 ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 ref.move((x0, y0)) return c
def bend_circular(angle: int = 90, npoints: int = 720, with_cladding_box: bool = True, cross_section: CrossSectionOrFactory = strip, **kwargs) -> Component: """Returns a radial arc. Args: angle: angle of arc (degrees) npoints: number of points with_cladding_box: square in layers_cladding to remove DRC cross_section: kwargs: cross_section settings .. code:: o2 | / / / o1_____/ """ x = cross_section(**kwargs) if callable(cross_section) else cross_section radius = x.info["radius"] p = arc(radius=radius, angle=angle, npoints=npoints) c = Component() path = extrude(p, x) ref = c << path c.add_ports(ref.ports) c.info.length = snap_to_grid(p.length()) c.info.dy = float(abs(p.points[0][0] - p.points[-1][0])) c.info.radius = float(radius) if with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] top = cladding_offset if angle == 180 else 0 points = get_padding_points( component=c, default=0, bottom=cladding_offset, right=cladding_offset, top=top, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) c.absorb(ref) return c
def bend_s(size: Float2 = (10.0, 2.0), nb_points: int = 99, with_cladding_box: bool = True, cross_section: CrossSectionFactory = strip, **kwargs) -> Component: """S bend with bezier curve stores min_bend_radius property in self.info['min_bend_radius'] min_bend_radius depends on height and length Args: size: in x and y direction nb_points: number of points with_cladding_box: square bounding box to avoid DRC errors cross_section: function kwargs: cross_section settings """ dx, dy = size x = cross_section(**kwargs) width = x.info["width"] layer = x.info["layer"] c = Component() bend = bezier( width=width, control_points=[(0, 0), (dx / 2, 0), (dx / 2, dy), (dx, dy)], npoints=nb_points, layer=layer, ) bend_ref = c << bend c.add_ports(bend_ref.ports) c.copy_child_info(bend) c.info.start_angle = bend.info.start_angle c.info.end_angle = bend.info.end_angle c.info.length = bend.info.length c.info.min_bend_radius = bend.info.min_bend_radius if with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] points = get_padding_points( component=c, default=0, bottom=cladding_offset, top=cladding_offset, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) auto_rename_ports(c) return c
def add_electrical_pads_shortest( component: Component, pad: ComponentOrFactory = pad_function, pad_port_spacing: float = 50.0, select_ports=select_ports_electrical, port_orientation: int = 90, layer: gf.types.Layer = (31, 0), **kwargs, ) -> Component: """Add pad to each closest electrical port. Args: component: pad: pad element or function pad_port_spacing: between pad and port select_ports: function port_orientation layer: for the routing **kwargs: pad_settings """ c = Component() c.component = component ref = c << component ports = select_ports(ref.ports) ports = list(ports.values()) pad = pad(**kwargs) if callable(pad) else pad pad_port_spacing += pad.info_child.full["size"][0] / 2 for port in ports: p = c << pad if port_orientation == 0: p.x = port.x + pad_port_spacing p.y = port.y c.add(route_quad(port, p.ports["e1"], layer=layer)) elif port_orientation == 180: p.x = port.x - pad_port_spacing p.y = port.y c.add(route_quad(port, p.ports["e3"], layer=layer)) elif port_orientation == 90: p.y = port.y + pad_port_spacing p.x = port.x c.add(route_quad(port, p.ports["e4"], layer=layer)) elif port_orientation == 270: p.y = port.y - pad_port_spacing p.x = port.x c.add(route_quad(port, p.ports["e2"], layer=layer)) c.add_ports(ref.ports) for port in ports: c.ports.pop(port.name) c.copy_child_info(component) return c
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 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 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 add_fidutials(component: ComponentFactory = pad_array, gap: float = 50, left: Optional[ComponentFactory] = cross, right: Optional[ComponentFactory] = cross, top: Optional[ComponentFactory] = None, bottom: Optional[ComponentFactory] = None, **kwargs) -> Component: """Return component with fidutials. Args: component: component to add to the new component. gap: from component to fidutial edge. left: optional left fidutial. right: optional right fidutial. top: optional top fidutial. bottom: optional bottom fidutial. """ c = Component() component = component(**kwargs) r = c << component if left: x1 = c << left() x1.xmax = r.xmin - gap if right: x2 = c << right() x2.xmin = r.xmax + gap if top: y1 = c << top() y1.ymin = r.ymax + gap if bottom: y2 = c << bottom() y2.ymin = r.ymin - gap c.add_ports(r.ports) c.copy_child_info(component) return c
def rectangle( size: Tuple[float, float] = (4.0, 2.0), layer: Layer = (1, 0), centered: bool = False, port_type: str = "electrical", ) -> Component: """rectangle Args: size: (tuple) Width and height of rectangle. layer: Specific layer to put polygon geometry on. centered: True sets center to (0, 0), False sets south-west to (0, 0) port_type: """ c = Component() ref = c << compass(size=size, layer=layer, port_type=port_type) if not centered: ref.move((size[0] / 2, size[1] / 2)) c.add_ports(ref.ports) 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 add_electrical_pads_top( component: Component, spacing: Float2 = (0.0, 100.0), pad_array: ComponentFactory = pad_array_function, select_ports=select_ports_electrical, **kwargs, ) -> Component: """Returns new component with electrical ports connected to top pad array Args: component: spacing: component to pad spacing select_ports: function to select electrical ports kwargs: pad settings pad: pad element pitch: x spacing n: number of pads **port_settings """ c = Component() c.component = component ref = c << component ports = select_ports(ref.ports) ports = list(ports.values()) pads = c << pad_array_function( columns=len(ports), orientation=270, **kwargs) pads.x = ref.x + spacing[0] pads.ymin = ref.ymax + spacing[1] ports_pads = list(pads.ports.values()) ports_pads = gf.routing.sort_ports.sort_ports_x(ports_pads) ports_component = gf.routing.sort_ports.sort_ports_x(ports) for p1, p2 in zip(ports_component, ports_pads): c.add(get_route_electrical_shortest_path(p1, p2)) c.add_ports(ref.ports) for port in ports: c.ports.pop(port.name) return c
def add_electrical_pads_top( component: Component, spacing: Float2 = (0.0, 100.0), pad_array: ComponentFactory = pad_array_function, select_ports=select_ports_electrical, layer: gf.types.Layer = (31, 0), ) -> Component: """Returns new component with electrical ports connected to top pad array Args: component: spacing: component to pad spacing pad_array: function for pad_array select_ports: function to select electrical ports layer: for the routes """ c = Component() c.component = component ref = c << component ports = select_ports(ref.ports) ports = list(ports.values()) pads = c << pad_array(columns=len(ports), orientation=270) pads.x = ref.x + spacing[0] pads.ymin = ref.ymax + spacing[1] ports_pads = list(pads.ports.values()) ports_pads = gf.routing.sort_ports.sort_ports_x(ports_pads) ports_component = gf.routing.sort_ports.sort_ports_x(ports) for p1, p2 in zip(ports_component, ports_pads): c.add(route_quad(p1, p2, layer=layer)) c.add_ports(ref.ports) for port in ports: c.ports.pop(port.name) c.copy_child_info(component) return c
def straight(length: float = 10.0, npoints: int = 2, with_cladding_box: bool = True, cross_section: CrossSectionOrFactory = strip, **kwargs) -> Component: """Returns a Straight waveguide. Args: length: straight length npoints: number of points with_cladding_box: box in layers_cladding to avoid DRC sharp edges cross_section: **kwargs: cross_section settings """ p = gf.path.straight(length=length, npoints=npoints) x = cross_section(**kwargs) if callable(cross_section) else cross_section c = Component() path = gf.path.extrude(p, x) ref = c << path c.add_ports(ref.ports) c.info.length = gf.snap.snap_to_grid(length) c.info.width = float(x.info["width"]) if length > 0 and with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] points = get_padding_points( component=c, default=0, bottom=cladding_offset, top=cladding_offset, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) c.absorb(ref) return c
def route_fiber_single( component: Component, fiber_spacing: float = 50.0, grating_coupler: Callable = grating_coupler_te, min_input_to_output_spacing: float = 200.0, optical_routing_type: int = 1, optical_port_labels: Optional[Tuple[str, ...]] = None, excluded_ports: Optional[Tuple[str, ...]] = None, auto_widen: bool = False, component_name: Optional[str] = None, select_ports: Callable = select_ports_optical, cross_section: CrossSectionFactory = strip, **kwargs, ) -> Tuple[List[Union[ComponentReference, Label]], List[ComponentReference]]: """Returns route Tuple(references, grating couplers) for single fiber input/output. Args: component: to add grating couplers fiber_spacing: between grating couplers grating_coupler: min_input_to_output_spacing: so opposite fibers do not touch optical_routing_type: 0 (basic), 1 (standard), 2 (looks at ports) optical_port_labels: port labels that need connection excluded_ports: ports excluded from routing auto_widen: for long routes component_name: select_ports: cross_section: **kwargs: cross_section settings Returns: elements: list of ComponentReferences for routes and labels grating_couplers: list of grating_couplers references .. code:: _________ | |_E1 W0_| | | |_E0 |_________| rotates +90 deg and routes West ports to South the rest of the original ports (East, North, South) will route south it calls route_fiber_array twice route_fiber_array is designed to route ports south E1 E0 _|___|_ | | | | | | | | | | | | |_______| | W0 1st part routes West ports south then rotates 180 and routes the rest of the ports North """ if not select_ports(component.ports): raise ValueError(f"No ports for {component.name}") component = component.copy() component_copy = component.copy() if optical_port_labels is None: optical_ports = select_ports(component.ports) else: optical_ports = [component.ports[lbl] for lbl in optical_port_labels] excluded_ports = excluded_ports or [] optical_ports = { p.name: p for p in optical_ports.values() if p.name not in excluded_ports } N = len(optical_ports) 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 gc_port2center = getattr(grating_coupler, "port2center", grating_coupler.xsize / 2) if component.xsize + 2 * gc_port2center < min_input_to_output_spacing: fanout_length = (gf.snap.snap_to_grid( min_input_to_output_spacing - component.xsize - 2 * gc_port2center, 10) / 2) else: fanout_length = None # route WEST ports to south component_west_ports = Component() ref = component_west_ports << component ref.rotate(90) south_ports = ref.get_ports_dict(orientation=270) component_west_ports.ports = south_ports if len(south_ports): elements_south, gratings_south, _ = route_fiber_array( component=component_west_ports, with_loopback=False, fiber_spacing=fiber_spacing, fanout_length=fanout_length, grating_coupler=grating_couplers[0], optical_routing_type=optical_routing_type, auto_widen=auto_widen, component_name=component_name, cross_section=cross_section, select_ports=select_ports, **kwargs, ) # route non WEST ports north component = gf.Component() component_ref = component << component_copy component_ref.rotate(-90) component.add_ports(component_ref.ports) for port_already_routed in south_ports.values(): component.ports.pop(port_already_routed.name) component.ports = select_ports(component.ports) elements_north, gratings_north, _ = route_fiber_array( component=component, with_loopback=False, fiber_spacing=fiber_spacing, fanout_length=fanout_length, grating_coupler=grating_couplers[1:], optical_routing_type=optical_routing_type, auto_widen=auto_widen, component_name=component_name, cross_section=cross_section, select_ports=select_ports, **kwargs, ) for e in elements_north: if isinstance(e, list): for ei in e: elements_south.append(ei.rotate(180)) else: 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 mzi_lattice( coupler_lengths: Tuple[float, ...] = (10.0, 20.0), coupler_gaps: Tuple[float, ...] = (0.2, 0.3), delta_lengths: Tuple[float, ...] = (10.0, ), mzi: ComponentFactory = mzi_coupler, splitter: ComponentFactory = coupler_function, **kwargs, ) -> Component: r"""Mzi lattice filter. Args: coupler_lengths: list of length for each coupler coupler_gaps: list of coupler gaps delta_lengths: list of length differences mzi: function for the mzi splitter: splitter function keyword Args: length_y: vertical length for both and top arms length_x: horizontal length bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x cross_section: for routing (sxtop/sxbot to combiner) .. code:: ______ ______ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cp_last=== | | | | | | | | DL1 | DL2 | | | | | |______| | | |______| """ assert len(coupler_lengths) == len(coupler_gaps) assert len(coupler_lengths) == len(delta_lengths) + 1 c = Component() splitter_settings = dict(gap=coupler_gaps[0], length=coupler_lengths[0]) combiner_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) cp1 = splitter1() sprevious = c << mzi( splitter=splitter1, combiner=combiner1, with_splitter=True, delta_length=delta_lengths[0], **kwargs, ) c.add_ports(sprevious.get_ports_list(port_type="electrical")) stages = [] for length, gap, delta_length in zip(coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:]): splitter_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1]) combiner_settings = dict(length=length, gap=gap) splitter1 = partial(splitter, **splitter_settings) combiner1 = partial(splitter, **combiner_settings) stage = c << mzi( splitter=splitter1, combiner=combiner1, with_splitter=False, delta_length=delta_length, **kwargs, ) splitter_settings = combiner_settings stages.append(stage) c.add_ports(stage.get_ports_list(port_type="electrical")) for stage in stages: stage.connect("o1", sprevious.ports["o4"]) # stage.connect('o2', sprevious.ports['o1']) sprevious = stage for port in cp1.get_ports_list(orientation=180, port_type="optical"): c.add_port(port.name, port=port) for port in sprevious.get_ports_list(orientation=0, port_type="optical"): c.add_port(f"o_{port.name}", port=port) c.auto_rename_ports() return c
def contact_slot( size: Tuple[float, float] = (11.0, 11.0), layers: Tuple[Layer, ...] = (LAYER.M1, LAYER.M2), layer_offsets: Tuple[float, ...] = (0, 1.0), layer_port: Optional[Layer] = None, via: ComponentOrFactory = via1, enclosure: float = 1.0, ysize: float = 0.5, yspacing: float = 2.0, ) -> Component: """Rectangular contact with slotted via in X direction Args: size: of the layers layers: layers on which to draw rectangles layer_offsets: cladding_offset for each layer layer_port: if None asumes port is on the last layer via: via to use to fill the rectangles enclosure: of the via by rectangle ysize: via height in y yspacing: via spacing pitch in y .. code:: enclosure _____________________________________ |<---> | | ______________________ | | | | | | | | ysize| | |______________________| | | | | | | yspacing | | | | | | ______________________ | | | | | | | | | | ysize| | | |______________________| | | | |___________________________________| size[0] """ layer_port = layer_port or layers[-1] c = Component() for layer, offset in zip(layers, list(layer_offsets) + [0] * len(layers)): ref = c << compass(size=(size[0] + 2 * offset, size[1] + 2 * offset), layer=layer) if layer == layer_port: c.add_ports(ref.ports) via = via(size=(size[0] - 2 * enclosure, ysize)) if callable(via) else via nb_vias_y = (size[1] - 2 * enclosure) / yspacing nb_vias_y = int(floor(nb_vias_y)) or 1 ref = c.add_array(via, columns=1, rows=nb_vias_y, spacing=(0, yspacing)) dy = (size[1] - (nb_vias_y - 1) * yspacing - size[1]) / 2 ref.move((0, dy)) return c
def mzi( delta_length: float = 10.0, length_y: float = 2.0, length_x: Optional[float] = 0.1, bend: ComponentOrFactory = bend_euler, straight: ComponentFactory = straight_function, straight_y: Optional[ComponentFactory] = None, straight_x_top: Optional[ComponentFactory] = None, straight_x_bot: Optional[ComponentFactory] = None, splitter: ComponentOrFactory = mmi1x2, combiner: Optional[ComponentFactory] = None, with_splitter: bool = True, port_e1_splitter: str = "o2", port_e0_splitter: str = "o3", port_e1_combiner: str = "o2", port_e0_combiner: str = "o3", nbends: int = 2, cross_section: CrossSectionFactory = strip, ) -> Component: """Mzi. Args: delta_length: bottom arm vertical extra length length_y: vertical length for both and top arms length_x: horizontal length. None uses to the straight_x_bot/top defaults bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x splitter: splitter function combiner: combiner function with_splitter: if False removes splitter port_e1_combiner: east top combiner port port_e0_splitter: east bot splitter port port_e1_splitter: east top splitter port port_e0_combiner: east bot combiner port nbends: from straight top/bot to combiner (at least 2) cross_section: for routing (sxtop/sxbot to combiner) .. code:: b2______b3 | sxtop | straight_y | | | b1 b4 splitter==| |==combiner b5 b8 | | straight_y | | | delta_length/2 | | | b6__sxbot__b7 Lx """ combiner = combiner or splitter straight = partial(straight, cross_section=cross_section) straight_x_top = straight_x_top or straight straight_x_bot = straight_x_bot or straight straight_y = straight_y or straight bend_factory = bend bend = bend_factory(cross_section=cross_section) c = Component() cp1 = splitter() if callable(splitter) else splitter cp2 = combiner() if combiner else cp1 if with_splitter: cp1 = c << cp1 cp2 = c << cp2 b5 = c << bend b5.mirror() b5.connect("o1", cp1.ports[port_e0_splitter]) syl = c << straight_y(length=delta_length / 2 + length_y, ) syl.connect("o1", b5.ports["o2"]) b6 = c << bend b6.connect("o1", syl.ports["o2"]) straight_x_bot = straight_x_bot( length=length_x) if length_x else straight_x_bot() sxb = c << straight_x_bot sxb.connect("o1", b6.ports["o2"]) b1 = c << bend b1.connect("o1", cp1.ports[port_e1_splitter]) sy = c << straight_y(length=length_y) sy.connect("o1", b1.ports["o2"]) b2 = c << bend b2.connect("o2", sy.ports["o2"]) straight_x_top = straight_x_top( length=length_x) if length_x else straight_x_top() sxt = c << straight_x_top sxt.connect("o1", b2.ports["o1"]) cp2.mirror() cp2.xmin = sxt.ports["o2"].x + bend.info["radius"] * nbends + 0.1 route = get_route( sxt.ports["o2"], cp2.ports[port_e1_combiner], straight=straight, bend=bend_factory, cross_section=cross_section, ) c.add(route.references) route = get_route( sxb.ports["o2"], cp2.ports[port_e0_combiner], straight=straight, bend=bend_factory, cross_section=cross_section, ) c.add(route.references) if with_splitter: c.add_ports(cp1.get_ports_list(orientation=180), prefix="in") else: c.add_port("o1", port=b1.ports["o1"]) c.add_port("o2", port=b5.ports["o1"]) c.add_ports(cp2.get_ports_list(orientation=0), prefix="out") c.add_ports(sxt.get_ports_list(port_type="electrical"), prefix="top") c.add_ports(sxb.get_ports_list(port_type="electrical"), prefix="bot") c.auto_rename_ports() return c
def straight_heater_metal_undercut( length: float = 320.0, length_undercut_spacing: float = 6.0, length_undercut: float = 30.0, length_straight_input: float = 15.0, heater_width: float = 2.5, cross_section_heater: CrossSectionFactory = strip_heater_metal, cross_section_heater_undercut: CrossSectionFactory = strip_heater_metal_undercut, with_undercut: bool = True, contact: Optional[ComponentFactory] = contact_heater, port_orientation1: int = 180, port_orientation2: int = 0, taper_length: Optional[float] = 5.0, **kwargs, ) -> Component: """Returns a thermal phase shifter. dimensions from https://doi.org/10.1364/OE.27.010456 Args: length: of the waveguide length_undercut_spacing: from undercut regions length_undercut: length of each undercut section length_straight_input: from input port to where trenches start cross_section_heater: for heated sections cross_section_heater_undercut: for heated sections with undercut with_undercut: isolation trenches for higher efficiency contact: via stack port_orientation1: left via stack port orientation port_orientation2: right via stack port orientation kwargs: cross_section common settings """ period = length_undercut + length_undercut_spacing n = int((length - 2 * length_straight_input) // period) length_straight_input = (length - n * period) / 2 s_si = gf.c.straight( cross_section=cross_section_heater, length=length_straight_input, heater_width=heater_width, **kwargs, ) cross_section_undercut = ( cross_section_heater_undercut if with_undercut else cross_section_heater ) s_uc = gf.c.straight( cross_section=cross_section_undercut, length=length_undercut, heater_width=heater_width, **kwargs, ) s_spacing = gf.c.straight( cross_section=cross_section_heater, length=length_undercut_spacing, heater_width=heater_width, **kwargs, ) symbol_to_component = { "-": (s_si, "o1", "o2"), "U": (s_uc, "o1", "o2"), "H": (s_spacing, "o1", "o2"), } # Each character in the sequence represents a component sequence = "-" + n * "UH" + "-" c = Component() sequence = gf.components.component_sequence( sequence=sequence, symbol_to_component=symbol_to_component ) c.add_ref(sequence) c.add_ports(sequence.ports) if contact: contactw = contact() contacte = contact() contact_west_midpoint = sequence.aliases["-1"].size_info.cw contact_east_midpoint = sequence.aliases["-2"].size_info.ce dx = contactw.get_ports_xsize() / 2 + taper_length or 0 contact_west = c << contactw contact_east = c << contacte contact_west.move(contact_west_midpoint - (dx, 0)) contact_east.move(contact_east_midpoint + (dx, 0)) c.add_port( "e1", port=contact_west.get_ports_list(orientation=port_orientation1)[0] ) c.add_port( "e2", port=contact_east.get_ports_list(orientation=port_orientation2)[0] ) if taper_length: x = cross_section_heater() taper = gf.c.taper( width1=contactw.ports["e1"].width, width2=heater_width, length=taper_length, layer=x.info["layer_heater"], ) taper1 = c << taper taper2 = c << taper taper1.connect("o1", contact_west.ports["e3"]) taper2.connect("o1", contact_east.ports["e1"]) return c
def bend_euler(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: """Returns an euler bend that adiabatically transitions from straight to curved. By default, `radius` corresponds to the minimum radius of curvature of the bend. However, if `with_arc_floorplan` is True, `radius` corresponds to the effective radius of curvature (making the curve a drop-in replacement for an arc). If p < 1.0, will create a "partial euler" curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394 default p = 0.5 based on this paper https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-8-9150&id=362937 Args: 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 .. code:: o2 | / / / o1_____/ """ x = cross_section(**kwargs) if callable(cross_section) else cross_section radius = x.info["radius"] c = Component() p = euler(radius=radius, angle=angle, p=p, use_eff=with_arc_floorplan, npoints=npoints) ref = c << extrude(p, x) c.add_ports(ref.ports) c.info.length = snap_to_grid(p.length()) c.info.dy = abs(float(p.points[0][0] - p.points[-1][0])) c.info.radius_min = float(snap_to_grid(p.info["Rmin"])) c.info.radius = float(radius) if with_cladding_box and x.info["layers_cladding"]: layers_cladding = x.info["layers_cladding"] cladding_offset = x.info["cladding_offset"] top = cladding_offset if angle == 180 else 0 points = get_padding_points( component=c, default=0, bottom=cladding_offset, right=cladding_offset, top=top, ) for layer in layers_cladding or []: c.add_polygon(points, layer=layer) if direction == "cw": ref.mirror(p1=[0, 0], p2=[1, 0]) c.absorb(ref) return c
def mzi_arms( delta_length: float = 10.0, length_y: float = 0.8, length_x: float = 0.1, bend: ComponentOrFactory = bend_euler, straight: ComponentFactory = straight_function, straight_y: Optional[ComponentFactory] = None, straight_x_top: Optional[ComponentFactory] = None, straight_x_bot: Optional[ComponentFactory] = None, splitter: ComponentOrFactory = mmi1x2, combiner: Optional[ComponentFactory] = None, with_splitter: bool = True, delta_yright: float = 0, **kwargs, ) -> Component: """Mzi made with arms. This MZI code is slightly deprecated You can find a more robust mzi in gf.components.mzi Args: delta_length: bottom arm vertical extra length length_y: vertical length for both and top arms length_x: horizontal length bend: 90 degrees bend library straight: straight function straight_y: straight for length_y and delta_length straight_x_top: top straight for length_x straight_x_bot: bottom straight for length_x splitter: splitter function combiner: combiner function with_splitter: if False removes splitter delta_yright: extra length for right y-oriented waveguide kwargs: cross_section settings .. code:: __Lx__ | | Ly Lyr (not a parameter) | | splitter==| |==combiner | | Ly Lyr (not a parameter) | | | delta_length/2 | | |__Lx__| ____________ __________ | | | | | ___| ____| |____ | splitter d1 d2 combiner ____| ____ | | ____ | | | |__________| |__________ """ combiner = combiner or splitter straight_x_top = straight_x_top or straight straight_x_bot = straight_x_bot or straight straight_y = straight_y or straight c = Component() cp1 = splitter() if callable(splitter) else splitter cp2 = combiner() if combiner else cp1 if with_splitter: cin = c << cp1 cout = c << cp2 ports_cp1 = cp1.get_ports_list(clockwise=False) ports_cp2 = cp2.get_ports_list(clockwise=False) port_e1_cp1 = ports_cp1[1] port_e0_cp1 = ports_cp1[0] port_e1_cp2 = ports_cp2[1] port_e0_cp2 = ports_cp2[0] y1t = port_e1_cp1.y y1b = port_e0_cp1.y y2t = port_e1_cp2.y y2b = port_e0_cp2.y d1 = abs(y1t - y1b) # splitter ports distance d2 = abs(y2t - y2b) # combiner ports distance delta_symm_half = -delta_yright / 2 if d2 > d1: length_y_left = length_y + (d2 - d1) / 2 length_y_right = length_y else: length_y_right = length_y + (d1 - d2) / 2 length_y_left = length_y _top_arm = mzi_arm( straight_x=straight_x_top, straight_y=straight_y, length_x=length_x, length_y_left=length_y_left + delta_symm_half, length_y_right=length_y_right + delta_symm_half + delta_yright, bend=bend, **kwargs, ) top_arm = c << _top_arm bot_arm = c << mzi_arm( straight_x=straight_x_bot, straight_y=straight_y, length_x=length_x, length_y_left=length_y_left + delta_length / 2, length_y_right=length_y_right + delta_length / 2, bend=bend, **kwargs, ) bot_arm.mirror() top_arm.connect("o1", port_e1_cp1) bot_arm.connect("o1", port_e0_cp1) cout.connect(port_e1_cp2.name, bot_arm.ports["o2"]) if with_splitter: c.add_ports(cin.get_ports_list(orientation=180), prefix="in") else: c.add_port("o1", port=bot_arm.ports["o1"]) c.add_port("o2", port=top_arm.ports["o1"]) c.add_ports(cout.get_ports_list(orientation=0), prefix="out") c.add_ports(top_arm.get_ports_list(port_type="electrical"), prefix="top") c.add_ports(bot_arm.get_ports_list(port_type="electrical"), prefix="bot") c.auto_rename_ports() return c
def rectangle_with_slits( size: Tuple[float, float] = (100.0, 200.0), layer: Layer = (1, 0), layer_slit: Optional[Layer] = (2, 0), centered: bool = False, port_type: Optional[str] = None, slit_size: Tuple[float, float] = (1.0, 1.0), slit_spacing: Float2 = (20, 20), slit_enclosure: float = 10, ) -> Component: """Returns a rectangle with slits. Metal slits reduce stress. Args: size: (tuple) Width and height of rectangle. layer: Specific layer to put polygon geometry on. layer_slit: does a boolan NOT when None. centered: True sets center to (0, 0), False sets south-west to (0, 0) port_type: for the rectangle slit_size: x, y slit size slit_spacing: pitch_x, pitch_y for slits slit_enclosure: from slit to rectangle edge .. code:: slit_enclosure _____________________________________ |<---> | | | | ______________________ | | | | | | | | slit_size[1] | |______________________| | | | | | | slit_spacing | | | | size[1] | | ______________________ | | | | | | | | | | | | | |______________________| | | <---------------------> | | slit_size[0] | |___________________________________| size[0] """ c = Component() r = rectangle(size=size, layer=layer, port_type=port_type, centered=centered) c.add_ports(r.ports) slit = rectangle(size=slit_size, port_type=None, layer=layer_slit or layer) columns = np.floor((size[0] - 2 * slit_enclosure) / slit_spacing[0]) rows = np.floor((size[1] - 2 * slit_enclosure) / slit_spacing[1]) slits = array(slit, columns=columns, rows=rows, spacing=slit_spacing).ref() slits.xmin = slit_enclosure slits.ymin = slit_enclosure if layer_slit: c << r c.add(slits) else: r_with_slits = c << gf.geometry.boolean( r, slits, operation="not", layer=layer) c.absorb(r_with_slits) return c
def contact( size: Tuple[float, float] = (11.0, 11.0), layers: Tuple[Layer, ...] = (LAYER.M1, LAYER.M2, LAYER.M3), vias: Optional[Tuple[Optional[ComponentOrFactory], ...]] = (via1, via2), layer_port: Optional[Layer] = None, ) -> Component: """Rectangular via array stack You can use it to connect different metal layers or metals to silicon. You can use the naming convention contact_layerSource_layerDestination Via array / stack name is more common for contacting metal while contact is used for contacting silicon http://www.vlsi-expert.com/2017/12/vias.html Args: size: of the layers layers: layers on which to draw rectangles vias: vias to use to fill the rectangles layer_port: if None asumes port is on the last layer """ width, height = size a = width / 2 b = height / 2 layer_port = layer_port or layers[-1] c = Component() c.height = height c.info.size = (float(size[0]), float(size[1])) c.info.layer = layer_port for layer in layers: ref = c << compass(size=(width, height), layer=layer) if layer == layer_port: c.add_ports(ref.ports) vias = vias or [] for via in vias: if via is not None: via = via() if callable(via) else via w, h = via.info["size"] g = via.info["enclosure"] pitch_x, pitch_y = via.info["spacing"] nb_vias_x = (width - w - 2 * g) / pitch_x + 1 nb_vias_y = (height - h - 2 * g) / pitch_y + 1 nb_vias_x = int(floor(nb_vias_x)) or 1 nb_vias_y = int(floor(nb_vias_y)) or 1 ref = c.add_array(via, columns=nb_vias_x, rows=nb_vias_y, spacing=(pitch_x, pitch_y)) cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2 ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 ref.move((x0, y0)) return c
def straight_pin_slot( length: float = 500.0, cross_section: CrossSectionFactory = pin, contact: ComponentFactory = contact_metal, contact_slot: ComponentFactory = contact_slot_slab, contact_width: float = 10.0, contact_spacing: float = 2, port_orientation_top: int = 0, port_orientation_bot: int = 180, taper: Optional[ComponentFactory] = taper_strip_to_ridge, **kwargs, ) -> Component: """Returns PIN doped waveguide with contacts with slotted via https://doi.org/10.1364/OE.26.029983 500um length from https://ieeexplore.ieee.org/document/8268112 Args: length: of the waveguide cross_section: for the waveguide contact: for the contacts contact_size: contact_spacing: spacing between contacts port_orientation_top: for top contact port_orientation_bot: for bottom contact taper: optional taper kwargs: cross_section settings """ c = Component() if taper: taper = taper() if callable(taper) else taper length -= 2 * taper.get_ports_xsize() wg = c << gf.c.straight( cross_section=cross_section, length=length, **kwargs, ) if taper: t1 = c << taper t2 = c << taper t1.connect("o2", wg.ports["o1"]) t2.connect("o2", wg.ports["o2"]) c.add_port("o1", port=t1.ports["o1"]) c.add_port("o2", port=t2.ports["o1"]) else: c.add_ports(wg.get_ports_list()) contact_length = length contact_top = c << contact( size=(contact_length, contact_width), ) contact_bot = c << contact( size=(contact_length, contact_width), ) contact_bot.xmin = wg.xmin contact_top.xmin = wg.xmin contact_top.ymin = +contact_spacing / 2 contact_bot.ymax = -contact_spacing / 2 slot_top = c << contact_slot( size=(contact_length, contact_width), ) slot_bot = c << contact_slot( size=(contact_length, contact_width), ) slot_bot.xmin = wg.xmin slot_top.xmin = wg.xmin slot_top.ymin = +contact_spacing / 2 slot_bot.ymax = -contact_spacing / 2 c.add_port( "e1", port=contact_top.get_ports_list(orientation=port_orientation_top)[0] ) c.add_port( "e2", port=contact_bot.get_ports_list(orientation=port_orientation_bot)[0] ) return c
def straight_heater_doped_rib( length: float = 320.0, nsections: int = 3, cross_section: CrossSectionFactory = strip_rib_tip, cross_section_heater: CrossSectionFactory = rib_heater_doped, contact: Optional[ComponentFactory] = contact_slab_npp_m3, contact_metal: Optional[ComponentFactory] = contact_metal_function, contact_metal_size: Tuple[float, float] = (10.0, 10.0), contact_size: Tuple[float, float] = (10.0, 10.0), taper: Optional[ComponentOrFactory] = taper_cross_section, with_taper1: bool = True, with_taper2: bool = True, heater_width: float = 2.0, heater_gap: float = 0.8, contact_gap: float = 0.0, width: float = 0.5, with_top_contact: bool = True, with_bot_contact: bool = True, **kwargs ) -> Component: r"""Returns a doped thermal phase shifter. dimensions from https://doi.org/10.1364/OE.27.010456 Args: length: of the waveguide nsections: between contacts cross_section: for the input/output ports cross_section_heater: for the heater contact: function to connect the heated strip contact_metal: function to connect the metal area contact_metal_size: contact_size: taper: optional taper function heater_width: heater_gap: contact_gap: from edge of contact to waveguide width: waveguide width on the ridge kwargs: cross_section settings .. code:: length |<--------------------------------------------->| | length_section | | <---------------------------> | | length_contact | | <-------> taper| | _________ _________ | | | | | | | | | contact|____________________| | | | | size | heater width | | | | /|________|____________________|________|\ | | / | heater_gap | \ | |/ |______________________________________| \ | \ |_______________width__________________| / \ | | / \|_____________heater_gap______________ |/ | | | | | |____heater_width____| | | | | | |________| |________| taper cross_section_heater |<------width------>| ____________________ heater_gap slab_gap top_contact | |<---------->| bot_contact <--> ___ ______________________| |___________________________|___ | | | undoped Si | | | | |layer_heater| intrinsic region |layer_heater | | |___|____________|__________________________________________|______________|___| <------------> heater_width <------------------------------------------------------------------------------> slab_width """ c = Component() cross_section_heater = gf.partial( cross_section_heater, heater_width=heater_width, heater_gap=heater_gap, width=width, **kwargs ) if taper: taper = ( taper(cross_section1=cross_section, cross_section2=cross_section_heater) if callable(taper) else taper ) length -= taper.get_ports_xsize() * 2 wg = c << gf.c.straight( cross_section=cross_section_heater, length=snap_to_grid(length), ) if taper: if with_taper1: taper1 = c << taper taper1.connect("o2", wg.ports["o1"]) c.add_port("o1", port=taper1.ports["o1"]) else: c.add_port("o1", port=wg.ports["o1"]) if with_taper2: taper2 = c << taper taper2.mirror() taper2.connect("o2", wg.ports["o2"]) c.add_port("o2", port=taper2.ports["o1"]) else: c.add_port("o2", port=wg.ports["o2"]) else: c.add_port("o2", port=wg.ports["o2"]) c.add_port("o1", port=wg.ports["o1"]) if contact_metal: contact_section = contact_metal(size=contact_metal_size) contacts = [] length_contact = snap_to_grid(contact_size[1]) length_section = snap_to_grid((length - length_contact) / nsections) x0 = contact_size[0] / 2 for i in range(0, nsections + 1): xi = x0 + length_section * i if contact_metal and contact: contact_center = c.add_ref(contact_section) contact_center.x = xi contact_ref = c << contact_section contact_ref.x = xi contact_ref.y = ( +contact_metal_size[1] if i % 2 == 0 else -contact_metal_size[1] ) contacts.append(contact_ref) if contact: if with_top_contact: contact_top = c << contact(size=contact_size) contact_top.x = xi contact_top.ymin = +(heater_gap + width / 2 + contact_gap) if with_bot_contact: contact_bot = c << contact(size=contact_size) contact_bot.x = xi contact_bot.ymax = -(heater_gap + width / 2 + contact_gap) if contact_metal and contact: contact_length = length + contact_metal_size[0] contact_top = c << contact_metal( size=(contact_length, contact_metal_size[0]), ) contact_bot = c << contact_metal( size=(contact_length, contact_metal_size[0]), ) contact_bot.xmin = contacts[0].xmin contact_top.xmin = contacts[0].xmin contact_top.ymin = contacts[0].ymax contact_bot.ymax = contacts[1].ymin c.add_ports(contact_top.ports, prefix="top_") c.add_ports(contact_bot.ports, prefix="bot_") return c
def contact_slot( size: Float2 = (11.0, 11.0), layers: Layers = (LAYER.M1, LAYER.M2), layer_offsets: Optional[Floats] = (0, 1.0), layer_offsetsx: Optional[Floats] = None, layer_offsetsy: Optional[Floats] = None, layer_port: Optional[Layer] = None, via: ComponentOrFactory = via1, enclosure: float = 1.0, ysize: float = 0.5, yspacing: float = 2.0, ) -> Component: """Rectangular contact with slotted via in X direction Args: size: of the layers layers: layers on which to draw rectangles layer_offsets: cladding_offset for each layer layer_offsetsx: optional xoffset for layers, defaults to layer_offsets layer_offsetsx: optional yoffset for layers, defaults to layer_offsets layer_port: if None asumes port is on the last layer via: via to use to fill the rectangles enclosure: of the via by rectangle ysize: via height in y yspacing: via spacing pitch in y .. code:: __________________________________________ | | | | | layer_offsetsy[1] | | ______________|______________________ | | |<---> |<>| | |enclosure | layer_offsetsx[1] | | ______________________ | | | | | | | | | | | via | ysize| | | | |______________________| | | | | | | | | | | yspacing size[1]| | | | | | | | | | ______________________ | | | | | | | | | | | | | via | ysize| | | | | |______________________| | | | | | | | | | | | |___________________________________| | | size[0] | | | |_________________________________________| """ if size[0] - 2 * enclosure < 0: raise ValueError( f"Contact length (size[0] = {size[0]}) < 2*enclosure ({2*enclosure}). " ) if size[1] - 2 * enclosure < 0: raise ValueError( f"Contact width (size[1] = {size[1]}) < 2*enclosure ({2*enclosure}). " ) layer_port = layer_port or layers[-1] c = Component() c.info.size = (float(size[0]), float(size[1])) layer_offsetsx = layer_offsetsx or layer_offsets layer_offsetsy = layer_offsetsy or layer_offsets layer_offsetsx = list(layer_offsetsx) + [0] * len(layers) layer_offsetsy = list(layer_offsetsy) + [0] * len(layers) for layer, offsetx, offsety in zip(layers, layer_offsetsx, layer_offsetsy): ref = c << compass(size=(size[0] + 2 * offsetx, size[1] + 2 * offsety), layer=layer) if layer == layer_port: c.add_ports(ref.ports) via = via(size=(size[0] - 2 * enclosure, ysize)) if callable(via) else via nb_vias_y = (size[1] - 2 * enclosure) / yspacing nb_vias_y = int(floor(nb_vias_y)) or 1 ref = c.add_array(via, columns=1, rows=nb_vias_y, spacing=(0, yspacing)) dy = (size[1] - (nb_vias_y - 1) * yspacing - size[1]) / 2 ref.move((0, dy)) return c
def ring_double_heater( gap: float = 0.2, radius: float = 10.0, length_x: float = 0.01, length_y: float = 0.01, coupler_ring: ComponentFactory = coupler_ring_function, straight: ComponentFactory = straight_function, bend: Optional[ComponentFactory] = None, cross_section_heater: gf.types.CrossSectionFactory = gf.cross_section. strip_heater_metal, cross_section: CrossSectionFactory = strip, contact: gf.types.ComponentFactory = contact_heater_m3_mini, port_orientation: int = 90, contact_offset: Float2 = (0, 0), **kwargs) -> Component: """Double bus ring made of two couplers (ct: top, cb: bottom) connected with two vertical straights (sl: left, sr: right) includes heater on top Args: gap: gap between for coupler radius: for the bend and coupler length_x: ring coupler length length_y: vertical straight length coupler_ring: ring coupler function straight: straight function bend: bend function cross_section_heater: cross_section: contact: port_orientation: for electrical ports to promote from contact contact_offset: for each contact kwargs: cross_section settings .. code:: --==ct==-- | | sl sr length_y | | --==cb==-- gap length_x """ assert_on_2nm_grid(gap) coupler_component = (coupler_ring(gap=gap, radius=radius, length_x=length_x, bend=bend, cross_section=cross_section, bend_cross_section=cross_section_heater, **kwargs) if callable(coupler_ring) else coupler_ring) straight_component = call_if_func(straight, length=length_y, cross_section=cross_section_heater, **kwargs) c = Component() cb = c.add_ref(coupler_component) ct = c.add_ref(coupler_component) sl = c.add_ref(straight_component) sr = c.add_ref(straight_component) sl.connect(port="o1", destination=cb.ports["o2"]) ct.connect(port="o3", destination=sl.ports["o2"]) sr.connect(port="o2", destination=ct.ports["o2"]) c.add_port("o1", port=cb.ports["o1"]) c.add_port("o2", port=cb.ports["o4"]) c.add_port("o3", port=ct.ports["o4"]) c.add_port("o4", port=ct.ports["o1"]) c1 = c << contact() c2 = c << contact() c1.xmax = -length_x / 2 + cb.x - contact_offset[0] c2.xmin = +length_x / 2 + cb.x + contact_offset[0] c1.movey(contact_offset[1]) c2.movey(contact_offset[1]) c.add_ports(c1.get_ports_list(orientation=port_orientation), prefix="e1") c.add_ports(c2.get_ports_list(orientation=port_orientation), prefix="e2") c.auto_rename_ports() return c
def straight_pin_slot( length: float = 500.0, cross_section: CrossSectionFactory = pin, contact: Optional[ComponentFactory] = contact_m1_m3, contact_width: float = 10.0, contact_slab: Optional[ComponentFactory] = contact_slot_slab_m1, contact_slab_top: Optional[ComponentFactory] = None, contact_slab_bot: Optional[ComponentFactory] = None, contact_slab_width: Optional[float] = None, contact_spacing: float = 3.0, contact_slab_spacing: float = 2.0, taper: Optional[ComponentFactory] = taper_strip_to_ridge, **kwargs, ) -> Component: """Returns a PIN straight waveguide with slotted via https://doi.org/10.1364/OE.26.029983 500um length for PI phase shift https://ieeexplore.ieee.org/document/8268112 to go beyond 2PI, you will need at least 1mm https://ieeexplore.ieee.org/document/8853396/ Args: length: of the waveguide cross_section: for the waveguide contact: for contacting the metal contact_width: contact_slab: function for the component contacting the slab contact_slab_top: Optional, defaults to contact_slab contact_slab_bot: Optional, defaults to contact_slab contact_slab_width: defaults to contact_width contact_spacing: spacing between contacts taper: optional taper kwargs: cross_section settings """ c = Component() if taper: taper = taper() if callable(taper) else taper length -= 2 * taper.get_ports_xsize() wg = c << gf.c.straight( cross_section=cross_section, length=length, **kwargs, ) contact_slab_width = contact_slab_width or contact_width contact_slab_spacing = contact_slab_spacing or contact_spacing if taper: t1 = c << taper t2 = c << taper t1.connect("o2", wg.ports["o1"]) t2.connect("o2", wg.ports["o2"]) c.add_port("o1", port=t1.ports["o1"]) c.add_port("o2", port=t2.ports["o1"]) else: c.add_ports(wg.get_ports_list()) contact_length = length if contact: contact_top = c << contact( size=(contact_length, contact_width), ) contact_bot = c << contact( size=(contact_length, contact_width), ) contact_bot.x = wg.x contact_top.x = wg.x contact_top.ymin = +contact_spacing / 2 contact_bot.ymax = -contact_spacing / 2 c.add_ports(contact_bot.ports, prefix="bot_") c.add_ports(contact_top.ports, prefix="top_") contact_slab_top = contact_slab_top or contact_slab contact_slab_bot = contact_slab_bot or contact_slab if contact_slab_top: slot_top = c << contact_slab_top( size=(contact_length, contact_slab_width), ) slot_top.x = wg.x slot_top.ymin = +contact_slab_spacing / 2 if contact_slab_bot: slot_bot = c << contact_slab_bot( size=(contact_length, contact_slab_width), ) slot_bot.x = wg.x slot_bot.ymax = -contact_slab_spacing / 2 return c