diff --git a/.vscode/launch.json b/.vscode/launch.json index abf2e31..a215e90 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -75,7 +75,7 @@ "--outpath", "./out/", "--technology", - "mlsi", + "dropx", "${file}" ], "console": "integratedTerminal" diff --git a/lfr/api.py b/lfr/api.py new file mode 100644 index 0000000..9f7c56b --- /dev/null +++ b/lfr/api.py @@ -0,0 +1,114 @@ +import os +from pathlib import Path +import sys +from typing import List + +from antlr4 import CommonTokenStream, FileStream, ParseTreeWalker +from lfr import parameters +from lfr.antlrgen.lfr.lfrXLexer import lfrXLexer +from lfr.antlrgen.lfr.lfrXParser import lfrXParser +from lfr.moduleinstanceListener import ModuleInstanceListener +from lfr.netlistgenerator.generator import ( + generate, + generate_dropx_library, + generate_mars_library, + generate_mlsi_library, +) +from lfr.postProcessListener import PostProcessListener + +from lfr.preprocessor import PreProcessor +from lfr.utils import print_netlist, printgraph, serialize_netlist + + +def compile_lfr( + input_files: List[str], + outpath: str = "out/", + technology: str = "dropx", + library_path: str = "./library", + no_mapping_flag: bool = False, + no_gen_flag: bool = False, + no_annotations_flag: bool = False, + pre_load: List[str] = [], +): + pre_load_file_list = pre_load + print(pre_load_file_list) + # Utilize the prepreocessor to generate the input file + preprocessor = PreProcessor(input_files, pre_load_file_list) + + if preprocessor.check_syntax_errors(): + print("Stopping compiler because of syntax errors") + sys.exit(0) + + preprocessor.process() + + print("output dir:", outpath) + print(input_files) + + rel_input_path = "pre_processor_dump.lfr" + input_path = Path(rel_input_path).resolve() + + abspath = os.path.abspath(outpath) + parameters.OUTPUT_DIR = abspath + + if os.path.isdir(abspath) is not True: + print("Creating the output directory:") + path = Path(parameters.OUTPUT_DIR) + path.mkdir(parents=True) + + library = None + # library = libraries[library_name] + + # Modifiy this to translate relative path to absolute path in the future + finput = FileStream(str(input_path)) + + lexer = lfrXLexer(finput) + + stream = CommonTokenStream(lexer) + + parser = lfrXParser(stream) + + tree = parser.skeleton() + + walker = ParseTreeWalker() + + if no_annotations_flag is True: + mapping_listener = ModuleInstanceListener() + else: + mapping_listener = PostProcessListener() + + walker.walk(mapping_listener, tree) + + mapping_listener.print_stack() + + mapping_listener.print_variables() + + if mapping_listener.currentModule is not None: + interactiongraph = mapping_listener.currentModule.FIG + printgraph(interactiongraph, mapping_listener.currentModule.name + ".dot") + + if no_gen_flag is True: + sys.exit(0) + + # Check if the module compilation was successful + if mapping_listener.success: + # Now Process the Modules Generated + # V2 generator + if technology == "dropx": + library = generate_dropx_library() + elif technology == "mars": + library = generate_mars_library() + elif technology == "mlsi": + library = generate_mlsi_library() + else: + print("Implement Library for whatever else") + pass + + if mapping_listener.currentModule is None: + raise ValueError() + if library is None: + raise ValueError() + unsized_devices = generate(mapping_listener.currentModule, library) + + for unsized_device in unsized_devices: + print_netlist(unsized_device) + serialize_netlist(unsized_device) diff --git a/lfr/cmdline.py b/lfr/cmdline.py index 41cc6f0..0ab55d5 100644 --- a/lfr/cmdline.py +++ b/lfr/cmdline.py @@ -1,26 +1,11 @@ import argparse import glob import json -from lfr.postProcessListener import PostProcessListener +from lfr.api import compile_lfr import os -import sys -from pathlib import Path - -from antlr4 import CommonTokenStream, FileStream, ParseTreeWalker import lfr.parameters as parameters -from lfr.moduleinstanceListener import ModuleInstanceListener -from lfr.antlrgen.lfr.lfrXLexer import lfrXLexer -from lfr.antlrgen.lfr.lfrXParser import lfrXParser from lfr.netlistgenerator.mappinglibrary import MappingLibrary -from lfr.netlistgenerator.generator import ( - generate, - generate_dropx_library, - generate_mars_library, - generate_mlsi_library, -) -from lfr.utils import print_netlist, printgraph, serialize_netlist -from lfr.preprocessor import PreProcessor from art import tprint @@ -84,86 +69,26 @@ def main(): ) args = parser.parse_args() - pre_load_file_list = args.pre_load - # Utilize the prepreocessor to generate the input file - preprocessor = PreProcessor(args.input, pre_load_file_list) - - if preprocessor.check_syntax_errors(): - print("Stopping compiler because of syntax errors") - sys.exit(0) - - preprocessor.process() - - print("output dir:", args.outpath) - print(args.input) - - rel_input_path = "pre_processor_dump.lfr" - input_path = Path(rel_input_path).resolve() - - abspath = os.path.abspath(args.outpath) - parameters.OUTPUT_DIR = abspath - - if os.path.isdir(abspath) is not True: - print("Creating the output directory:") - path = Path(parameters.OUTPUT_DIR) - path.mkdir(parents=True) - - library = None - # library = libraries[library_name] - - # Modifiy this to translate relative path to absolute path in the future - finput = FileStream(str(input_path)) - - lexer = lfrXLexer(finput) - - stream = CommonTokenStream(lexer) - - parser = lfrXParser(stream) - - tree = parser.skeleton() - - walker = ParseTreeWalker() - - if args.no_annotations is True: - mapping_listener = ModuleInstanceListener() - else: - mapping_listener = PostProcessListener() - - walker.walk(mapping_listener, tree) - - mapping_listener.print_stack() - - mapping_listener.print_variables() - - if mapping_listener.currentModule is not None: - interactiongraph = mapping_listener.currentModule.FIG - printgraph(interactiongraph, mapping_listener.currentModule.name + ".dot") - - if args.no_gen is True: - sys.exit(0) - - # Check if the module compilation was successful - if mapping_listener.success: - # Now Process the Modules Generated - # V2 generator - if args.technology == "dropx": - library = generate_dropx_library() - elif args.technology == "mars": - library = generate_mars_library() - elif args.technology == "mlsi": - library = generate_mlsi_library() - else: - print("Implement Library for whatever else") - pass - - if mapping_listener.currentModule is None: - raise ValueError() - if library is None: - raise ValueError() - unsized_device = generate(mapping_listener.currentModule, library) - - print_netlist(unsized_device) - serialize_netlist(unsized_device) + # Generate proxy variables for the parsed args + input_files = args.input + outpath = args.outpath + technology = args.technology + library_path = args.library + no_mapping_flag = args.no_mapping + no_gen_flag = args.no_gen + no_annotations_flag = args.no_annotations + pre_load = args.pre_load + + compile_lfr( + input_files=input_files, + outpath=outpath, + technology=technology, + library_path=library_path, + no_mapping_flag=no_mapping_flag, + no_gen_flag=no_gen_flag, + no_annotations_flag=no_annotations_flag, + pre_load=pre_load, + ) if __name__ == "__main__": diff --git a/lfr/graphmatch/interface.py b/lfr/graphmatch/interface.py index b58036b..f7f2750 100644 --- a/lfr/graphmatch/interface.py +++ b/lfr/graphmatch/interface.py @@ -1,6 +1,6 @@ from lfr.fig.annotation import DistributeAnnotation from lfr.graphmatch.figmappingmatcher import FIGMappingMatcher -from typing import Any, Dict, FrozenSet, List, Tuple +from typing import Any, Dict, FrozenSet, List, Optional, Tuple from lfr.netlistgenerator.mappinglibrary import MappingLibrary from lfr.fig.fluidinteractiongraph import FluidInteractionGraph from lfr.graphmatch.matchpattern import MatchPattern @@ -12,7 +12,7 @@ def bijective_match_node_constraints( semantic_information: Dict[str, NodeFilter], subgraph: Dict[str, str], ) -> bool: - # TODO - Check if the constraints match for the subgraph + # Check if the constraints match for the subgraph # STEP 1 - generate new unique names for each node to simplify the matching # algorihtm @@ -177,7 +177,7 @@ def bijective_match_node_constraints( def get_fig_matches( fig: FluidInteractionGraph, library: MappingLibrary -) -> List[Tuple[str, Any]]: +) -> List[Tuple[str, Dict[str, str]]]: patterns: Dict[ str, MatchPattern ] = dict() # Store the mint and the match pattern object here @@ -269,3 +269,15 @@ def get_fig_matches( continue return ret + + +def generate_single_match( + fig_subgraph, library_entry +) -> Optional[Tuple[str, Dict[str, str]]]: + + # TODO - using fig subgraph view test to see if the subgraph is a structural match + # to technology entry from the mapping library, pass back the match tuple if it is + # if it isn't then figure out how to do this separately. Also don't enable node + # filters for this step. Enabling them will cause the match to fail. + + return ("test", {"test": "test"}) diff --git a/lfr/netlistgenerator/constructiongraph.py b/lfr/netlistgenerator/constructiongraph-old.py similarity index 99% rename from lfr/netlistgenerator/constructiongraph.py rename to lfr/netlistgenerator/constructiongraph-old.py index 49e462d..409d746 100644 --- a/lfr/netlistgenerator/constructiongraph.py +++ b/lfr/netlistgenerator/constructiongraph-old.py @@ -1,7 +1,7 @@ from copy import copy from typing import Dict, List, Optional, Set, Tuple -from networkx import nx +import networkx as nx from networkx.algorithms import isomorphism from networkx.classes.digraph import DiGraph from pymint.mintcomponent import MINTComponent @@ -20,7 +20,7 @@ ) -class ConstructionGraph(nx.DiGraph): +class OLDConstructionGraph(nx.DiGraph): """Construction Graph is the proxy datastructure that we use for representing the loose connections between the fluid interaction graph and the real hardware design primitives that would be pieced together. diff --git a/lfr/netlistgenerator/constructiongraph/__init__.py b/lfr/netlistgenerator/constructiongraph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lfr/netlistgenerator/constructiongraph/constructiongraph.py b/lfr/netlistgenerator/constructiongraph/constructiongraph.py new file mode 100644 index 0000000..37020d0 --- /dev/null +++ b/lfr/netlistgenerator/constructiongraph/constructiongraph.py @@ -0,0 +1,261 @@ +from lfr.fig.fignode import Flow, Signal +from typing import List, Tuple +from lfr.fig.fluidinteractiongraph import FluidInteractionGraph +from lfr.netlistgenerator.constructiongraph.constructionnode import ConstructionNode +import networkx as nx +from enum import Enum + + +class VariantType(Enum): + SUBSTITUTION = 1 + ADDITION = 2 + + +class ConstructionGraph(nx.Graph): + """ + This class is a sub-class of networkx.DiGraph. + It acts as a proxy datastructure for generating the device netlist. + """ + + def __init__(self, id: str, fig: FluidInteractionGraph) -> None: + super().__init__() + self._id = id + self._fig = fig + self._construction_nodes: List[ConstructionNode] = [] + + @property + def ID(self) -> str: + return self._id + + def add_construction_node( + self, construction_node: ConstructionNode, variant_type: VariantType + ) -> None: + + # TODO - Just add the construction node into the graph + if variant_type == VariantType.SUBSTITUTION: + # Remove the existing construction node that has an intersecting fig cover + # with the new construction node + for cn in self._construction_nodes: + if cn.fig_cover.intersection(construction_node.fig_cover): + self.remove_construction_node(cn) + break + else: + raise ValueError( + "No construction node found with an intersecting fig cover" + ) + self._construction_nodes.append(construction_node) + self.add_node(construction_node.ID) + elif variant_type == VariantType.ADDITION: + self._construction_nodes.append(construction_node) + self.add_node(construction_node.ID) + else: + raise ValueError("Invalid variant type") + + def remove_construction_node(self, construction_node: ConstructionNode) -> None: + # Remove the construction node from the graph + self.remove_node(construction_node.ID) + self._construction_nodes.remove(construction_node) + + def generate_construction_edges(self) -> None: + """ + This method generates the connections between the nodes in the graph. + """ + # Check if the FIG cover of neighboring construction nodes + # and generate connection candidates + self._bridge_channel_networks() + # Step 1 - Generate a map where the key is a fig node and the value is a list of + # construction nodes that have a fig node in their fig_subgraph + fig_node_to_cn_map = self._generate_fignode_to_cn_map() + # Step 2 - Generate edges for between the construction node with the biggest + # fig_cover and the rest of the nodes + for fig_node in fig_node_to_cn_map: + cn_list = fig_node_to_cn_map[fig_node] + # Get the construction node with the biggest fig_cover + cn_with_biggest_fig_cover = max(cn_list, key=lambda x: len(x.fig_cover)) + # Get the rest of the construction nodes + cn_list.remove(cn_with_biggest_fig_cover) + # Generate edges between the construction nodes + for cn in cn_list: + self.add_edge(cn_with_biggest_fig_cover.ID, cn.ID) + + # Step 3 - Generate edges between the construction nodes that have fig nodes + # that are neighbors of each other + # Utilize the networkx.Graph.neighbors method to get the neighbors of each fig + # node in the fignode map + for fig_node in fig_node_to_cn_map: + fig_node_neighbors = self._fig.neighbors(fig_node) + for fig_node_neighbor in fig_node_neighbors: + # Get the construction nodes that have the neighbor fig node + cn_list = fig_node_to_cn_map[fig_node_neighbor] + # Get the construction nodes that have the original fig node + cn_list_with_fig_node = fig_node_to_cn_map[fig_node] + # Generate edges between the construction nodes + for cn in cn_list_with_fig_node: + for cn_neighbor in cn_list: + self.add_edge(cn.ID, cn_neighbor.ID) + + def _generate_fignode_to_cn_map(self): + fig_node_to_cn_map = {} + for cn in self._construction_nodes: + for fig_node in cn.fig_subgraph.nodes: + if fig_node.ID not in fig_node_to_cn_map: + fig_node_to_cn_map[fig_node.ID] = [] + fig_node_to_cn_map[fig_node.ID].append(cn) + return fig_node_to_cn_map + + def is_fig_fully_covered(self) -> bool: + """ + This method checks if the FIG is fully covered by the construction graph + """ + # Check if all the fig nodes are covered by construction nodes fig_subgraph + # Create a set of all the fig node ids + # Go through each of the construction nodes and the corresponding fig subgraph + # nodes if the fig subgraph node is not in the list of fig node ids, then the + # graph is not fully covered + fig_node_set = set([node for node in self._fig.nodes]) + for cn in self._construction_nodes: + fig_subgraph = cn.fig_subgraph + for node in fig_subgraph.nodes: + if node not in fig_node_set: + return False + else: + return True + + def check_variant_criteria( + self, node: ConstructionNode + ) -> Tuple[bool, VariantType]: + # Check if the node's fig mapping overlaps with the fig cover of the + # existing construction nodes according to the axioms definined. If it does + # return True, else return False. + for cn in self._construction_nodes: + if cn.fig_cover == node.fig_cover: + return True, VariantType.SUBSTITUTION + elif node.has_border_overlap(cn): + return True, VariantType.ADDITION + else: + return False, VariantType.ADDITION + + def generate_variant(self, new_id: str) -> "ConstructionGraph": + # Generate a variant of the construction graph + ret = ConstructionGraph(new_id, self._fig) + for cn in self._construction_nodes: + ret.add_construction_node(cn, VariantType.ADDITION) + # Get the existing edges and add them to the new graph + for edge in self.edges: + ret.add_edge(edge[0], edge[1]) + return ret + + def _bridge_channel_networks(self) -> None: + # TODO - Bridge the channel networks + # Find all the passthrough nodes in the fig + # Make a copy of the fig and eliminate all the fig nodes that are either not + # FLOW or are not covered by the construction nodes + copy_fig = self._fig.copy() + for cn in self._construction_nodes: + for fig_node in cn.fig_subgraph.nodes: + copy_fig.remove_node(fig_node.ID) + # Delete all the non-flow nodes + for node in copy_fig.nodes: + if ( + isinstance(node, Flow) is not True + and isinstance(node, Signal) is not True + ): + copy_fig.remove_node(node.ID) + + # Get all the disconnected components in the fig + components = list(nx.connected_components(copy_fig)) + # Check if each of the components are pass through or not + for component in components: + is_passthrough = self.__check_if_passthrough(component) + if is_passthrough is True: + # Generate an edge bewtween the passthrough nodes + self.__generate_edge_between_passthrough_nodes(component) + + def __check_if_passthrough(self, sub) -> bool: + """Checks if its a passthrough chain + + Args: + sub (subgraph): subgraph + + Returns: + bool: Return true if its a single chain of flow channels + """ + in_count = 0 + out_count = 0 + for node in list(sub.nodes): + inedges = list(sub.in_edges(node)) + outedges = list(sub.out_edges(node)) + if len(inedges) == 0: + in_count += 1 + if len(outedges) == 0: + out_count += 1 + + if in_count == 1 and out_count == 1: + return True + else: + return False + + def __generate_edge_between_passthrough_nodes( + self, + sub, + ) -> None: + # Get the fig node to cn map + fig_node_to_cn_map = self._generate_fignode_to_cn_map() + # Generate a pass through construciton node + # If it getting till here we know for a fact that the subgraph is a passthrough and has 1 input and 1 output node + # Get the in node and the out node + in_node = None + out_node = None + for node in list(sub.nodes): + if sub.in_degree(node) == 0: + in_node = node + if sub.out_degree(node) == 0: + out_node = node + + # Find the neighbooring fig nodes + if in_node is None or out_node is None: + raise ValueError( + "In and out nodes are not found, cannot apply passthrough connection in" + " construction graph, check passthrough candidate identification logic" + ) + in_neighbors = list(sub.neighbors(in_node)) + out_neighbors = list(sub.neighbors(out_node)) + # Find the corresponding construction nodes + in_cn_list = fig_node_to_cn_map[in_neighbors[0].ID] + out_cn_list = fig_node_to_cn_map[out_neighbors[0].ID] + # If construction nodes are the same, throw and error since we can cant have + # passthrough if its looping around + for in_cn in in_cn_list: + if in_cn in out_cn_list: + raise ValueError( + "Encountered situation where in_cn is also out_cn, cannot have self" + " loops" + ) + # Now for each of the cases 1->1 , 1->n, n->1, n->n, n->m cases generate + # connections + if len(in_cn_list) == 1 and len(out_cn_list) == 1: + # 1->1 + self.add_edge(in_cn_list[0], out_cn_list[0]) + elif len(in_cn_list) == 1 and len(out_cn_list) > 1: + # 1->n + for out_cn in out_cn_list: + self.add_edge(in_cn_list[0], out_cn) + elif len(in_cn_list) > 1 and len(out_cn_list) == 1: + # n->1 + for in_cn in in_cn_list: + self.add_edge(in_cn, out_cn_list[0]) + elif len(in_cn_list) > 1 and len(out_cn_list) == len(in_cn_list): + # n->n + for in_cn, out_cn in zip(in_cn_list, out_cn_list): + self.add_edge(in_cn, out_cn) + elif ( + len(in_cn_list) > 1 + and len(out_cn_list) > 1 + and len(in_cn_list) != len(out_cn_list) + ): + # n->m + for in_cn in in_cn_list: + for out_cn in out_cn_list: + self.add_edge(in_cn, out_cn) + + raise NotImplementedError() diff --git a/lfr/netlistgenerator/constructiongraph/constructionnode.py b/lfr/netlistgenerator/constructiongraph/constructionnode.py new file mode 100644 index 0000000..f837057 --- /dev/null +++ b/lfr/netlistgenerator/constructiongraph/constructionnode.py @@ -0,0 +1,178 @@ +from lfr.netlistgenerator.primitive import Primitive +from lfr.postprocessor.constraints import Constraint +from lfr.netlistgenerator.connectingoption import ConnectingOption +from lfr.netlistgenerator.mappingoption import MappingOption +from typing import List, Optional, Set +import networkx as nx + + +class ConstructionNode: + def __init__( + self, node_id: str, primitive: Primitive = None, subgraph_view=None + ) -> None: + self._id = node_id + self._explict_mapping_flag = False + self._fig_subgraph: nx.DiGraph = subgraph_view + self._primitive: Optional[Primitive] = primitive + + # Connection options that we want to load here + self._input_options: List[ConnectingOption] = [] + self._output_options: List[ConnectingOption] = [] + self._loading_options: List[ConnectingOption] = [] + self._carrier_options: List[ConnectingOption] = [] + + # Mapping Constraints + # These will be all the imported constraints + self._constraints: List[Constraint] = [] + + @property + def primitive(self): + return self._primitive + + @primitive.setter + def primitive(self, primitive: Primitive) -> None: + self._primitive = primitive + + @property + def fig_subgraph(self): + return self._fig_subgraph + + @fig_subgraph.setter + def fig_subgraph(self, subgraph: nx.DiGraph) -> None: + self._fig_subgraph = subgraph + + @property + def is_explictly_mapped(self) -> bool: + return self._explict_mapping_flag + + @property + def constraints(self) -> List[Constraint]: + return self._constraints + + @constraints.setter + def constraints(self, vals: List[Constraint]) -> None: + self._constraints = vals + + @property + def input_options(self) -> List[ConnectingOption]: + # return self.mapping_options.[0].primitive.input_options.... + return self._input_options + + @property + def output_options(self) -> List[ConnectingOption]: + return self._output_options + + @property + def loading_options(self) -> List[ConnectingOption]: + """Returns the list of loading options for the mapption option candidate + + Returns: + List[ConnectingOption]: List of loading options + """ + return self._loading_options + + @property + def carrier_options(self) -> List[ConnectingOption]: + """Returns the list of carrier options set for the construction node + + Returns: + List[ConnectingOption]: List of carrier options + """ + return self._carrier_options + + @property + def ID(self) -> str: + """Returns the id of the construction node + + Returns: + str: ID of the construction node + """ + return self._id + + @property + def fig_cover(self) -> Set[str]: + """Returns the cover of the figure subgraph + + Returns: + Set[str]: Cover of the figure subgraph + """ + return set(self.fig_subgraph.nodes) + + def use_explicit_mapping(self, mapping: MappingOption) -> None: + """Uses the explicit mapping option passed as the parameter + + Args: + mapping (MappingOption): MappingOption that needs to set + here explicitly + """ + # Set the flag for explicit mapping + self._explict_mapping_flag = True + # Delete all the existing mapping options + self._primitive = mapping.primitive + # Now that we have overwritten all the netlist options here + # we basically cherry pick the one little bit that we want to attach here + self.load_connection_options() + + def load_connection_options(self) -> None: + """Loads the corresponding different connecting options into + the construction node + """ + # Load the input options + if self.primitive is None: + raise Exception("Primitive not set for construction node") + + self._input_options = self.primitive.export_inputs(self.fig_subgraph) + self._output_options = self.primitive.export_outputs(self.fig_subgraph) + + options = self.primitive.export_loadings(self.fig_subgraph) + if options is None: + options = [] + + self._loading_options = options + + options = self.primitive.export_carriers(self.fig_subgraph) + if options is None: + options = [] + + self._carrier_options = options + + def merge_construction_node(self, construction_node: "ConstructionNode") -> None: + """Merges the construction node passed as an arugment into the this + construction node. + + This will let us handle the scenario where we might want to merge + construction nodes that have undercoverage and help support the future + combinatorial generation options + + + Args: + construction_node (ConstructionNode): [description] + """ + raise NotImplementedError( + "Implement this when we are trying to make combinatorial operations work" + ) + + def has_border_overlap(self, other_node: "ConstructionNode") -> bool: + """Checks if the border of the current node overlaps with the border""" + + # Step 1 - Get the intersection of the two covers, If the intersection is empty + # then we do not have a border overlap + # Step 2 - If any of thos are in the border of the subgraph (i.e. the incoming + # or outgoing edges are 0) + # then we have a border overlap + + intersection_cover = self.fig_cover.intersection(other_node.fig_cover) + if len(intersection_cover) == 0: + return False + else: + # Check if any of the intersection cover is in the border of the subgraph + for node in intersection_cover: + if ( + self.fig_subgraph.in_degree(node) == 0 + or self.fig_subgraph.out_degree(node) == 0 + ): + return True + return False + + def __str__(self) -> str: + return "Construction Node: {}".format(self.ID) diff --git a/lfr/netlistgenerator/constructiongraph/variant_generator.py b/lfr/netlistgenerator/constructiongraph/variant_generator.py new file mode 100644 index 0000000..a3f7546 --- /dev/null +++ b/lfr/netlistgenerator/constructiongraph/variant_generator.py @@ -0,0 +1,97 @@ +from lfr.netlistgenerator.gen_strategies.genstrategy import GenStrategy +from lfr.netlistgenerator.namegenerator import NameGenerator +from lfr.netlistgenerator.constructiongraph.constructionnode import ConstructionNode +from lfr.netlistgenerator.mappinglibrary import MappingLibrary +from lfr.netlistgenerator.constructiongraph.constructiongraph import ( + ConstructionGraph, +) +from typing import Dict, List, Tuple +from lfr.fig.fluidinteractiongraph import FluidInteractionGraph + + +def generate_match_variants( + matches: List[Tuple[str, Dict[str, str]]], + fig: FluidInteractionGraph, + library: MappingLibrary, + active_strategy: GenStrategy, +) -> List[ConstructionGraph]: + """ + Generate all possible match variants of a given graph. + + Args: + fig: The graph to generate match variants for. + + Returns: + A list of all possible match variants of the graph. + """ + + def generate_match_subgraph(match): + """Generate a fig subgraph view of a match.""" + nodes_list = list(match[1].keys()) + return fig.subgraph(nodes_list) + + variants: List[ConstructionGraph] = [] + cn_name_generator = NameGenerator() + # sort the matches based on the size of the dict keys + matches.sort(key=lambda x: len(x[1])) + + # TODO - take all the matches and start creating construction graphs and variants + # Loop trhough each of the matches + variant_index = 0 + + # Create the first variant here + seed_variant = ConstructionGraph(f"variant_{variant_index}", fig) + variants.append(seed_variant) + next_level_variants = [] + for match in matches: + print(match) + # For each match, create a construction node + technology_string = match[0] + node = ConstructionNode( + cn_name_generator.generate_name(f"cn_{technology_string}"), + library.get_primitive(technology_string), + generate_match_subgraph(match), + ) + + # Check if the construction node satisfies the variant criteria for each of the variants + for variant in variants: + is_variant, variant_type = variant.check_variant_criteria(node) + # IF YES: + if is_variant: + print( + "Generating new {} Variant for Match {} - Confict for fig node {}" + .format(variant_type, match, node.fig_cover) + ) + # Create a new variant of the construction graph + variant_index += 1 + new_variant = variant.generate_variant(f"variant_{variant_index}") + # Add the new variant to the list of variants + next_level_variants.append(new_variant) + # Add node to the new the variant graphs(or just the substitution variant) + new_variant.add_construction_node(node, variant_type) + # TODO - Validate if this is the best way to do it + # ELSE: + else: + # Add the node to the construction graph / leaves of the variant tree + variant.add_construction_node(node, variant_type) + + # Update the variants list with the new variants and then clear the new variants array + variants.extend(next_level_variants) + next_level_variants.clear() + + # Prune all the variants + # STEP 7 - Eliminate FLOW passthrough nodes by generating explicit matches for + # those node subgraphs + + # Delete all the variants that dont fully cover the fig + for variant in variants: + # TODO - First Bridge channel networks + if variant.is_fig_fully_covered() is not True: + variants.remove(variant) + + # TODO - Prune variants using the plugins (Figre out how to use the generation + # strategy next) + # STEP 8 - Prune variants using the plugins + # active_strategy.prune_variants(variants) + + return variants diff --git a/lfr/netlistgenerator/constructionnode.py b/lfr/netlistgenerator/constructionnode-old.py similarity index 99% rename from lfr/netlistgenerator/constructionnode.py rename to lfr/netlistgenerator/constructionnode-old.py index 5cb5117..10d05c6 100644 --- a/lfr/netlistgenerator/constructionnode.py +++ b/lfr/netlistgenerator/constructionnode-old.py @@ -4,7 +4,7 @@ from typing import List -class ConstructionNode: +class OLDConstructionNode: def __init__(self, node_id: str) -> None: self._id = node_id self._mapping_options: List[MappingOption] = [] diff --git a/lfr/netlistgenerator/gen_strategies/dropxstrategy.py b/lfr/netlistgenerator/gen_strategies/dropxstrategy.py index 52b9552..0a159ae 100644 --- a/lfr/netlistgenerator/gen_strategies/dropxstrategy.py +++ b/lfr/netlistgenerator/gen_strategies/dropxstrategy.py @@ -1,18 +1,16 @@ +# from lfr.netlistgenerator.constructiongraph import ConstructionGraph from lfr.fig.interaction import Interaction, InteractionType from lfr.netlistgenerator.dafdadapter import DAFDAdapter from lfr.fig.fluidinteractiongraph import FluidInteractionGraph -from lfr.netlistgenerator.constructiongraph import ConstructionGraph -from lfr.netlistgenerator.constructionnode import ConstructionNode +from lfr.netlistgenerator.constructiongraph.constructionnode import ConstructionNode from lfr.netlistgenerator.gen_strategies.genstrategy import GenStrategy import networkx as nx from pymint import MINTDevice class DropXStrategy(GenStrategy): - def __init__( - self, construction_graph: ConstructionGraph, fig: FluidInteractionGraph - ) -> None: - super().__init__(construction_graph, fig) + def __init__(self, fig: FluidInteractionGraph) -> None: + super().__init__(fig) def reduce_mapping_options(self) -> None: # Generate a topological order for the FIG to make sure athat we know the order of things diff --git a/lfr/netlistgenerator/gen_strategies/dummy.py b/lfr/netlistgenerator/gen_strategies/dummy.py index 6011a3b..2b99dcb 100644 --- a/lfr/netlistgenerator/gen_strategies/dummy.py +++ b/lfr/netlistgenerator/gen_strategies/dummy.py @@ -1,14 +1,13 @@ from lfr.fig.fluidinteractiongraph import FluidInteractionGraph from lfr.netlistgenerator.mappingoption import MappingOption from lfr.netlistgenerator.gen_strategies.genstrategy import GenStrategy -from lfr.netlistgenerator.constructiongraph import ConstructionGraph + +# from lfr.netlistgenerator.constructiongraph import ConstructionGraph class DummyStrategy(GenStrategy): - def __init__( - self, construction_graph: ConstructionGraph, fig: FluidInteractionGraph - ) -> None: - super().__init__(construction_graph, fig) + def __init__(self, fig: FluidInteractionGraph) -> None: + super().__init__(fig) def reduce_mapping_options(self) -> None: super().reduce_mapping_options() diff --git a/lfr/netlistgenerator/gen_strategies/genstrategy.py b/lfr/netlistgenerator/gen_strategies/genstrategy.py index 75440dd..9f4b3e8 100644 --- a/lfr/netlistgenerator/gen_strategies/genstrategy.py +++ b/lfr/netlistgenerator/gen_strategies/genstrategy.py @@ -18,36 +18,34 @@ class GenStrategy: - def __init__( - self, construction_graph: ConstructionGraph, fig: FluidInteractionGraph - ) -> None: - self._construction_graph: ConstructionGraph = construction_graph + def __init__(self, fig: FluidInteractionGraph) -> None: self._fig: FluidInteractionGraph = fig self._fig_netlist_map: Dict[str, str] = {} def reduce_mapping_options(self) -> None: # Dummy strategy - for cn in self._construction_graph.construction_nodes: - # print(len(cn.mapping_options)) - # clean this - # Remove the extra mappings - print( - "Reducing mapping options for Construction node: {} from {} to {}" - .format(cn.ID, len(cn.mapping_options), 1), - ) - if len(cn.mapping_options) > 1: - for option in cn.mapping_options: - print(" -{}".format(option.primitive.mint)) - del cn.mapping_options[1 : len(cn.mapping_options)] - # print("... -> {}".format(len(cn.mapping_options))) - - print("Printing all final mapping options:") - for cn in self._construction_graph.construction_nodes: - print("Construction node: {}".format(cn.ID)) - print("Options: ") - - for mapping_option in cn.mapping_options: - print(mapping_option.primitive.mint) + # for cn in self._construction_graph.construction_nodes: + # # print(len(cn.mapping_options)) + # # clean this + # # Remove the extra mappings + # print( + # "Reducing mapping options for Construction node: {} from {} to {}" + # .format(cn.ID, len(cn.mapping_options), 1), + # ) + # if len(cn.mapping_options) > 1: + # for option in cn.mapping_options: + # print(" -{}".format(option.primitive.mint)) + # del cn.mapping_options[1 : len(cn.mapping_options)] + # # print("... -> {}".format(len(cn.mapping_options))) + + # print("Printing all final mapping options:") + # for cn in self._construction_graph.construction_nodes: + # print("Construction node: {}".format(cn.ID)) + # print("Options: ") + + # for mapping_option in cn.mapping_options: + # print(mapping_option.primitive.mint) + pass def generate_flow_network(self, fig_subgraph_view) -> MINTDevice: # TODO - For now just assume that the networks basically are a bunch @@ -123,3 +121,9 @@ def generate_loading_connectingoptions(subgraph_view) -> List[ConnectingOption]: def size_netlist(self, device: MINTDevice) -> None: raise NotImplementedError() + + def prune_variants(self, variants: List[ConstructionGraph]) -> None: + for variant in variants: + if variant.is_fig_fully_covered() is not True: + print("Removing variant (Construction Graph): {}".format(variant.ID)) + variants.remove(variant) \ No newline at end of file diff --git a/lfr/netlistgenerator/gen_strategies/mars.py b/lfr/netlistgenerator/gen_strategies/mars.py index fa33a12..8f17498 100644 --- a/lfr/netlistgenerator/gen_strategies/mars.py +++ b/lfr/netlistgenerator/gen_strategies/mars.py @@ -1,22 +1,23 @@ -from lfr.netlistgenerator.constructiongraph import ConstructionGraph +from typing import overload +from lfr.fig.fluidinteractiongraph import FluidInteractionGraph from lfr.netlistgenerator.gen_strategies.genstrategy import GenStrategy class MARSStrategy(GenStrategy): - def __init__(self, construction_graph: ConstructionGraph) -> None: - super().__init__(construction_graph) + def __init__(self, fig: FluidInteractionGraph) -> None: + super().__init__(fig) def reduce_mapping_options(self) -> None: # TODO - Implement Generalized Ali Strategy 1 - # Dummy strategy - for cn in [v for k, v in self._construction_nodes.items()]: - print(len(cn.mapping_options)) - # Remove the extra mappings - del cn.mapping_options[1 : len(cn.mapping_options)] - print(len(cn.mapping_options)) - pass + # # Dummy strategy + # for cn in [v for k, v in self._construction_nodes.items()]: + # print(len(cn.mapping_options)) + # # Remove the extra mappings + # del cn.mapping_options[1 : len(cn.mapping_options)] + # print(len(cn.mapping_options)) + # pass + pass - @overload - def size_netlist(): + def size_netlist(self): super() diff --git a/lfr/netlistgenerator/gen_strategies/marsstrategy.py b/lfr/netlistgenerator/gen_strategies/marsstrategy.py index bf0ac70..ba7abc6 100644 --- a/lfr/netlistgenerator/gen_strategies/marsstrategy.py +++ b/lfr/netlistgenerator/gen_strategies/marsstrategy.py @@ -2,19 +2,20 @@ from typing import TYPE_CHECKING, Dict -from networkx.generators.degree_seq import directed_configuration_model from pymint.mintlayer import MINTLayerType from lfr.fig.fluidinteractiongraph import FluidInteractionGraph from lfr.netlistgenerator.dafdadapter import DAFDAdapter -from lfr.netlistgenerator.constructionnode import ConstructionNode +from lfr.netlistgenerator.constructiongraph.constructionnode import ConstructionNode from typing import Dict, TYPE_CHECKING from pymint.mintlayer import MINTLayerType if TYPE_CHECKING: - from lfr.netlistgenerator.constructiongraph import ConstructionGraph + from lfr.netlistgenerator.constructiongraph.constructiongraph import ( + ConstructionGraph, + ) from lfr.netlistgenerator.connectingoption import ConnectingOption from typing import List @@ -25,25 +26,22 @@ class MarsStrategy: - def __init__( - self, construction_graph: ConstructionGraph, fig: FluidInteractionGraph - ) -> None: - self._construction_graph: ConstructionGraph = construction_graph + def __init__(self, fig: FluidInteractionGraph) -> None: self._fig: FluidInteractionGraph = fig self._fig_netlist_map: Dict[str, str] = dict() def reduce_mapping_options(self) -> None: # Dummy strategy - for fignode_id in self._fig.nodes: - fignode = self._fig.get_fignode(fignode_id) + # for fignode_id in self._fig.nodes: + # fignode = self._fig.get_fignode(fignode_id) - if ConstructionNode(fignode_id).is_explictly_mapped: - pass - else: - if isinstance(fignode, Interaction): - cn = self._construction_graph.get_fignode_cn(fignode) + # if ConstructionNode(fignode_id).is_explictly_mapped: + # pass + # else: + # if isinstance(fignode, Interaction): + # cn = self._construction_graph.get_fignode_cn(fignode) - del cn.mapping_options[1 : len(cn.mapping_options)] + # del cn.mapping_options[1 : len(cn.mapping_options)] # for cn in self._construction_graph.construction_nodes: # # print(len(cn.mapping_options)) @@ -67,6 +65,7 @@ def reduce_mapping_options(self) -> None: # for mapping_option in cn.mapping_options: # print(mapping_option.primitive.mint) + pass def generate_flow_network(self, fig_subgraph_view) -> MINTDevice: # TODO - For now just assume that the networks basically are a bunch @@ -146,14 +145,15 @@ def size_netlist(self, device: MINTDevice) -> None: """ Sizes the device based on either lookup tables, inverse design algorithms, etc. """ - dafd_adapter = DAFDAdapter(device) - # Default size for PORT is 2000 um - for component in device.components: - constraints = self._construction_graph.get_component_cn( - component - ).constraints - if component.entity == "NOZZLE DROPLET GENERATOR": - # dafd_adapter.size_droplet_generator(component, constraints) - print("Skipping calling DAFD since its crashing everything right now") - elif component.entity == "PORT": - component.params.set_param("portRadius", 2000) + # dafd_adapter = DAFDAdapter(device) + # # Default size for PORT is 2000 um + # for component in device.components: + # constraints = self._construction_graph.get_component_cn( + # component + # ).constraints + # if component.entity == "NOZZLE DROPLET GENERATOR": + # # dafd_adapter.size_droplet_generator(component, constraints) + # print("Skipping calling DAFD since its crashing everything right now") + # elif component.entity == "PORT": + # component.params.set_param("portRadius", 2000) + pass diff --git a/lfr/netlistgenerator/generator.py b/lfr/netlistgenerator/generator.py index fc569b7..e60cef8 100644 --- a/lfr/netlistgenerator/generator.py +++ b/lfr/netlistgenerator/generator.py @@ -1,6 +1,12 @@ +from lfr.netlistgenerator.constructiongraph.constructiongraph import ( + ConstructionGraph, +) +from lfr.netlistgenerator.constructiongraph.variant_generator import ( + generate_match_variants, +) import sys from copy import deepcopy -from typing import List, Set +from typing import Dict, FrozenSet, List, Set, Tuple import networkx as nx from pymint.mintdevice import MINTDevice @@ -10,7 +16,14 @@ from lfr.netlistgenerator.gen_strategies.dropxstrategy import DropXStrategy from lfr.netlistgenerator.gen_strategies.marsstrategy import MarsStrategy from lfr.fig.fluidinteractiongraph import FluidInteractionGraph -from lfr.postprocessor.mapping import NetworkMapping, NodeMappingTemplate +from lfr.postprocessor.mapping import ( + FluidicOperatorMapping, + NetworkMapping, + NodeMappingInstance, + NodeMappingTemplate, + PumpMapping, + StorageMapping, +) from pymint.mintlayer import MINTLayerType from lfr.netlistgenerator.primitive import NetworkPrimitive, Primitive, PrimitiveType from lfr.netlistgenerator.connectingoption import ConnectingOption @@ -23,8 +36,9 @@ from lfr.fig.fignode import IOType, Pump, Storage, ValueNode from lfr.netlistgenerator.namegenerator import NameGenerator from lfr.netlistgenerator.gen_strategies.dummy import DummyStrategy -from lfr.netlistgenerator.constructionnode import ConstructionNode -from lfr.netlistgenerator.constructiongraph import ConstructionGraph +from lfr.netlistgenerator.constructiongraph.constructionnode import ConstructionNode + +# from lfr.netlistgenerator.constructiongraph import ConstructionGraph from lfr.fig.interaction import ( FluidIntegerInteraction, FluidNumberInteraction, @@ -32,7 +46,7 @@ ) from lfr.netlistgenerator.mappingoption import MappingOption from lfr.compiler.module import Module - +import itertools # def generate_MARS_library() -> MappingLibrary: # # TODO - Programatically create each of the items necessary for the MARS @@ -961,527 +975,214 @@ def generate_dropx_library() -> MappingLibrary: return library -def generate(module: Module, library: MappingLibrary) -> MINTDevice: - - construction_graph = ConstructionGraph() +def generate(module: Module, library: MappingLibrary) -> List[MINTDevice]: - name_generator = NameGenerator() + # In order to create the device, we do the following + # STEP 1 - + # STEP 2 - Initialize the active strategy + # STEP 3 - Get all the technology mapping matches for the FIG + # STEP 4 - Eliminate the matches that are exactly the same as the explicit matches + # STEP 5 - Generate the waste outputs + # STEP 6 - Generate the mapping variants + # STEP 7 - Generate the control logic network + # STEP 8 - Generate the connections + # STEP 9 - Size the components + # STEP 10 - Size the connections - cur_device = MINTDevice(module.name) + # construction_graph = ConstructionGraph() - # Add a MINT Layer so that the device has something to work with - cur_device.create_mint_layer("0", "0", 0, MINTLayerType.FLOW) + # Step 1 - + # STEP 2 - Initialize the active strategy # TODO - I need to change this DummyStrategy later on if library.name == "dropx": - active_strategy = DropXStrategy(construction_graph, module.FIG) + active_strategy = DropXStrategy(module.FIG) elif library.name == "mars": # raise NotImplementedError() - active_strategy = MarsStrategy(construction_graph, module.FIG) + active_strategy = MarsStrategy(module.FIG) elif library.name == "hmlp": raise NotImplementedError() else: - active_strategy = DummyStrategy(construction_graph, module.FIG) - - # First go through all the interactions in the design + active_strategy = DummyStrategy(module.FIG) - # IF interaction is mix/sieve/divide/dilute/meter look at the library - # to get all the options available and set them up as options for the - # construction graph to pick and choose from the options. - # - # FUTURE WORK - # + # STEP 3 - Get all the technology mapping matches for the FIG # Do the reggie matching to find the mapping options # This means that we might need to have a forest of construction of graphs # as there would be alternatives for each type of mapping matches = get_fig_matches(module.FIG, library) + print("Total Matches against library : {}".format(len(matches))) for match in matches: + # Generate an object that is usable going forward (mapping template perhaps) print(match) - # Map the interactions in the fig to individual library options - for interaction in module.FIG.get_interactions(): - operator_candidates = library.get_operators(interaction_type=interaction.type) - cn = ConstructionNode(interaction.ID) - # if isinstance(interaction, ValueNode): - # continue - - for operator_candidate in operator_candidates: - # TODO: This will change in the future when we can match subgraphs correctly - if isinstance( - interaction, (FluidNumberInteraction, FluidIntegerInteraction) - ): - # Basically add the value node id into the subgraph view also - node_ids = [ - module.FIG.get_fignode(edge[0]).ID - for edge in module.FIG.in_edges(interaction.ID) - if isinstance(module.FIG.get_fignode(edge[0]), ValueNode) - ] - node_ids.append(interaction.ID) - sub_graph = module.FIG.subgraph(node_ids) - else: - sub_graph = module.FIG.subgraph(interaction.ID) - mapping_option = MappingOption(operator_candidate, sub_graph) - cn.add_mapping_option(mapping_option) + # STEP 4 - Eliminate the matches that are exactly the same as the explicit matches + # Get the explicit mapping and find the explicit mappings here + explicit_mappings = module.get_explicit_mappings() + matches = eliminate_explicit_match_alternates(matches, explicit_mappings) - construction_graph.add_construction_node(cn) + print( + "Total matches against library after explicit mapping eliminations: {}".format( + len(matches) + ) + ) + for match in matches: + print(match) - # Generate all ports necessary for the Explicitly declared IO - # ------- - # Generate the flow layer IO. These are typically declared explicitly - # TODO - Figure out how we should generate the construction nodes for control - # networks + # STEP 5 - Generate the waste outputs + # TODO - Add fignodes to all the orphaned flow nodes for this to function + # connect_orphan_IO() - for io_ref in module.io: - if io_ref.type is IOType.CONTROL: - continue - for io in io_ref.vector_ref: - cn = ConstructionNode(io.ID) - sub_graph = module.FIG.subgraph(io.ID) - mapping_candidate = library.get_default_IO() - mapping_option = MappingOption(mapping_candidate, sub_graph) - cn.add_mapping_option(mapping_option) - - construction_graph.add_construction_node(cn) - - # Map the storage and pump elements to their own individual construction graph nodes - for fig_node_id in list(module.FIG.nodes): - fig_node = module.FIG.get_fignode(fig_node_id) - if isinstance(fig_node, Pump): - cn = ConstructionNode(fig_node.ID) - sub_graph = module.FIG.subgraph(fig_node_id) - mapping_candidates = library.get_pump_entries() - for mapping_candidate in mapping_candidates: - mapping_option = MappingOption(mapping_candidate, sub_graph) - cn.add_mapping_option(mapping_option) - - elif isinstance(fig_node, Storage): - cn = ConstructionNode(fig_node.ID) - sub_graph = module.FIG.subgraph(fig_node_id) - mapping_candidates = library.get_storage_entries() - for mapping_candidate in mapping_candidates: - mapping_option = MappingOption(mapping_candidate, sub_graph) - cn.add_mapping_option(mapping_option) - - # TODO - Validate if this is a legit way to do things - mappings = module.get_explicit_mappings() - override_network_mappings(mappings, library, module.FIG, construction_graph) - - # TODO - Go through the different flow-flow edge networks to generate construction - # nodes specific to these networks, Conditions: - # if its a 1-1 flow-flow connection, then create a construction node for the two - # flow nodes - # if its a 1-n / n-1 / n-n construction nodes, then create a construction node - # capturing the whole network - - # TODO - Deal with coverage issues here since we need to figure out what are the - # flow networks, that we want to match first and then ensure that they're no - # included on any list - cn_nodes = get_flow_flow_candidates(module, active_strategy) - for cn in cn_nodes: - construction_graph.add_construction_node(cn) - - # Apply all the explicit mappings in the module to the nodes, overwriting - # the options from the library to match against - # TODO - Modify Explicit Mapping Data structure - - # Find all the explicit mappings and override them in the construction graph - override_mappings(mappings, library, module.FIG, construction_graph) - - # Whittle Down the mapping options here to only include the requried single - # candidates - # TODO - Check what library is being used and use the required library here - active_strategy.reduce_mapping_options() - - # TODO - Consider what needs to get done for a combinatorial design space - # ---------------- - # Generate edges in the construction graph, these edges will guide the generation/ - # reduction of path and pipelineing that needs to get done for mars devices - construction_graph.generate_edges(module.FIG) - - # TODO - Extract all pass through networks - eliminate_passthrough_nodes(construction_graph) - - # Now since all the mapping options are finalized Extract the netlist necessary - construction_graph.construct_components(name_generator, cur_device) - - # TODO - Rewrite this whole thing !!! - construction_graph.construct_connections(name_generator, cur_device) - - # Finally join all the netlist pieces attached to the construction nodes - # and the input/output/load/carrier flows - # TODO - MINIMIZE - carrier / load flows - this might require us to generate - # multiple netlist options and pick the best - construction_graph.generate_flow_cn_edges(module) - - construction_graph.generate_control_cn_edges(module) - - # Generate all the unaccounted carriers and waste output lines necessary - # for this to function - connect_orphan_IO() - - # Size the component netlist - active_strategy.size_netlist(cur_device) - - return cur_device - - -def override_mappings( - mappings: List[NodeMappingTemplate], - mapping_library: MappingLibrary, - fig: FluidInteractionGraph, - construction_graph: ConstructionGraph, -) -> None: - # Go through the entire set of mappings in the FIG and generate / append the - # mapping options - # Step 1 - Loop through each of the mappingtemplates - # Step 2 - Loop through each of the instances in teh mappingtemplate - # Step 3 - Find the cn associated with each of the fig nodes and override - # the explicit mapping if mappingtemplate has an associated technology string - for mapping in mappings: - for instance in mapping.instances: - - primitive_to_use = None - if mapping.technology_string is not None: - # Create a mapping option from the library with the corresponding info - primitive_to_use = mapping_library.get_primitive( - mapping.technology_string - ) + # STEP 6 - Generate the mapping variants + variants = generate_match_variants(matches, module.FIG, library, active_strategy) - node_ids = [] - cn = None # Get the right construction node for doing the stuff - cn_mapping_options = [] + # Now generate the devices for each of the variants + generated_devices = [] + for variant in variants: + # Create the device for each of the variants + name_generator = NameGenerator() - if isinstance(instance, NetworkMapping): - print( - "Skipping Network Mapping: \n Input - {} \n Output - {}".format( - ",".join([n.ID for n in instance.input_nodes]), - ",".join([n.ID for n in instance.output_nodes]), - ) - ) - continue - else: - print("Applying Network Mapping: \n Nodes - {}".format(instance.node)) - - # Find the construction node assicated with the - # FIG node and then do the followinging: - # Step 1 - If the mappingtemplate has no technology string assiciated - # with the mapping, just apply the constraints to the associated mapping - # options - - # Step 2 - In there is a string assiciated with the mappingtemplate, we - # eliminate all mapping options that dont have a matching string / - # generate a mapping option with the corresponding - - # In the case of an Fluid Value interaction put all valuenodes in the - # subgraph - node_ids.extend( - [ - fig.get_fignode(edge[0]).ID - for edge in fig.in_edges(instance.node.ID) - if isinstance(fig.get_fignode(edge[0]), ValueNode) - ] - ) - node_ids.append(instance.node.ID) - subgraph = fig.subgraph(node_ids) - - # Get the Construction node that has the corresponding subgraph, - # and then replace the mapping option - cn = construction_graph.get_subgraph_cn(subgraph) - if primitive_to_use is not None: - mapping_option = MappingOption(primitive_to_use, subgraph) - cn.use_explicit_mapping(mapping_option) - cn_mapping_options.append(mapping_option) - else: - # Add the constraints to all the mapping options - # This is an example where since no explicit mapping - # was specified, we only add the performance/material - # constraints. This can be ulitized for whittling down - # options later if necessary. - cn_mapping_options.extend(cn.mapping_options) - - # Now that we know what the mapping options are (either explicit - # loaded from the library, we can add the performance constraints) - for mapping_option in cn_mapping_options: - # Add all the constraints to the mapping_option - cn.constraints.extend(mapping.constraints) - - -def override_network_mappings( - mappings: List[NodeMappingTemplate], - mapping_library: MappingLibrary, - fig: FluidInteractionGraph, - construction_graph: ConstructionGraph, -) -> None: - # Go through the entire set of mappings in the FIG and generate / append the - # mapping options - # Step 1 - Loop through each of the mappingtemplates - # Step 2 - Loop through each of the instances in teh mappingtemplate - # Step 3 - Find the cn associated with each of the fig nodes and override the - # explicit mapping if mappingtemplate has an associated technology string - assign_node_index = 0 - for mapping in mappings: - for instance in mapping.instances: - - primitive_to_use = None - if mapping.technology_string is not None: - # Create a mapping option from the library with the corresponding info - try: - primitive_to_use = mapping_library.get_primitive( - mapping.technology_string - ) - except Exception: - print( - "Could not find primitive with technology: {}".format( - mapping.technology_string - ) - ) - sys.exit(-100) + cur_device = MINTDevice(module.name) - node_ids = [] - cn = None # Get the right construction node for doing the stuff - cn_mapping_options = [] + # Add a MINT Layer so that the device has something to work with + cur_device.create_mint_layer("0", "0", 0, MINTLayerType.FLOW) - if isinstance(instance, NetworkMapping): - print( - "Applying Network Mapping: \n Input - {} \n Output - {}".format( - ",".join([n.ID for n in instance.input_nodes]), - ",".join([n.ID for n in instance.output_nodes]), - ) - ) + generate_device(variant, cur_device, name_generator) + # STEP 7 - Generate the control logic network + # TODO - Whatever this entails (put in the implementation) - node_ids.extend(n.ID for n in instance.input_nodes) - node_ids.extend(n.ID for n in instance.output_nodes) - subgraph = fig.subgraph(node_ids) - # try: - # # TODO - Incase this is a flow-flow candidate, we need to get the - # # cn corresponding to this mapping. - # # TODO - do we need to have a new flow node constructed - - # cn = construction_graph.get_subgraph_cn(subgraph) - # except Exception as e: - # # Incase we cannot find a corresponding construction node, - # # we need to create a new construction node - # print(e) - # cn = ConstructionNode("assign_{}".format(assign_node_index)) - # # Increment the index of the assign construction node - # assign_node_index += 1 - - # # Find the cn's associated with the input nodes - # input_cns = [] - # output_cns = [] - # for fig_node in instance.input_nodes: - # cn_temp = construction_graph.get_fignode_cn(fig_node) - # if cn_temp not in input_cns: - # input_cns.append(cn_temp) - # for fig_node in instance.output_nodes: - # cn_temp = construction_graph.get_fignode_cn(fig_node) - # if cn_temp not in output_cns: - # output_cns.append(cn_temp) - - # # split_groups = [] - # # # TODO - If we need to split the we first are gonna make a copy - # # # of the subgraph and then delete any edges between the inputs - # # # and the outputs - # # subgraph_copy = deepcopy(subgraph) - # # # Delete any edges between inputs and outputs - # # for input_node in instance.input_nodes: - # # for output_node in instance.output_nodes: - # # if subgraph_copy.has_edge(input_node.id, output_node.id): - # # subgraph_copy.remove_edge(input_node.id, output_node.id) - - # # components = subgraph_copy.connected_components() - # # for component in components: - # # split_groups.append(list(component.nodes)) - - # # TODO - If inputcns and output cns are the same, we split them - # for input_cn in input_cns: - # if input_cn in output_cns: - # split_groups = generate_split_groups( - # input_cn.mapping_options[0].fig_subgraph, instance - # ) - # construction_graph.split_cn(input_cn, split_groups, fig) - # # Now insert the node - # construction_graph.insert_cn(cn, input_cns, output_cns) - - # # Check to see if this works or not - # cn = construction_graph.get_subgraph_cn(subgraph) - - # Find the cn's associated with the input nodes - input_cns = [] - output_cns = [] - for fig_node in instance.input_nodes: - cn_temp = construction_graph.get_fignode_cn(fig_node) - if cn_temp not in input_cns: - input_cns.append(cn_temp) - for fig_node in instance.output_nodes: - cn_temp = construction_graph.get_fignode_cn(fig_node) - if cn_temp not in output_cns: - output_cns.append(cn_temp) - - cn = ConstructionNode("assign_{}".format(assign_node_index)) - assign_node_index += 1 - if isinstance(primitive_to_use, NetworkPrimitive) is not True: - raise TypeError() - if primitive_to_use is None: - raise ValueError() - mapping_option = NetworkMappingOption( - network_primitive=primitive_to_use, - mapping_type=NetworkMappingOptionType.COMPONENT_REPLACEMENT, - subgraph_view=subgraph, - ) - cn.use_explicit_mapping(mapping_option) - construction_graph.insert_cn(cn, input_cns, output_cns, fig) - else: - continue - # Now that we know what the mapping options are (either explicit - # loaded from the library, we can add the performance constraints) - for mapping_option in cn_mapping_options: - # Add all the constraints to the mapping_option - cn.constraints.extend(mapping.constraints) - - -def eliminate_passthrough_nodes(construction_graph: ConstructionGraph): - for node_id in list(construction_graph.nodes): - cn = construction_graph.get_cn(node_id) - assert len(cn.mapping_options) == 1 - mapping_option = cn.mapping_options[0] - if isinstance(mapping_option, NetworkMappingOption): - if mapping_option.mapping_type is NetworkMappingOptionType.PASS_THROUGH: - - print("Eliminating PASS THROUGH construction node = {}".format(cn.ID)) - - # First get all the in and out edges - in_edges = list(construction_graph.in_edges(node_id)) - out_edges = list(construction_graph.out_edges(node_id)) - - # In Points - in_points = [in_edge[0] for in_edge in in_edges] - out_points = [out_edge[1] for out_edge in out_edges] - - # Create edges for the different cases - # Case 1 - 1->1 - if len(in_points) == 1 and len(out_points) == 1: - # Delete the node - construction_graph.delete_node(node_id) - construction_graph.add_edge(in_points[0], out_points[0]) - # Case 2 - n->1 - # Case 3 - 1->n - elif (len(in_points) > 1 and len(out_points) == 1) or ( - len(in_points) == 1 and len(out_points) > 1 - ): - # Delete the node - construction_graph.delete_node(node_id) - for in_point in in_points: - for out_point in out_points: - construction_graph.add_edge(in_point, out_point) - else: - raise Exception( - "Pass through network node elimination not implemented " - " when n->n edge creation is necessary" - ) + # STEP 8 - Generate the connections + # TODO - Generate connections between all the outputs and the inputs of the + # construction nodes + # TODO - write the algorithm for carriers and optimize the flows + # Generate all the unaccounted carriers and waste output lines necessary + # STEP 9 - Size the components + # Size the component netlist + # active_strategy.size_netlist(cur_device) -def generate_split_groups(subgraph, instance) -> List[Set[str]]: - split_groups = [] - # TODO - If we need to split the we first are gonna make a copy - # of the subgraph and then delete any edges between the inputs - # and the outputs - subgraph_copy = deepcopy(subgraph) - # Delete delete all the input and output nodes here - for input_node in instance.input_nodes: - subgraph_copy.remove_node(input_node.id) + generated_devices.append(cur_device) - for output_node in instance.output_nodes: - subgraph_copy.remove_node(output_node.id) + return generated_devices - components = nx.connected_components(nx.to_undirected(subgraph_copy)) - for component in components: - split_groups.append(component) - return split_groups +def eliminate_explicit_match_alternates( + matches: List[Tuple[str, Dict[str, str]]], + explict_mappings: List[NodeMappingTemplate], +) -> List[Tuple[str, Dict[str, str]]]: + # extract the fignode ID set from matches + match_node_set_dict: Dict[FrozenSet, List[Tuple[str, Dict[str, str]]]] = {} + for match in matches: + frozen_set = frozenset(match[1].keys()) + if frozen_set not in match_node_set_dict: + match_node_set_dict[frozen_set] = [] + match_node_set_dict[frozen_set].append(match) + else: + match_node_set_dict[frozen_set].append(match) + + # Go through each of the explict matches, generate a subgraph and compare against + # all the matches + for explicit_mapping in explict_mappings: + # Only do the explicit mapping if the the mapping object has a technology + # associated with it else skip it + if explicit_mapping.technology_string is None: + continue -def connect_orphan_IO(): - print("Implement the orphan io generation system") + # Generate a subgraph for each of the mapping instance fig + for instance in explicit_mapping.instances: + node_set = set() -def get_flow_flow_candidates( - module: Module, gen_strategy: GenStrategy -) -> List[ConstructionNode]: - """Get canddiates where it its a "flow" only sub graph + # Check what kind of an instance this is + if isinstance(instance, NodeMappingInstance): + # This is a single node scenario + node_set.add(instance.node.ID) + elif isinstance(instance, FluidicOperatorMapping): + node_set.add(instance.node.ID) - Args: - module (Module): the module we want to check - gen_strategy (GenStrategy): the generation strategy we want to use + elif isinstance(instance, StorageMapping): + node_set.add(instance.node.ID) - Returns: - List[ConstructionNode]: List of all the construction nodes - """ - # TODO - go through all the edges and see which ones are between flow-flow graphs - # If these connectsions are between flow-flow nodes then we need to figure out - # which ones are part of the same network/connected graphs with only flow nodes - # The networks with only the flow nodes will need to be covered as a part of. - # these construction nodes. - - ret = [] - - # Step 1. Do a shallow copy of the graph - # Step 2. Remove all the fignodes that are not Flow - # Step 3. Now get the all the disconnected pieces of the graph - # Step 4. Create a Construction node for each of the disconnected pieces - # Return all the constructions nodes - - # Step 1. Do a shallow copy of the graph - fig_original = module.FIG - fig_copy = module.FIG.copy( - as_view=False - ) # Note this does not copy anything besides the nx.DiGraph at the moment - - # Step 2. Remove all the fignodes that are not Flow - remove_list = [] - - # Remove nodes from the explicit mapping construction nodes - for mapping in module.mappings: - for instance in mapping.instances: - if isinstance(instance, NetworkMapping): - remove_list.extend([n.ID for n in instance.input_nodes]) - remove_list.extend([n.ID for n in instance.output_nodes]) + elif isinstance(instance, PumpMapping): + node_set.add(instance.node.ID) + + elif isinstance(instance, NetworkMapping): + node_set = set() + node_set.union(set([node.ID for node in instance.input_nodes])) + node_set.union(set([node.ID for node in instance.output_nodes])) + + if frozenset(node_set) in match_node_set_dict: + # This is an explicit match + # Remove the explicit match from the list of matches + print( + "Eliminating match: {}".format( + match_node_set_dict[frozenset(node_set)] + ) + ) + match_node_set_dict[frozenset(node_set)].clear() + + # Now generate a match tuple for this instance + match_tuple = ( + explicit_mapping.technology_string, + {}, + ) + + # TODO - Retouch this part if we ever go into modifying how the matches are + # generated if we use the match string coordinates (use the match interface + # for this) (function - generate_single_match) + + # Check what kind of an instance this is + if isinstance(instance, NodeMappingInstance): + # This is a single node scenario + match_tuple[1][instance.node.ID] = "v1" + elif isinstance(instance, FluidicOperatorMapping): + match_tuple[1][instance.node.ID] = "v1" + + elif isinstance(instance, StorageMapping): + match_tuple[1][instance.node.ID] = "v1" + + elif isinstance(instance, PumpMapping): + match_tuple[1][instance.node.ID] = "v1" + + elif isinstance(instance, NetworkMapping): + for i in range(len(instance.input_nodes)): + node = instance.input_nodes[i] + match_tuple[1][node.ID] = f"vi{i}" + for i in range(len(instance.output_nodes)): + node = instance.output_nodes[i] + match_tuple[1][node.ID] = f"vo{i}" + + # Add this match tuple to the list of matches + if frozenset(node_set) in match_node_set_dict: + match_node_set_dict[frozenset(node_set)].append(match_tuple) else: - remove_list.append(instance.node.ID) - - for node_id in fig_copy.nodes: - node = fig_original.get_fignode(node_id) - if node.match_string != "FLOW": - remove_list.append(node_id) - - remove_list = list(set(remove_list)) - for node_id in remove_list: - fig_copy.remove_node(node_id) - - # Step 3. Now get the all the disconnected pieces of the graph - i = 0 - for component in nx.connected_components(fig_copy.to_undirected()): - print("Flow candidate") - print(component) - sub = fig_original.subgraph(component) - # TODO - Decide what the mapping type should be. for now assume that we just a - # single passthrough type scenario where we don't have to do much work - is_passthrough = __check_if_passthrough(sub) - if is_passthrough: - mapping_type = NetworkMappingOptionType.PASS_THROUGH - else: - mapping_type = NetworkMappingOptionType.CHANNEL_NETWORK - nprimitive = NetworkPrimitive(sub, gen_strategy) - nprimitive.generate_netlist() - mapping_option = NetworkMappingOption(nprimitive, mapping_type, sub) - # Step 4. Create a Construction node for each of the disconnected pieces - cn = ConstructionNode("flow_network_{}".format(i)) - cn.add_mapping_option(mapping_option) + match_node_set_dict[frozenset(node_set)] = [match_tuple] + + # Modify the matches list + eliminated_matches = [] + for match_tuple_list in match_node_set_dict.values(): + for match_tuple in match_tuple_list: + eliminated_matches.append(match_tuple) + + return eliminated_matches - i += 1 - ret.append(cn) - return ret +def generate_device( + construction_graph: ConstructionGraph, + scaffhold_device: MINTDevice, + name_generator: NameGenerator, +) -> None: + # TODO - Generate the device + # Step 1 - go though each of the construction nodes and genrate the corresponding + # components + # Step 2 - generate the connections between the outputs to input on the connected + # construction nodes + # Step 3 - TODO - Generate the control network + pass + + +def connect_orphan_IO(): + print("Implement the orphan io generation system") def __check_if_passthrough(sub) -> bool: @@ -1506,4 +1207,4 @@ def __check_if_passthrough(sub) -> bool: if in_count == 1 and out_count == 1: return True else: - return False + return False \ No newline at end of file diff --git a/lfr/netlistgenerator/mappingoption.py b/lfr/netlistgenerator/mappingoption.py index 44dffc6..4a1af97 100644 --- a/lfr/netlistgenerator/mappingoption.py +++ b/lfr/netlistgenerator/mappingoption.py @@ -1,6 +1,6 @@ from typing import Optional -from networkx import nx +import networkx as nx from lfr.fig.interaction import InteractionType from lfr.netlistgenerator.mappinglibrary import Primitive diff --git a/lfr/netlistgenerator/primitive.py b/lfr/netlistgenerator/primitive.py index ccbbb58..1c200e2 100644 --- a/lfr/netlistgenerator/primitive.py +++ b/lfr/netlistgenerator/primitive.py @@ -84,17 +84,33 @@ def match_string(self) -> str: return self._match_string def export_inputs(self, subgraph) -> List[ConnectingOption]: + # TODO - Figure out how to map connecting options to match string nodes + print( + "Warning: Implment how the connecting option is mapped to match string node" + ) return [copy.copy(c) for c in self._inputs] def export_outputs(self, subgraph) -> List[ConnectingOption]: + # TODO - Figure out how to map connecting options to match string nodes + print( + "Warning: Implment how the connecting option is mapped to match string node" + ) return [copy.copy(c) for c in self._outputs] def export_loadings(self, subgraph) -> Optional[List[ConnectingOption]]: + # TODO - Figure out how to map connecting options to match string nodes + print( + "Warning: Implment how the connecting option is mapped to match string node" + ) if self._loadings is None: return None return [copy.copy(c) for c in self._loadings] def export_carriers(self, subgraph) -> Optional[List[ConnectingOption]]: + # TODO - Figure out how to map connecting options to match string nodes + print( + "Warning: Implment how the connecting option is mapped to match string node" + ) if self._carriers is None: return None return [copy.copy(c) for c in self._carriers] diff --git a/lfr/utils.py b/lfr/utils.py index 16837d4..fc58c2a 100644 --- a/lfr/utils.py +++ b/lfr/utils.py @@ -2,7 +2,7 @@ import os from typing import List -from networkx import nx +import networkx as nx from pymint.mintdevice import MINTDevice import lfr.parameters as parameters diff --git a/random_graph_gen.py b/random_graph_gen.py new file mode 100644 index 0000000..7533737 --- /dev/null +++ b/random_graph_gen.py @@ -0,0 +1,67 @@ +import networkx as nx +from lfr import utils +from lfr.fig.fignode import Flow, IONode +from lfr.fig.fluidinteractiongraph import FluidInteractionGraph +from lfr.fig.interaction import FluidFluidInteraction, FluidProcessInteraction, Interaction, InteractionType +import random + + +G = nx.gnr_graph(8, 0.3) +FG = FluidInteractionGraph() + + +topo_sort = nx.topological_sort(G) +# print(list(topo_sort)) +flow_list = [] +type_list = list(InteractionType) + +node_list = [node for node in G.nodes if G.in_edges(node) == 0] +print(node_list) + +print(list(G)) + +for node in G: + if G.pred[node] != {}: + # if random.randrange(2) == 1: + # fig_to_add = Interaction(node, type_list[random.randrange(len(type_list))]) + fig_to_add = Flow(node) + + + else: + fig_to_add = IONode(node) + + print(type(fig_to_add)) + FG.add_fignode(fig_to_add) + flow_list.append(fig_to_add) + + +# print(list(G)) + +# for new_node in G: +# # if new_node.pred[new_node] +# new_temp_flow = Flow(new_node) if G.pred[node] +# FG.add_fignode(new_temp_flow) + + + +for fnode in G: + if len(G.pred[fnode]) >= 2: + print(random.randrange(2)) + if random.randrange(2) == 1: + store_vals = [Flow(x) for x in list(G.pred[fnode])] + print("trig") + for i in range(len(store_vals)-1): + ffint = FluidFluidInteraction(store_vals[i], store_vals[i+1], type_list[random.randrange(len(type_list))]) + flow_list[store_vals[i].ID] = ffint + flow_list[store_vals[i+1].ID] = ffint + FG.add_interaction(ffint) + +for source, sink in G.edges(): + FG.connect_fignodes(flow_list[source], flow_list[sink]) + +print(list(FG)) + +print(G.in_edges()) + +utils.printgraph(FG, "fg.dot") +