def wg_heater_connector( heater_ports: List[Port], metal_width: float = 10.0, tlm_layers: List[Tuple[int, int]] = [ LAYER.VIA1, LAYER.M1, LAYER.VIA2, LAYER.M2, LAYER.VIA3, LAYER.M3, ], ) -> Component: """ Connects together a pair of wg heaters and connect to a M3 port """ cmp = Component() assert len(heater_ports) == 2 assert (heater_ports[0].orientation == heater_ports[1].orientation ), "both ports should be facing in the same direction" angle = heater_ports[0].orientation angle = angle % 360 assert angle in [0, 180], "angle should be 0 or 180, got {}".format(angle) dx = 0.0 dy = 0.0 angle_to_dps = {0: [(-dx, -dy), (-dx, dy)], 180: [(dx, -dy), (dx, dy)]} ports = heater_ports hw = heater_ports[0].width if angle in [0, 180]: ports.sort(key=lambda p: p.y) else: ports.sort(key=lambda p: p.x) _heater_to_metal = tlm(width=0.5, height=0.5, layers=tlm_layers, vias=[]) tlm_positions = [] for port, dp in zip(ports, angle_to_dps[angle]): # Extend heater p = port.midpoint # Add via/metal transitions tlm_pos = p + dp hm = _heater_to_metal.ref(position=tlm_pos) tlm_positions += [tlm_pos] cmp.add(hm) ss = 1 if angle == 0 else -1 # Connect both sides with top metal edge_metal_piece_width = 7.0 x = ss * edge_metal_piece_width / 2 top_metal_layer = tlm_layers[-1] cmp.add_polygon( line( tlm_positions[0] + (x, -hw / 2), tlm_positions[1] + (x, hw / 2), edge_metal_piece_width, ), layer=top_metal_layer, ) # Add metal port cmp.add_port( name="0", midpoint=0.5 * sum(tlm_positions) + (ss * edge_metal_piece_width / 2, 0), orientation=angle, width=metal_width, layer=top_metal_layer, port_type="dc", ) return cmp
def waveguide_heater( length: float = 10.0, width: float = 0.5, heater_width: float = 0.5, heater_spacing: float = 1.2, sstw: float = 2.0, trench_width: float = 0.5, trench_keep_out: float = 2.0, trenches: List[Dict[str, int]] = [ { "nb_segments": 2, "lane": 1, "x_start_offset": 0 }, { "nb_segments": 2, "lane": -1, "x_start_offset": 0 }, ], layer_heater: Tuple[int, int] = LAYER.HEATER, waveguide_factory: Callable = waveguide, layer_trench: Tuple[int, int] = LAYER.DEEPTRENCH, ) -> Component: """waveguide with heater .. code:: TTTTTTTTTTTTT TTTTTTTTTTTTT <-- trench HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH <-- heater ------------------------------ <-- waveguide HHHHHHHHHHHHHHHHHHHHHHHHHHHHHH <-- heater TTTTTTTTTTTTT TTTTTTTTTTTTT <-- trench .. plot:: :include-source: import pp c = pp.c.waveguide_heater() pp.plotgds(c) """ c = Component() _heater = heater(length=length, width=heater_width, layer_heater=layer_heater) y_heater = heater_spacing + (width + heater_width) / 2 heater_top = c << _heater heater_bot = c << _heater heater_top.movey(+y_heater) heater_bot.movey(-y_heater) wg = c << waveguide_factory(length=length, width=width) for i in [heater_top, heater_bot, wg]: c.absorb(i) # Add wg ports for p in wg.ports.values(): c.add_port(name=p.name, port=p) # Add heater ports for p in heater_top.ports.values(): c.add_port(name="HT" + p.name, port=p) for p in heater_bot.ports.values(): c.add_port(name="HB" + p.name, port=p) c.settings["width"] = width c.settings["heater_width"] = heater_width c.settings["heater_spacing"] = heater_spacing c.settings["length"] = length add_trenches(c, sstw, trench_width, trench_keep_out, trenches, layer_trench=layer_trench) return c
def ring_single( wg_width: float = 0.5, gap: float = 0.2, bend_radius: float = 10.0, length_x: float = 4.0, length_y: float = 0.001, coupler: Callable = coupler_ring, waveguide: Callable = waveguide_function, bend: Callable = bend_circular, pins: bool = False, ) -> Component: """Single bus ring made of a ring coupler (cb: bottom) connected with two vertical waveguides (wl: left, wr: right) two bends (bl, br) and horizontal waveguide (wg: top) Args: wg_width: waveguide width gap: gap between for coupler bend_radius: for the bend and coupler length_x: ring coupler length length_y: vertical waveguide length coupler: ring coupler function waveguide: waveguide function bend: bend function pins: add pins .. code:: bl-wt-br | | wl wr length_y | | --==cb==-- gap length_x .. plot:: :include-source: import pp c = pp.c.ring_single(wg_width=0.5, gap=0.2, length_x=4, length_y=0.1, bend_radius=5) pp.plotgds(c) """ bend_radius = float(bend_radius) assert_on_2nm_grid(gap) coupler = call_if_func(coupler, gap=gap, wg_width=wg_width, bend_radius=bend_radius, length_x=length_x) waveguide_side = call_if_func(waveguide, width=wg_width, length=length_y) waveguide_top = call_if_func(waveguide, width=wg_width, length=length_x) bend_ref = bend(width=wg_width, radius=bend_radius) if callable(bend) else bend c = Component() cb = c << coupler wl = c << waveguide_side wr = c << waveguide_side bl = c << bend_ref br = c << bend_ref wt = c << waveguide_top wl.connect(port="E0", destination=cb.ports["N0"]) bl.connect(port="N0", destination=wl.ports["W0"]) wt.connect(port="W0", destination=bl.ports["W0"]) br.connect(port="N0", destination=wt.ports["E0"]) wr.connect(port="W0", destination=br.ports["W0"]) wr.connect(port="E0", destination=cb.ports["N1"]) # just for netlist c.add_port("E0", port=cb.ports["E0"]) c.add_port("W0", port=cb.ports["W0"]) if pins: pp.add_pins_to_references(c) return c
def coupler( wg_width: float = 0.5, gap: float = 0.236, length: float = 20.007, coupler_symmetric_factory: Callable = coupler_symmetric, coupler_straight: Callable = coupler_straight, layer: Tuple[int, int] = LAYER.WG, layers_cladding: List[Tuple[int, int]] = [LAYER.WGCLAD], cladding_offset: int = 3, ) -> Component: r""" symmetric coupler Args: gap length coupler_symmetric_factory coupler_straight .. code:: W1 __ __ E1 \ / \ length / ======================= gap / \ _/ \_ W0 E0 coupler_straight coupler_symmetric_factory .. plot:: :include-source: import pp c = pp.c.coupler(gap=0.2, length=10) pp.plotgds(c) """ assert_on_1nm_grid(length) assert_on_1nm_grid(gap) c = Component() sbend = coupler_symmetric_factory( gap=gap, wg_width=wg_width, layer=layer, layers_cladding=layers_cladding, cladding_offset=cladding_offset, ) sr = c << sbend sl = c << sbend cs = c << coupler_straight( length=length, gap=gap, width=wg_width, layer=layer, layers_cladding=layers_cladding, cladding_offset=cladding_offset, ) sl.connect("W0", destination=cs.ports["W0"]) sr.connect("W0", destination=cs.ports["E0"]) c.add_port("W1", port=sl.ports["E0"]) c.add_port("W0", port=sl.ports["E1"]) c.add_port("E0", port=sr.ports["E0"]) c.add_port("E1", port=sr.ports["E1"]) c.absorb(sl) c.absorb(sr) c.absorb(cs) return c
def component_from_yaml( yaml_str: Union[str, pathlib.Path, IO[Any]], component_factory: Dict[str, Callable] = None, route_factory: Dict[str, Callable] = route_factory, link_factory: Dict[str, Callable] = link_factory, label_instance_function: Callable = _add_instance_label, **kwargs, ) -> Component: """Returns a Component defined in YAML file or string. Args: yaml: YAML IO describing Component file or string (with newlines) (instances, placements, routes, ports, connections, names) component_factory: dict of {factory_name: factory_function} route_factory: for routes link_factory: for links label_instance_function: to label each instance kwargs: cache, pins ... to pass to all factories Returns: Component .. code:: valid properties: name: name of Component instances: name: component: settings (Optional) placements: x: Optional[float, str] str can be instanceName,portName y: Optional[float, str] rotation: Optional[float] mirror: Optional[bool, float] float is x mirror axis port: Optional[str] port anchor connections (Optional): between instances ports (Optional): defines ports to expose routes (Optional): defines bundles of routes routeName: factory: optical links: instance1,port1: instance2,port2 .. code:: instances: mmi_bot: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: 10 mmi_top: component: mmi1x2 settings: width_mmi: 4.5 length_mmi: 5 placements: mmi_top: port: W0 x: 0 y: 0 mmi_bot: port: W0 x: mmi_top,E1 y: mmi_top,E1 dx: 30 dy: -30 routes: optical: factory: optical links: mmi_top,E0: mmi_bot,W0 """ yaml_str = ( io.StringIO(yaml_str) if isinstance(yaml_str, str) and "\n" in yaml_str else yaml_str ) component_factory = component_factory or component_factory_default conf = OmegaConf.load(yaml_str) # nicer loader than conf = yaml.safe_load(yaml_str) for key in conf.keys(): assert key in valid_keys, f"{key} not in {list(valid_keys)}" instances = {} routes = {} name = conf.get("name", "Unnamed") c = Component(name) placements_conf = conf.get("placements") routes_conf = conf.get("routes") ports_conf = conf.get("ports") connections_conf = conf.get("connections") instances_dict = conf["instances"] for instance_name in instances_dict: instance_conf = instances_dict[instance_name] component_type = instance_conf["component"] assert ( component_type in component_factory ), f"{component_type} not in {list(component_factory.keys())}" component_settings = instance_conf["settings"] or {} component_settings.update(**kwargs) ci = component_factory[component_type](**component_settings) ref = c << ci instances[instance_name] = ref placements_conf = dict() if placements_conf is None else placements_conf connections_by_transformed_inst = transform_connections_dict(connections_conf) components_to_place = set(placements_conf.keys()) components_with_placement_conflicts = components_to_place.intersection( connections_by_transformed_inst.keys() ) for instance_name in components_with_placement_conflicts: placement_settings = placements_conf[instance_name] if "x" in placement_settings or "y" in placement_settings: print( f"YAML defined: ({', '.join(components_with_placement_conflicts)}) " + "with both connection and placement. Please use one or the other.", ) all_remaining_insts = list( set(placements_conf.keys()).union(set(connections_by_transformed_inst.keys())) ) while all_remaining_insts: place( placements_conf=placements_conf, connections_by_transformed_inst=connections_by_transformed_inst, instances=instances, encountered_insts=list(), all_remaining_insts=all_remaining_insts, ) for instance_name in instances_dict: label_instance_function( component=c, instance_name=instance_name, reference=instances[instance_name] ) if routes_conf: for route_alias in routes_conf: route_names = [] ports1 = [] ports2 = [] routes_dict = routes_conf[route_alias] if not hasattr(routes_dict, "__items__"): print(f"Unvalid syntax for {routes_dict}\n", sample_mmis) raise ValueError(f"Unvalid syntax for {routes_dict}") for key in routes_dict.keys(): if key not in valid_route_keys: raise ValueError( f"`{route_alias}` has a key=`{key}` not in valid {valid_route_keys}" ) if "factory" not in routes_dict: raise ValueError( f"`{route_alias}` route needs `factory` : {list(route_factory.keys())}" ) route_type = routes_dict.pop("factory") assert isinstance(route_factory, dict), "route_factory needs to be a dict" assert ( route_type in route_factory ), f"factory `{route_type}` not in route_factory {list(route_factory.keys())}" route_filter = route_factory[route_type] route_settings = routes_dict.pop("settings", {}) link_function_name = routes_dict.pop("link_factory", "link_ports") assert ( link_function_name in link_factory ), f"function `{link_function_name}` not in link_factory {list(link_factory.keys())}" link_function = link_factory[link_function_name] link_settings = routes_dict.pop("link_settings", {}) if "links" not in routes_dict: raise ValueError( f"You need to define links for the `{route_alias}` route" ) links_dict = routes_dict["links"] for port_src_string, port_dst_string in links_dict.items(): # print(port_src_string) if ":" in port_src_string: src, src0, src1 = [s.strip() for s in port_src_string.split(":")] dst, dst0, dst1 = [s.strip() for s in port_dst_string.split(":")] instance_src_name, port_src_name = [ s.strip() for s in src.split(",") ] instance_dst_name, port_dst_name = [ s.strip() for s in dst.split(",") ] src0 = int(src0) src1 = int(src1) dst0 = int(dst0) dst1 = int(dst1) if src1 > src0: ports1names = [ f"{port_src_name}{i}" for i in range(src0, src1 + 1, 1) ] else: ports1names = [ f"{port_src_name}{i}" for i in range(src0, src1 - 1, -1) ] if dst1 > dst0: ports2names = [ f"{port_dst_name}{i}" for i in range(dst0, dst1 + 1, 1) ] else: ports2names = [ f"{port_dst_name}{i}" for i in range(dst0, dst1 - 1, -1) ] # print(ports1names) # print(ports2names) assert len(ports1names) == len(ports2names) route_names += [ f"{instance_src_name},{i}:{instance_dst_name},{j}" for i, j in zip(ports1names, ports2names) ] instance_src = instances[instance_src_name] instance_dst = instances[instance_dst_name] for port_src_name in ports1names: assert port_src_name in instance_src.ports, ( f"{port_src_name} not in {list(instance_src.ports.keys())}" f"for {instance_src_name} " ) ports1.append(instance_src.ports[port_src_name]) for port_dst_name in ports2names: assert port_dst_name in instance_dst.ports, ( f"{port_dst_name} not in {list(instance_dst.ports.keys())}" f"for {instance_dst_name}" ) ports2.append(instance_dst.ports[port_dst_name]) # print(ports1) # print(ports2) # print(route_names) else: instance_src_name, port_src_name = port_src_string.split(",") instance_dst_name, port_dst_name = port_dst_string.split(",") instance_src_name = instance_src_name.strip() instance_dst_name = instance_dst_name.strip() port_src_name = port_src_name.strip() port_dst_name = port_dst_name.strip() assert ( instance_src_name in instances ), f"{instance_src_name} not in {list(instances.keys())}" assert ( instance_dst_name in instances ), f"{instance_dst_name} not in {list(instances.keys())}" instance_src = instances[instance_src_name] instance_dst = instances[instance_dst_name] assert port_src_name in instance_src.ports, ( f"{port_src_name} not in {list(instance_src.ports.keys())} for" f" {instance_src_name} " ) assert port_dst_name in instance_dst.ports, ( f"{port_dst_name} not in {list(instance_dst.ports.keys())} for" f" {instance_dst_name}" ) ports1.append(instance_src.ports[port_src_name]) ports2.append(instance_dst.ports[port_dst_name]) route_name = f"{port_src_string}:{port_dst_string}" route_names.append(route_name) if link_function_name in [ "link_electrical_waypoints", "link_optical_waypoints", ]: route_dict_or_list = link_function( route_filter=route_filter, **route_settings, **link_settings, ) else: route_dict_or_list = link_function( ports1, ports2, route_filter=route_filter, **route_settings, **link_settings, ) # FIXME, make all routers to return lists if isinstance(route_dict_or_list, list): for route_name, route_dict in zip(route_names, route_dict_or_list): c.add(route_dict["references"]) routes[route_name] = route_dict["settings"] elif isinstance(route_dict_or_list, dict): c.add(route_dict_or_list["references"]) routes[route_name] = route_dict_or_list["settings"] else: raise ValueError(f"{route_dict_or_list} needs to be dict or list") if ports_conf: assert hasattr(ports_conf, "items"), f"{ports_conf} needs to be a dict" for port_name, instance_comma_port in ports_conf.items(): instance_name, instance_port_name = instance_comma_port.split(",") instance_name = instance_name.strip() instance_port_name = instance_port_name.strip() assert ( instance_name in instances ), f"{instance_name} not in {list(instances.keys())}" instance = instances[instance_name] assert instance_port_name in instance.ports, ( f"{instance_port_name} not in {list(instance.ports.keys())} for" f" {instance_name} " ) c.add_port(port_name, port=instance.ports[instance_port_name]) c.routes = routes c.instances = instances return c
def add_fiber_single( component: Component, grating_coupler: Callable = grating_coupler_te, layer_label: Tuple[int, int] = LAYER.LABEL, optical_io_spacing: int = 50, bend_factory: Callable = bend_circular, straight_factory: Callable = waveguide, taper_factory: Callable = taper, taper_length: float = 10.0, route_filter: Callable = connect_strip_way_points, min_input2output_spacing: int = 127, optical_routing_type: int = 2, with_align_ports: bool = True, component_name: Optional[str] = None, gc_port_name: str = "W0", **kwargs, ) -> Component: r"""returns component with grating ports and labels on each port can add align_ports reference structure Args: component: to connect grating_coupler: grating coupler instance, function or list of functions layer_label: LAYER.LABEL optical_io_spacing: SPACING_GC bend_factory: bend_circular straight_factory: waveguide fanout_length: None # if None, automatic calculation of fanout length max_y0_optical: None with_align_ports: True, adds loopback structures waveguide_separation: 4.0 bend_radius: BEND_RADIUS list_port_labels: None, adds TM labels to port indices in this list connected_port_list_ids: None # only for type 0 optical routing nb_optical_ports_lines: 1 force_manhattan: False excluded_ports: grating_indices: None routing_method: connect_strip gc_port_name: W0 optical_routing_type: None: autoselection, 0: no extension gc_rotation: -90 component_name: name of component taper_factory: taper .. code:: fiber ______ /| | | / | | | W0| | | | \ | | | | \|_|_|_ | xmin = 0 .. plot:: :include-source: import pp from pp.routing import add_fiber_array c = pp.c.crossing() cc = add_fiber_array(c) pp.plotgds(cc) """ component = component() if callable(component) else component gc = grating_coupler = (grating_coupler() if callable(grating_coupler) else grating_coupler) gc_port_to_edge = abs(gc.xmax - gc.ports[gc_port_name].midpoint[0]) port_width_gc = grating_coupler.ports[gc_port_name].width optical_ports = component.get_ports_list(port_type="optical") port_width_component = optical_ports[0].width if port_width_component != port_width_gc: component = add_tapers( component, taper_factory(length=taper_length, width1=port_width_gc, width2=port_width_component), ) component_name = component_name or component.name name = f"{component_name}_{grating_coupler.polarization}" elements, grating_couplers = route_fiber_single( component, component_name=component_name, optical_io_spacing=optical_io_spacing, bend_factory=bend_factory, straight_factory=straight_factory, route_filter=route_filter, grating_coupler=grating_coupler, layer_label=layer_label, optical_routing_type=optical_routing_type, min_input2output_spacing=min_input2output_spacing, gc_port_name=gc_port_name, **kwargs, ) c = Component(name=name) cr = c << component cr.rotate(90) for e in elements: c.add(e) for gc in grating_couplers: c.add(gc) for pname, p in component.ports.items(): if p.port_type != "optical": c.add_port(pname, port=p) if isinstance(grating_coupler, list): grating_couplers = [call_if_func(g) for g in grating_coupler] grating_coupler = grating_couplers[0] else: grating_coupler = call_if_func(grating_coupler) grating_couplers = [grating_coupler] if with_align_ports: length = c.ysize - 2 * gc_port_to_edge wg = c << straight_factory(length=length) wg.rotate(90) wg.xmax = (c.xmin - optical_io_spacing if abs(c.xmin) > abs(optical_io_spacing) else c.xmin - optical_io_spacing) wg.ymin = c.ymin + gc_port_to_edge gci = c << grating_coupler gco = c << grating_coupler gci.connect(gc_port_name, wg.ports["W0"]) gco.connect(gc_port_name, wg.ports["E0"]) gds_layer_label, gds_datatype_label = pd._parse_layer(layer_label) port = wg.ports["E0"] text = get_optical_text(port, grating_coupler, 0, component_name=f"loopback_{component.name}") label = pd.Label( text=text, position=port.midpoint, anchor="o", layer=gds_layer_label, texttype=gds_datatype_label, ) c.add(label) port = wg.ports["W0"] text = get_optical_text(port, grating_coupler, 1, component_name=f"loopback_{component.name}") label = pd.Label( text=text, position=port.midpoint, anchor="o", layer=gds_layer_label, texttype=gds_datatype_label, ) c.add(label) return c
def add_ports_from_markers_center( component: Component, port_layer2type=port_layer2type, port_type2layer=port_type2layer, inside: bool = False, tol=0.1, ): """add ports from polygons in certain layers markers at port center, so half of the marker goes inside and half ouside the port Args: component: to read polygons from and to write ports to port_layer2type: dict of layer to port_type port_type2layer: dict of port_type to layer inside: True-> markers inside. False-> markers at center .. code:: _______________ | | | | | | ||| | ||| | | | | __ | |_______________| __ dx < dy: port is east or west x > xc: east x < xc: west dx > dy: port is north or south y > yc: north y < yc: south dx = dy x > xc: east x < xc: west """ i = 0 xc = component.x yc = component.y xmax = component.xmax xmin = component.xmin ymax = component.ymax ymin = component.ymin for port_layer, port_type in port_layer2type.items(): port_markers = read_port_markers(component, [port_layer]) for p in port_markers.polygons: dy = p.ymax - p.ymin dx = p.xmax - p.xmin x = p.x y = p.y layer = port_type2layer[port_type] pxmax = p.xmax pxmin = p.xmin pymax = p.ymax pymin = p.ymin if dx < dy and x > xc: # east orientation = 0 width = dy if inside: x = p.xmax elif dx < dy and x < xc: # west orientation = 180 width = dy if inside: x = p.xmin elif dx > dy and y > yc: # north orientation = 90 width = dx if inside: y = p.ymax elif dx > dy and y < yc: # south orientation = 270 width = dx if inside: y = p.ymin # port markers have same width and height # check which edge (E, W, N, S) they are closer to elif pxmax > xmax - tol: # east orientation = 0 width = dy x = p.xmax elif pxmin < xmin + tol: # west orientation = 180 width = dy x = p.xmin elif pymax > ymax - tol: # north orientation = 90 width = dx y = p.ymax elif pymin < ymin + tol: # south orientation = 270 width = dx y = p.ymin else: raise ValueError(f"port marker x={x} y={y}, dx={dx}, dy={dy}") component.add_port( i, midpoint=(x, y), width=width, orientation=orientation, port_type=port_type, layer=layer, ) i += 1