def add_labels( component: Component, get_label_function: Callable = get_input_label_electrical, layer_label: Layer = gf.LAYER.LABEL, gc: Optional[Component] = None, **kwargs, ) -> Component: """Returns component with labels a particular type of ports Args: component: to add labels to get_label_function: function to get label layer_label: layer_label gc: Optional grating coupler **port_settings to select Returns: original component with labels """ ports = component.get_ports_list(**kwargs) for i, port in enumerate(ports): label = get_label_function( port=port, gc=gc, gc_index=i, component_name=component.name, layer_label=layer_label, ) component.add(label) return component
def crossing_arm( width: float = 0.5, r1: float = 3.0, r2: float = 1.1, w: float = 1.2, L: float = 3.4, ) -> Component: """arm of a crossing""" c = Component() _ellipse = ellipse(radii=(r1, r2), layer=LAYER.SLAB150).ref() c.add(_ellipse) c.absorb(_ellipse) a = np.round(L + w / 2, 3) h = width / 2 taper_pts = [ (-a, h), (-w / 2, w / 2), (w / 2, w / 2), (a, h), (a, -h), (w / 2, -w / 2), (-w / 2, -w / 2), (-a, -h), ] c.add_polygon(taper_pts, layer=LAYER.WG) c.add_port( name="o1", midpoint=(-a, 0), orientation=180, width=width, layer=LAYER.WG ) c.add_port(name="o2", midpoint=(a, 0), orientation=0, width=width, layer=LAYER.WG) 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 test_manhattan_pass() -> Component: waypoints = [ [10.0, 0.0], [20.0, 0.0], [20.0, 12.0], [120.0, 12.0], [120.0, 80.0], [110.0, 80.0], ] route = round_corners(waypoints, radius=5) c = Component() c.add(route.references) return c
def _demo_manhattan_fail() -> Component: waypoints = [ [10.0, 0.0], [20.0, 0.0], [20.0, 12.0], [120.0, 12.0], [120.0, 80.0], [110.0, 80.0], ] route = round_corners(waypoints, radius=10.0, with_point_markers=False) c = Component() c.add(route.references) 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 _sample_route_sides() -> Component: c = Component() _dummy_t = _sample_route_side() sides = ["north", "south", "east", "west"] positions = [(0, 0), (400, 0), (400, 400), (0, 400)] for pos, side in zip(positions, sides): dummy_ref = _dummy_t.ref(position=pos) c.add(dummy_ref) routes, ports = route_ports_to_side(dummy_ref, side, layer=(2, 0)) for route in routes: c.add(route.references) for i, p in enumerate(ports): c.add_port(name=f"{side[0]}{i}", port=p) 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 test_manhattan_fail() -> Component: waypoints = [ [10.0, 0.0], [20.0, 0.0], [20.0, 12.0], [120.0, 12.0], [120.0, 80.0], [110.0, 80.0], ] with pytest.warns(RouteWarning): route = round_corners(waypoints, radius=10.0, with_point_markers=False) c = Component() c.add(route.references) 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 crossing_from_taper(taper=lambda: taper(width2=2.5, length=3.0)): """ Crossing based on a taper. The default is a dummy taper """ taper = taper() if callable(taper) else taper c = Component() for i, a in enumerate([0, 90, 180, 270]): _taper = taper.ref(position=(0, 0), port_id="o2", rotation=a) c.add(_taper) c.add_port(name=i, port=_taper.ports["o1"]) c.absorb(_taper) c.auto_rename_ports() return c
def copy(D: Component, prefix: str = "", suffix: str = "_copy", cache: bool = True) -> Component: """returns a deep copy of a Component. based on phidl.geometry with CellArray support """ D_copy = Component(name=f"{prefix}{D.name}{suffix}") D_copy.info = python_copy.deepcopy(D.info) for ref in D.references: if isinstance(ref, DeviceReference): new_ref = ComponentReference( ref.parent, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) new_ref.owner = D_copy elif isinstance(ref, gdspy.CellArray): new_ref = CellArray( device=ref.parent, columns=ref.columns, rows=ref.rows, spacing=ref.spacing, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) D_copy.add(new_ref) for alias_name, alias_ref in D.aliases.items(): if alias_ref == ref: D_copy.aliases[alias_name] = new_ref for port in D.ports.values(): D_copy.add_port(port=port) for poly in D.polygons: D_copy.add_polygon(poly) for label in D.labels: D_copy.add_label( text=label.text, position=label.position, layer=(label.layer, label.texttype), ) if cache: D_copy = avoid_duplicated_cells(D_copy) return D_copy
def crossing(arm: ComponentFactory = crossing_arm) -> Component: """Waveguide crossing""" cx = Component() arm = arm() if callable(arm) else arm arm_h = arm.ref() arm_v = arm.ref(rotation=90) port_id = 0 for c in [arm_h, arm_v]: cx.add(c) cx.absorb(c) for p in c.ports.values(): cx.add_port(name=port_id, port=p) port_id += 1 cx.auto_rename_ports() return cx
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 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 get_route_error( points, cross_section: Optional[CrossSection] = None, layer_path: Layer = LAYER.ERROR_PATH, layer_label: Layer = LAYER.TEXT, layer_marker: Layer = LAYER.ERROR_MARKER, references: Optional[List[ComponentReference]] = None, ) -> Route: width = cross_section.info["width"] if cross_section else 10 warnings.warn( f"Route error for points {points}", RouteWarning, ) c = Component(f"route_{uuid.uuid4()}"[:16]) path = gdspy.FlexPath( points, width=width, gdsii_path=True, layer=layer_path[0], datatype=layer_path[1], ) c.add(path) ref = ComponentReference(c) port1 = gf.Port(name="p1", midpoint=points[0], width=width) port2 = gf.Port(name="p2", midpoint=points[1], width=width) point_marker = gf.c.rectangle(size=(width * 2, width * 2), centered=True, layer=layer_marker) point_markers = [point_marker.ref(position=point) for point in points] + [ref] labels = [ gf.Label(text=str(i), position=point, layer=layer_label[0], texttype=layer_label[1]) for i, point in enumerate(points) ] references = references or [] references += point_markers return Route(references=references, ports=[port1, port2], length=-1, labels=labels)
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 test_manhattan() -> Component: top_cell = Component() inputs = [ Port("in1", (10, 5), 0.5, 90), # Port("in2", (-10, 20), 0.5, 0), # Port("in3", (10, 30), 0.5, 0), # Port("in4", (-10, -5), 0.5, 90), # Port("in5", (0, 0), 0.5, 0), # Port("in6", (0, 0), 0.5, 0), ] outputs = [ Port("in1", (290, -60), 0.5, 180), # Port("in2", (-100, 20), 0.5, 0), # Port("in3", (100, -25), 0.5, 0), # Port("in4", (-150, -65), 0.5, 270), # Port("in5", (25, 3), 0.5, 180), # Port("in6", (0, 10), 0.5, 0), ] lengths = [349.974] for input_port, output_port, length in zip(inputs, outputs, lengths): # input_port = Port("input_port", (10,5), 0.5, 90) # output_port = Port("output_port", (90,-60), 0.5, 180) # bend = bend_circular(radius=5.0) route = route_manhattan( input_port=input_port, output_port=output_port, straight=straight_function, radius=5.0, auto_widen=True, width_wide=2, # layer=(2, 0), # width=0.2, ) top_cell.add(route.references) assert np.isclose(route.length, length), route.length return top_cell
def add_labels( component: Component, get_label_function: Callable = get_input_label_electrical, layer_label: Layer = gf.LAYER.LABEL, gc: Optional[Component] = None, **kwargs, ) -> Component: """Returns component with labels on some ports Args: component: to add labels to get_label_function: function to get label layer_label: layer_label gc: Optional grating coupler keyword Args: layer: port GDS layer prefix: with in port name orientation: in degrees width: layers_excluded: List of layers to exclude port_type: optical, electrical, ... clockwise: if True, sort ports clockwise, False: counter-clockwise Returns: original component with labels """ ports = component.get_ports_list(**kwargs) for i, port in enumerate(ports): label = get_label_function( port=port, gc=gc, gc_index=i, component_name=component.name, layer_label=layer_label, ) component.add(label) return component
def add_electrical_pads_top( component: Component, 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 _demo(): """plot curvature of bends""" from matplotlib import pyplot as plt c = crossing45(port_spacing=20.0, dx=15) c2 = compensation_path(crossing45=c) print(c.info["min_bend_radius"]) print(c2.info["min_bend_radius"]) component = Component(name="top_lvl") component.add(c.ref(port_id="o1")) component.add(c2.ref(port_id="o1", position=(0, 10))) bend_info1 = c.info["components"]["bezier_bend"].info bend_info2 = c2.info["components"]["sbend"].info DL = bend_info1["length"] L2 = bend_info1["length"] plt.plot(bend_info1["t"][1:-1] * DL, abs(bend_info1["curvature"])) plt.plot(bend_info2["t"][1:-1] * L2, abs(bend_info2["curvature"])) plt.xlabel("bend length (um)") plt.ylabel("curvature (um^-1)") component.show() plt.show()
def array_with_via( component: ComponentOrFactory = pad, columns: int = 3, spacing: float = 150.0, via_spacing: float = 10.0, straight_length: float = 60.0, cross_section: Optional[CrossSectionFactory] = metal2, contact: ComponentFactory = contact_factory, contact_dy: float = 0, port_orientation: int = 180, port_offset: Optional[Float2] = None, **kwargs, ) -> Component: """Returns an array of components in X axis with fanout waveguides facing west Args: component: to replicate in the array columns: number of components spacing: for the array via_spacing: for fanout straight_length: lenght of the straight at the end waveguide: waveguide definition cross_section: contact: contact_dy: contact offset port_orientation: 180: facing west port_offset: Optional port movement kwargs: cross_section settings """ c = Component() component = component() if callable(component) else component contact = contact() for col in range(columns): ref = component.ref() ref.x = col * spacing c.add(ref) if port_orientation == 180: xlength = col * spacing + straight_length elif port_orientation == 0: xlength = columns * spacing - (col * spacing) + straight_length elif port_orientation == 270: xlength = col * via_spacing + straight_length elif port_orientation == 90: xlength = columns * via_spacing - (col * via_spacing) + straight_length else: raise ValueError( f"Invalid port_orientation = {port_orientation}", "180: west, 0: east, 90: north, 270: south", ) contact_ref = c << contact contact_ref.x = col * spacing contact_ref.y = col * via_spacing + contact_dy if cross_section: port_name = f"e{col}" straightx_ref = c << straight( length=xlength, cross_section=cross_section, **kwargs) straightx_ref.connect( "e2", contact_ref.get_ports_list(orientation=port_orientation)[0]) c.add_port(port_name, port=straightx_ref.ports["e1"]) if port_offset: c.ports[port_name].move(np.array(port_offset) * col) return c
def from_yaml( yaml_str: Union[str, pathlib.Path, IO[Any]], component_factory: ComponentFactoryDict = factory, routing_strategy: Dict[str, Callable] = routing_strategy_factories, cross_section_factory: Dict[str, CrossSectionFactory] = cross_section_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 functions {factory_name: factory_function} routing_strategy: for links label_instance_function: to label each instance kwargs: cache, prefix, autoname ... to pass to all factories Returns: Component .. code:: valid properties: name: Optional Component name vars: Optional variables info: Optional component info description: just a demo polarization: TE ... instances: name: component: (ComponentFactory) settings (Optional) length: 10 ... 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): ports to expose routes (Optional): bundles of routes routeName: library: optical links: instance1,port1: instance2,port2 .. code:: vars: lenght: 3 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: o1 x: 0 y: 0 mmi_bot: port: o1 x: mmi_top,o2 y: mmi_top,o2 dx: 30 dy: -30 routes: optical: library: optical links: mmi_top,o3: mmi_bot,o1 """ yaml_str = (io.StringIO(yaml_str) if isinstance(yaml_str, str) and "\n" in yaml_str else yaml_str) conf = OmegaConf.load( yaml_str) # nicer loader than conf = yaml.safe_load(yaml_str) for key in conf.keys(): assert key in valid_top_level_keys, f"{key} not in {list(valid_top_level_keys)}" instances = {} routes = {} name = conf.get( "name", f"Unnamed_{hashlib.md5(json.dumps(OmegaConf.to_container(conf)).encode()).hexdigest()[:8]}", ) if name in CACHE: return CACHE[name] else: c = Component(name) CACHE[name] = c placements_conf = conf.get("placements") routes_conf = conf.get("routes") ports_conf = conf.get("ports") connections_conf = conf.get("connections") instances_dict = conf["instances"] c.info = conf.get("info", omegaconf.DictConfig({})) 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())}" settings = instance_conf.get("settings", {}) settings = OmegaConf.to_container(settings, resolve=True) if settings else {} settings.update(**kwargs) if "cross_section" in settings: name_or_dict = settings["cross_section"] if isinstance(name_or_dict, str): cross_section = cross_section_factory[name_or_dict] elif isinstance(name_or_dict, dict): name = name_or_dict.pop("function") cross_section = functools.partial(cross_section_factory[name], **name_or_dict) else: raise ValueError( f"invalid type for cross_section={name_or_dict}") settings["cross_section"] = cross_section ci = component_factory[component_type](**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: warnings.warn( 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] for key in routes_dict.keys(): if key not in valid_route_keys: raise ValueError( f"`{route_alias}` key=`{key}` not in {valid_route_keys}" ) settings = routes_dict.pop("settings", {}) settings = (OmegaConf.to_container(settings, resolve=True) if settings else {}) if "cross_section" in settings: name_or_dict = settings["cross_section"] if isinstance(name_or_dict, str): cross_section = cross_section_factory[name_or_dict] elif isinstance(name_or_dict, dict): name = name_or_dict.pop("function") cross_section = functools.partial( cross_section_factory[name], **name_or_dict) else: raise ValueError( f"invalid type for cross_section={name_or_dict}") settings["cross_section"] = cross_section routing_strategy_name = routes_dict.pop("routing_strategy", "get_bundle") if routing_strategy_name not in routing_strategy: raise ValueError( f"function `{routing_strategy_name}` not in routing_strategy {list(routing_strategy.keys())}" ) 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(): 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) ] 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) routing_function = routing_strategy[routing_strategy_name] route_or_route_list = routing_function( ports1=ports1, ports2=ports2, **settings, ) # FIXME, be more consistent if isinstance(route_or_route_list, list): for route_name, route_dict in zip(route_names, route_or_route_list): c.add(route_dict.references) routes[route_name] = route_dict.length elif isinstance(route_or_route_list, Route): c.add(route_or_route_list.references) routes[route_name] = route_or_route_list.length else: raise ValueError( f"{route_or_route_list} needs to be a Route or a 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 _sample_route_sides() -> Component: c = Component() _dummy_t = _sample_route_side() sides = ["north", "south", "east", "west"] positions = [(0, 0), (400, 0), (400, 400), (0, 400)] for pos, side in zip(positions, sides): dummy_ref = _dummy_t.ref(position=pos) c.add(dummy_ref) routes, ports = route_ports_to_side(dummy_ref, side, layer=(2, 0)) for route in routes: c.add(route.references) for i, p in enumerate(ports): c.add_port(name=f"{side[0]}{i}", port=p) return c if __name__ == "__main__": c = Component() _dummy_t = _sample_route_side() sides = ["north", "south", "east", "west"] positions = [(0, 0), (400, 0), (400, 400), (0, 400)] for pos, side in zip(positions, sides): dummy_ref = _dummy_t.ref(position=pos) c.add(dummy_ref) routes, ports = route_ports_to_side(dummy_ref, side, layer=(2, 0)) for route in routes: c.add(route.references) for i, p in enumerate(ports): c.add_port(name=f"{side[0]}{i}", port=p) c.show()
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 coupler_asymmetric( bend: ComponentFactory = bend_s, straight: ComponentFactory = straight_function, gap: float = 0.234, dy: float = 5.0, dx: float = 10.0, cross_section: CrossSectionFactory = strip, **kwargs ) -> Component: """bend coupled to straight waveguide Args: bend: straight: straight library gap: um dy: port to port vertical spacing dx: bend length in x direction cross_section: **kwargs: cross_section settings .. code:: dx |-----| _____ o2 / | _____/ | gap o1____________ | dy o3 """ x = cross_section(**kwargs) width = x.info["width"] bend_component = ( bend(size=(dx, dy - gap - width), cross_section=cross_section, **kwargs) if callable(bend) else bend ) wg = ( straight(cross_section=cross_section, **kwargs) if callable(straight) else straight ) w = bend_component.ports["o1"].width y = (w + gap) / 2 c = Component() wg = wg.ref(position=(0, y), port_id="o1") bottom_bend = bend_component.ref(position=(0, -y), port_id="o1", v_mirror=True) c.add(wg) c.add(bottom_bend) # Using absorb here to have a flat cell and avoid # to have deeper hierarchy than needed c.absorb(wg) c.absorb(bottom_bend) port_width = 2 * w + gap c.add_port(name="o1", midpoint=[0, 0], width=port_width, orientation=180) c.add_port(port=bottom_bend.ports["o2"], name="o3") c.add_port(port=wg.ports["o2"], name="o2") 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
def test_connect_corner(config: str, data_regression: DataRegressionFixture, check: bool = True, N=6) -> Component: d = 10.0 sep = 5.0 c = Component(name=f"test_connect_corner_{config}") if config in ["A", "B"]: a = 100.0 ports_A_TR = [ Port("A_TR_{}".format(i), (d, a / 2 + i * sep), 0.5, 0) for i in range(N) ] ports_A_TL = [ Port("A_TL_{}".format(i), (-d, a / 2 + i * sep), 0.5, 180) for i in range(N) ] ports_A_BR = [ Port("A_BR_{}".format(i), (d, -a / 2 - i * sep), 0.5, 0) for i in range(N) ] ports_A_BL = [ Port("A_BL_{}".format(i), (-d, -a / 2 - i * sep), 0.5, 180) for i in range(N) ] ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL] ports_B_TR = [ Port("B_TR_{}".format(i), (a / 2 + i * sep, d), 0.5, 90) for i in range(N) ] ports_B_TL = [ Port("B_TL_{}".format(i), (-a / 2 - i * sep, d), 0.5, 90) for i in range(N) ] ports_B_BR = [ Port("B_BR_{}".format(i), (a / 2 + i * sep, -d), 0.5, 270) for i in range(N) ] ports_B_BL = [ Port("B_BL_{}".format(i), (-a / 2 - i * sep, -d), 0.5, 270) for i in range(N) ] ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL] elif config in ["C", "D"]: a = N * sep + 2 * d ports_A_TR = [ Port("A_TR_{}".format(i), (a, d + i * sep), 0.5, 0) for i in range(N) ] ports_A_TL = [ Port("A_TL_{}".format(i), (-a, d + i * sep), 0.5, 180) for i in range(N) ] ports_A_BR = [ Port("A_BR_{}".format(i), (a, -d - i * sep), 0.5, 0) for i in range(N) ] ports_A_BL = [ Port("A_BL_{}".format(i), (-a, -d - i * sep), 0.5, 180) for i in range(N) ] ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL] ports_B_TR = [ Port("B_TR_{}".format(i), (d + i * sep, a), 0.5, 90) for i in range(N) ] ports_B_TL = [ Port("B_TL_{}".format(i), (-d - i * sep, a), 0.5, 90) for i in range(N) ] ports_B_BR = [ Port("B_BR_{}".format(i), (d + i * sep, -a), 0.5, 270) for i in range(N) ] ports_B_BL = [ Port("B_BL_{}".format(i), (-d - i * sep, -a), 0.5, 270) for i in range(N) ] ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL] lengths = {} i = 0 if config in ["A", "C"]: for ports1, ports2 in zip(ports_A, ports_B): routes = get_bundle(ports1, ports2) for route in routes: c.add(route.references) lengths[i] = route.length i += 1 elif config in ["B", "D"]: for ports1, ports2 in zip(ports_A, ports_B): routes = get_bundle(ports2, ports1) for route in routes: c.add(route.references) lengths[i] = route.length i += 1 if check: data_regression.check(lengths) difftest(c) return c
def add_grating_couplers_with_loopback_fiber_array( component: Component, grating_coupler: ComponentFactory = grating_coupler_te, excluded_ports: None = None, grating_separation: float = 127.0, bend_radius_loopback: Optional[float] = None, gc_port_name: str = "o1", gc_rotation: int = -90, straight_separation: float = 5.0, bend: ComponentFactory = bend_euler, straight: ComponentFactory = straight_function, layer_label: Tuple[int, int] = (200, 0), layer_label_loopback: Optional[Tuple[int, int]] = None, component_name: Optional[str] = None, with_loopback: bool = True, nlabels_loopback: int = 2, get_input_labels_function: Callable = get_input_labels, cross_section: CrossSectionFactory = strip, select_ports: Callable = select_ports_optical, **kwargs, ) -> Component: """Returns a component with grating_couplers and loopback. Args: component: grating_coupler: excluded_ports: grating_separation: bend_radius_loopback: gc_port_name: gc_rotation: straight_separation: bend: straight: layer_label: component_name: with_loopback: If True, add compact loopback alignment ports nlabels_loopback: number of ports to label (0: no labels, 1: first port, 2: both ports) cross_section: **kwargs: cross_section settings """ x = cross_section(**kwargs) bend_radius_loopback = bend_radius_loopback or x.info["radius"] excluded_ports = excluded_ports or [] gc = grating_coupler() if callable(grating_coupler) else grating_coupler direction = "S" component_name = component_name or component.info_child.name c = Component() c.component = component c.add_ref(component) # Find grating port name if not specified if gc_port_name is None: gc_port_name = list(gc.ports.values())[0].name # List the optical ports to connect optical_ports = select_ports(component.ports) optical_ports = list(optical_ports.values()) optical_ports = [p for p in optical_ports if p.name not in excluded_ports] optical_ports = direction_ports_from_list_ports(optical_ports)[direction] # Check if the ports are equally spaced grating_separation_extracted = check_ports_have_equal_spacing(optical_ports) if grating_separation_extracted != grating_separation: raise ValueError( "Grating separation must be {}. Got {}".format( grating_separation, grating_separation_extracted ) ) # Add grating references references = [] for port in optical_ports: gc_ref = c.add_ref(gc) gc_ref.connect(gc.ports[gc_port_name].name, port) references += [gc_ref] labels = get_input_labels_function( io_gratings=references, ordered_ports=optical_ports, component_name=component_name, layer_label=layer_label, gc_port_name=gc_port_name, ) c.add(labels) if with_loopback: y0 = references[0].ports[gc_port_name].y xs = [p.x for p in optical_ports] x0 = min(xs) - grating_separation x1 = max(xs) + grating_separation gca1, gca2 = [ gc.ref(position=(x, y0), rotation=gc_rotation, port_id=gc_port_name) for x in [x0, x1] ] gsi = gc.size_info port0 = gca1.ports[gc_port_name] port1 = gca2.ports[gc_port_name] p0 = port0.position p1 = port1.position a = bend_radius_loopback + 0.5 b = max(2 * a, grating_separation / 2) y_bot_align_route = -gsi.width - straight_separation points = np.array( [ p0, p0 + (0, a), p0 + (b, a), p0 + (b, y_bot_align_route), p1 + (-b, y_bot_align_route), p1 + (-b, a), p1 + (0, a), p1, ] ) bend90 = bend( radius=bend_radius_loopback, cross_section=cross_section, **kwargs ) loopback_route = round_corners( points=points, bend=bend90, straight=straight, cross_section=cross_section, **kwargs, ) c.add([gca1, gca2]) c.add(loopback_route.references) component_name_loopback = f"loopback_{component_name}" if nlabels_loopback == 1: io_gratings_loopback = [gca1] ordered_ports_loopback = [port0] if nlabels_loopback == 2: io_gratings_loopback = [gca1, gca2] ordered_ports_loopback = [port0, port1] if nlabels_loopback == 0: pass elif 0 < nlabels_loopback <= 2: c.add( get_input_labels_function( io_gratings=io_gratings_loopback, ordered_ports=ordered_ports_loopback, component_name=component_name_loopback, layer_label=layer_label_loopback or layer_label, gc_port_name=gc_port_name, ) ) else: raise ValueError( f"Invalid nlabels_loopback = {nlabels_loopback}, " "valid (0: no labels, 1: first port, 2: both ports2)" ) c.copy_child_info(component) return c