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 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 import_gds( gdspath: Union[str, Path], cellname: Optional[str] = None, flatten: bool = False, snap_to_grid_nm: Optional[int] = None, name: Optional[str] = None, decorator: Optional[Callable] = None, gdsdir: Optional[Union[str, Path]] = None, **kwargs, ) -> Component: """Returns a Componenent from a GDS file. Adapted from phidl/geometry.py if any cell names are found on the component CACHE we append a $ with a number to the name Args: gdspath: path of GDS file. cellname: cell of the name to import (None) imports top cell. flatten: if True returns flattened (no hierarchy) snap_to_grid_nm: snap to different nm grid (does not snap if False) name: Optional name. Over-rides the default imported name. decorator: function to apply over the imported gds. gdsdir: optional GDS directory. kwargs: settings for the imported component (polarization, wavelength ...). """ gdspath = Path(gdsdir) / Path(gdspath) if gdsdir else Path(gdspath) if not gdspath.exists(): raise FileNotFoundError(f"No file {gdspath!r} found") metadata_filepath = gdspath.with_suffix(".yml") gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(str(gdspath)) top_level_cells = gdsii_lib.top_level() cellnames = [c.name for c in top_level_cells] if cellname is not None: if cellname not in gdsii_lib.cells: raise ValueError( f"cell {cellname} is not in file {gdspath} with cells {cellnames}" ) topcell = gdsii_lib.cells[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: raise ValueError( f"import_gds() There are multiple top-level cells in {gdspath!r}, " f"you must specify `cellname` to select of one of them among {cellnames}" ) if name: if name in CACHE or name in CACHE_IMPORTED_CELLS: raise ValueError( f"name = {name!r} already on cache. " "Please, choose a different name or set name = None. ") else: topcell.name = name if flatten: component = Component(name=name or cellname or cellnames[0]) polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): component.add_polygon(polys, layer=layer_in_gds) component = avoid_duplicated_cells(component) else: D_list = [] cell_to_device = {} for c in gdsii_lib.cells.values(): D = Component(name=c.name) D.polygons = c.polygons D.references = c.references D.name = c.name for label in c.labels: rotation = label.rotation if rotation is None: rotation = 0 label_ref = D.add_label( text=label.text, position=np.asfarray(label.position), magnification=label.magnification, rotation=rotation * 180 / np.pi, layer=(label.layer, label.texttype), ) label_ref.anchor = label.anchor D = avoid_duplicated_cells(D) D.unlock() cell_to_device.update({c: D}) D_list += [D] for D in D_list: # First convert each reference so it points to the right Device converted_references = [] for e in D.references: ref_device = cell_to_device[e.ref_cell] if isinstance(e, gdspy.CellReference): dr = DeviceReference( device=ref_device, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) elif isinstance(e, gdspy.CellArray): dr = CellArray( device=ref_device, columns=e.columns, rows=e.rows, spacing=e.spacing, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) D.references = converted_references # Next convert each Polygon # temp_polygons = list(D.polygons) # D.polygons = [] # for p in temp_polygons: # D.add_polygon(p) # Next convert each Polygon temp_polygons = list(D.polygons) D.polygons = [] for p in temp_polygons: if snap_to_grid_nm: points_on_grid = snap_to_grid(p.polygons[0], nm=snap_to_grid_nm) p = gdspy.Polygon(points_on_grid, layer=p.layers[0], datatype=p.datatypes[0]) D.add_polygon(p) component = cell_to_device[topcell] cast(Component, component) name = name or component.name component.name = name if metadata_filepath.exists(): logger.info(f"Read YAML metadata from {metadata_filepath}") metadata = OmegaConf.load(metadata_filepath) for port_name, port in metadata.ports.items(): if port_name not in component.ports: component.add_port( name=port_name, midpoint=port.midpoint, width=port.width, orientation=port.orientation, layer=port.layer, port_type=port.port_type, ) component.info = metadata.info component.info.update(**kwargs) component.name = name component.info.name = name if decorator: component_new = decorator(component) component = component_new or component if flatten: component.flatten() component.lock() return component