Source code for gdsfactory.get_netlist_flat

from __future__ import annotations

from collections.abc import Iterable
from typing import Any

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.get_netlist import get_netlist_recursive


[docs] def get_netlist_flat( component: Component, **kwargs, ) -> dict[str, Any]: """Parses a recursive netlist for a component as if it was a single netlist \ with its lowest-level instances. Procedure: - Recursively parse the recursive dict to generate a unique list of all instances **even if they are reused** Each entry is formatted as [(netlist, instance),...], \ and defines a unique flat_name = {top}{hierarchy_delimiter}{instance}{hierarchy_delimiter}{instance}{hierarchy_delimiter}... - Populate the flat netlist dict with similar entries as regular gdsfactory netlists: - instances: component, info, and settings from the original netlist, but keyed with flat_name - placements: For instance corresponding to each flat_name, accumulate placements across the hierarchy - ports and connections: each unique port of each flat_name instance is\ uniquely named as flat_name,port_name for each flat_name,port_name, starting at lowest hierarchy level: - list connections at that level (finding the proper other flat_name,port_name if non-leaf level) - if possible, maps the port to a port of that instance's netlist, and repeat at a higher level - if the top level is reached, assign that flat_name,port_name to a top-level component port instead \ the returned ports dict has top level component portname: flat_name,port_name mappings \ the returned connections dict is sorted and flattened into a minimal (flat_name,port_name)_1: [(flat_name,port_name)] key: value pairs \ If allow_multiple flag in get_netlist is True, the values will be lists (to support multiple connections) - name: top_level_component name Args: component: to extract flat netlist. Keyword Args: component_suffix: suffix to append to each component name, useful if to save and reload a back-annotated netlist. get_netlist_func: function to extract individual netlists. tolerance: tolerance in nm to consider two ports connected. exclude_port_types: optional list of port types to exclude from netlisting. get_instance_name: function to get instance name. Returns: instances: Dict of instance name and settings. connections: Dict of Instance1Name,portName: Instance2Name,portName. placements: Dict of instance names and placements (x, y, rotation). port: Dict portName: ComponentName,port. name: name of component. warnings: warning messages (disconnected pins). """ recursive_netlist = get_netlist_recursive(component, **kwargs) top_level_name = component.name hierarchical_instances = _flatten_hierarchy(top_level_name, recursive_netlist) connections = {} ports = {} placements = {} instances = {} for hierarchical_instance in hierarchical_instances: c, p = _map_connections_ports( hierarchical_instance, top_level_name, recursive_netlist ) for key, value in c.items(): if len(value) != 0: connections[key] = value for key, value in p.items(): if len(value) != 0: ports[key] = value placements.update( _accumulate_placements(hierarchical_instance, recursive_netlist) ) instances.update(_get_instance_info(hierarchical_instance, recursive_netlist)) return { "connections": connections, "placements": placements, "instances": instances, "ports": ports, "name": recursive_netlist[top_level_name]["name"], }
def _flat_name( hierarchy, hierarchy_delimiter: str = "~", ): """Returns a unique name for the instance from its hierarchy.""" name = "" for _, instance in hierarchy[:-1]: name += instance name += hierarchy_delimiter name += hierarchy[-1][1] return name def _get_instance_info(hierarchy, recursive_netlist): """Returns instance info from its instance data.""" return { _flat_name(hierarchy): recursive_netlist[hierarchy[-2][0]]["instances"][ hierarchy[-1][1] ] } def _get_leaf( instance_port, netlist, flat_name, all_netlists, hierarchy_delimiter: str = "~", ): """Given a instance, port and its netlist, maps ports down to the lowest hierarchy level.""" instance, port = instance_port.split(",") netlist = all_netlists[netlist]["instances"][instance]["component"] found_lower_level = netlist in all_netlists.keys() if found_lower_level: instance_port = all_netlists[netlist]["ports"][port] flat_name = f"{flat_name}{hierarchy_delimiter}{instance}" found_lower_level, flat_name = _get_leaf( instance_port, netlist, flat_name, all_netlists ) else: found_lower_level = False flat_name = f"{flat_name}{hierarchy_delimiter}{instance_port}" return found_lower_level, flat_name def _lateral_map( local_leaf_port: str, all_netlists: dict[str, Any], higher_component: str, level: int, hierarchy, hierarchy_delimiter: str = "~", ): """Returns connected ports at this hierarchical level.""" lateral_equivalencies = [] for port1, port2 in all_netlists[higher_component]["connections"].items(): flat_name_prefix = _flat_name(hierarchy[: len(hierarchy) - level - 1]) if local_leaf_port == port1 and local_leaf_port != port2: lateral_equivalencies.append( _get_leaf(port2, higher_component, flat_name_prefix, all_netlists)[1] ) elif local_leaf_port == port2 and local_leaf_port != port1: lateral_equivalencies.append( _get_leaf(port1, higher_component, flat_name_prefix, all_netlists)[1] ) return lateral_equivalencies def _map_connections_ports( hierarchy, top_name, all_netlists, hierarchy_delimiter: str = "~", ): """Returns all nodes of interest across the flat recursive netlist.""" connections = {} ports = {} # Starting point is ports of the leaf instance leaf_instance = hierarchy[-1][1] leaf_instance_name = hierarchy[-1][0].split(".")[-1] try: leaf_instance_ports = list(gf.get_component(leaf_instance_name).ports.keys()) except ValueError: leaf_instance_ports = [] for leaf_portname in leaf_instance_ports: current_connections = [] current_ports = [] local_leaf_port = f"{leaf_instance},{leaf_portname}" for level, (higher_component, higher_instance) in enumerate( hierarchy[:-1][::-1] ): # Identify connected node (with flattened names) current_connections.extend( _lateral_map( local_leaf_port, all_netlists, higher_component, level, hierarchy, hierarchy_delimiter, ) ) # Traverse up to higher level found_higher_level = False for portname, port in all_netlists[higher_component]["ports"].items(): if local_leaf_port == port: local_leaf_port = f"{higher_instance},{portname}" found_higher_level = True # If there is a port at top level, map to that as well if higher_instance == top_name: current_ports.append(local_leaf_port) if not found_higher_level: break flat_leaf_port = f"{_flat_name(hierarchy)},{leaf_portname}" connections[flat_leaf_port] = current_connections ports[flat_leaf_port] = current_ports return connections, ports def _accumulate_placements( hierarchy, all_netlists, ): """Iterate through hierarchy tuples, accumulating placement information.""" placements = {key: 0 for key in ["x", "y", "mirror", "rotation"]} for (higher_component, _higher_instance), (_lower_component, lower_instance) in zip( hierarchy[:-1], hierarchy[1:] ): for key in ["x", "y", "mirror", "rotation"]: value = ( all_netlists.get(higher_component, {}) .get("placements", {}) .get(lower_instance, {}) .get(key, None) ) if value is not None: placements[key] += value return {_flat_name(hierarchy): placements} def _flatten_str_list(xs): """Flatten nested list of strings to list of strings.""" for x in xs: if isinstance(x, Iterable) and not isinstance(x, str | bytes): yield from _flatten_str_list(x) else: yield x def _flatten_hierarchy( netlist_name: str, all_netlists: list[dict[str, Any]], hierarchy_delimiter: str = "~", component_instance_delimiter: str = ";", ): """Converts _flatten_netlist output str's to list of hierarchical (component, instance) tuples.""" instance_dict = _flatten_hierarchy_recurse( netlist_name=netlist_name, all_netlists=all_netlists, hierarchy_delimiter=hierarchy_delimiter, component_instance_delimiter=component_instance_delimiter, ) hierarchies_lists = [] for hierarchy in _flatten_str_list(list(instance_dict.keys())): hierarchy_list = [] levels = hierarchy.split(hierarchy_delimiter) for level in levels: components, instance = level.split(component_instance_delimiter) hierarchy_list.append((components, instance)) hierarchies_lists.append(hierarchy_list) return hierarchies_lists def _flatten_hierarchy_recurse( netlist_name: str, all_netlists: list[dict[str, Any]], hierarchy: str | None = None, hierarchy_delimiter: str = "~", component_instance_delimiter: str = ";", ) -> dict[str, Any]: """Flattens the provided recursive netlist by recursively updating a list. Args: netlist_name: netlist entry to flatten all_netlists: list of all possible netlists (output of get_netlist_recursive) hierarchy: str to append to netlist_name hierarchy_delimiter: str to separate hierarchy levels in flattened keys component_instance_delimiter: str to separate instance name from component name for netlist reconstruction Returns: str of ...{hierarchy_delimiter}{component}{component_instance_delimiter}{instance}{hierarchy_delimiter} used to flatten the netlist """ instance_dict = {} if hierarchy is None: top_component = all_netlists[netlist_name]["name"] hierarchy = f"{top_component}{component_instance_delimiter}{netlist_name}" for instance_name, instance in all_netlists[netlist_name]["instances"].items(): component_name = instance["component"] hierarchy_str = f"{hierarchy}{hierarchy_delimiter}{component_name}{component_instance_delimiter}{instance_name}" if component_name not in all_netlists.keys(): instance_dict[hierarchy_str] = True # Done for this leaf else: instance_dict.update( _flatten_hierarchy_recurse( component_name, all_netlists, hierarchy_str, ) ) return instance_dict if __name__ == "__main__": import pprint coupler_lengths = [10, 20, 30, 40] coupler_gaps = [0.1, 0.2, 0.4, 0.5] delta_lengths = [10, 100, 200] c = gf.components.mzi_lattice( coupler_lengths=coupler_lengths, coupler_gaps=coupler_gaps, delta_lengths=delta_lengths, ) flat_netlist = get_netlist_flat(c) print("") print("FLAT NETLIST") print("") pprint.pprint(flat_netlist) # """ # Testing electrical netlist w/ identical component references # """ # # Define compound component # series_resistors = gf.Component("seriesResistors") # rseries1 = series_resistors << gf.get_component( # gf.components.resistance_sheet, width=20, ohms_per_square=20 # ) # rseries2 = series_resistors << gf.get_component( # gf.components.resistance_sheet, width=20, ohms_per_square=20 # ) # rseries1.connect("pad2", rseries2.ports["pad1"]) # series_resistors.add_port("pad1", port=rseries1.ports["pad1"]) # series_resistors.add_port("pad2", port=rseries2.ports["pad2"]) # # Increase hierarchy levels more # double_series_resistors = gf.Component("double_seriesResistors") # rseries1 = double_series_resistors << gf.get_component(series_resistors) # rseries2 = double_series_resistors << gf.get_component(series_resistors) # rseries1.connect("pad2", rseries2.ports["pad1"]) # double_series_resistors.add_port("pad1", port=rseries1.ports["pad1"]) # double_series_resistors.add_port("pad2", port=rseries2.ports["pad2"]) # # Define top-level component # vdiv = gf.Component("voltageDivider") # r1 = vdiv << double_series_resistors # r2 = vdiv << series_resistors # r3 = ( # vdiv # << gf.get_component( # gf.components.resistance_sheet, width=20, ohms_per_square=20 # ).rotate() # ) # r4 = vdiv << gf.get_component( # gf.components.resistance_sheet, width=20, ohms_per_square=20 # ) # r1.connect("pad2", r2.ports["pad1"]) # r3.connect("pad1", r2.ports["pad1"], preserve_orientation=True) # r4.connect("pad1", r3.ports["pad2"], preserve_orientation=True) # vdiv.add_port("gnd1", port=r2.ports["pad2"]) # vdiv.add_port("gnd2", port=r4.ports["pad2"]) # vdiv.add_port("vsig", port=r1.ports["pad1"]) # vdiv.show(show_ports=True) # recursive_netlist = get_netlist_recursive(vdiv, allow_multiple=True) # import pprint # print("RECURSIVE NETLIST") # print("") # pprint.pprint(recursive_netlist) # print("") # print("OPERATION") # print("") # flat_netlist = get_netlist_flat(vdiv, allow_multiple=True) # print("") # print("FLAT NETLIST") # print("") # pprint.pprint(flat_netlist)