|  | 
|  | 1 | +"""Model Graph Generator Code""" | 
|  | 2 | + | 
|  | 3 | +from pyomo.common.dependencies import networkx as nx | 
|  | 4 | +from pyomo.core import Constraint, Objective, Var, ComponentMap, SortComponents | 
|  | 5 | +from pyomo.core.expr.current import identify_variables | 
|  | 6 | +from pyomo.contrib.community_detection.event_log import _event_log | 
|  | 7 | + | 
|  | 8 | + | 
|  | 9 | +def generate_model_graph(model, type_of_graph, with_objective=True, weighted_graph=True, | 
|  | 10 | +                         use_only_active_components=True): | 
|  | 11 | +    """ | 
|  | 12 | +    Creates a networkX graph of nodes and edges based on a Pyomo optimization model | 
|  | 13 | +
 | 
|  | 14 | +    This function takes in a Pyomo optimization model, then creates a graphical representation of the model with | 
|  | 15 | +    specific features of the graph determined by the user (see Parameters below). | 
|  | 16 | +
 | 
|  | 17 | +    (This function is designed to be called by detect_communities, but can be used solely for the purpose of | 
|  | 18 | +    creating model graphs as well.) | 
|  | 19 | +
 | 
|  | 20 | +    Parameters | 
|  | 21 | +    ---------- | 
|  | 22 | +    model: Block | 
|  | 23 | +        a Pyomo model or block to be used for community detection | 
|  | 24 | +    type_of_graph: str | 
|  | 25 | +        a string that specifies the type of graph that is created from the model | 
|  | 26 | +        'constraint' creates a graph based on constraint nodes, | 
|  | 27 | +        'variable' creates a graph based on variable nodes, | 
|  | 28 | +        'bipartite' creates a graph based on constraint and variable nodes (bipartite graph). | 
|  | 29 | +    with_objective: bool, optional | 
|  | 30 | +        a Boolean argument that specifies whether or not the objective function is included in the graph; the | 
|  | 31 | +        default is True | 
|  | 32 | +    weighted_graph: bool, optional | 
|  | 33 | +        a Boolean argument that specifies whether a weighted or unweighted graph is to be created from the Pyomo | 
|  | 34 | +        model; the default is True (type_of_graph='bipartite' creates an unweighted graph regardless of this parameter) | 
|  | 35 | +    use_only_active_components: bool, optional | 
|  | 36 | +        a Boolean argument that specifies whether inactive constraints/objectives are included in the networkX graph | 
|  | 37 | +
 | 
|  | 38 | +    Returns | 
|  | 39 | +    ------- | 
|  | 40 | +    bipartite_model_graph/projected_model_graph: nx.Graph | 
|  | 41 | +        a NetworkX graph with nodes and edges based on the given Pyomo optimization model | 
|  | 42 | +    number_component_map: dict | 
|  | 43 | +        a dictionary that (deterministically) maps a number to a component in the model | 
|  | 44 | +    constraint_variable_map: dict | 
|  | 45 | +        a dictionary that maps a numbered constraint to a list of (numbered) variables that appear in the constraint | 
|  | 46 | +    """ | 
|  | 47 | + | 
|  | 48 | +    # Start off by making a bipartite graph (regardless of the value of type_of_graph), then if | 
|  | 49 | +    # type_of_graph = 'variable' or 'constraint', we will "collapse" this bipartite graph into a variable node | 
|  | 50 | +    # or constraint node graph | 
|  | 51 | + | 
|  | 52 | +    # Initialize the data structure needed to keep track of edges in the graph (this graph will be made | 
|  | 53 | +    # without edge weights, because edge weights are not useful for this bipartite graph) | 
|  | 54 | +    edge_set = set() | 
|  | 55 | + | 
|  | 56 | +    bipartite_model_graph = nx.Graph()  # Initialize NetworkX graph for the bipartite graph | 
|  | 57 | +    constraint_variable_map = {}  # Initialize map of the variables in constraint equations | 
|  | 58 | + | 
|  | 59 | +    # Make a dict of all the components we need for the NetworkX graph (since we cannot use the components directly | 
|  | 60 | +    # in the NetworkX graph) | 
|  | 61 | +    if with_objective: | 
|  | 62 | +        component_number_map = ComponentMap((component, number) for number, component in enumerate( | 
|  | 63 | +            model.component_data_objects(ctype=(Constraint, Var, Objective), active=use_only_active_components, | 
|  | 64 | +                                         descend_into=True, | 
|  | 65 | +                                         sort=SortComponents.deterministic))) | 
|  | 66 | +    else: | 
|  | 67 | +        component_number_map = ComponentMap((component, number) for number, component in enumerate( | 
|  | 68 | +            model.component_data_objects(ctype=(Constraint, Var), active=use_only_active_components, descend_into=True, | 
|  | 69 | +                                         sort=SortComponents.deterministic))) | 
|  | 70 | + | 
|  | 71 | +    # Create the reverse of component_number_map, which will be used in detect_communities to convert the node numbers | 
|  | 72 | +    # to their corresponding Pyomo modeling components | 
|  | 73 | +    number_component_map = dict((number, comp) for comp, number in component_number_map.items()) | 
|  | 74 | + | 
|  | 75 | +    # Add the components as nodes to the bipartite graph | 
|  | 76 | +    bipartite_model_graph.add_nodes_from([node_number for node_number in range(len(component_number_map))]) | 
|  | 77 | + | 
|  | 78 | +    # Loop through all constraints in the Pyomo model to determine what edges need to be created | 
|  | 79 | +    for model_constraint in model.component_data_objects(ctype=Constraint, active=use_only_active_components, | 
|  | 80 | +                                                         descend_into=True): | 
|  | 81 | +        numbered_constraint = component_number_map[model_constraint] | 
|  | 82 | + | 
|  | 83 | +        # Create a list of the variable numbers that occur in the given constraint equation | 
|  | 84 | +        numbered_variables_in_constraint_equation = [component_number_map[constraint_variable] | 
|  | 85 | +                                                     for constraint_variable in | 
|  | 86 | +                                                     identify_variables(model_constraint.body)] | 
|  | 87 | + | 
|  | 88 | +        # Update constraint_variable_map | 
|  | 89 | +        constraint_variable_map[numbered_constraint] = numbered_variables_in_constraint_equation | 
|  | 90 | + | 
|  | 91 | +        # Create a list of all the edges that need to be created based on the variables in this constraint equation | 
|  | 92 | +        edges_between_nodes = [(numbered_constraint, numbered_variable_in_constraint) | 
|  | 93 | +                               for numbered_variable_in_constraint in numbered_variables_in_constraint_equation] | 
|  | 94 | + | 
|  | 95 | +        # Update edge_set based on the determined edges between nodes | 
|  | 96 | +        edge_set.update(edges_between_nodes) | 
|  | 97 | + | 
|  | 98 | +    # This if statement will be executed if the user chooses to include the objective function as a node in | 
|  | 99 | +    # the model graph | 
|  | 100 | +    if with_objective: | 
|  | 101 | + | 
|  | 102 | +        # Use a loop to account for the possibility of multiple objective functions | 
|  | 103 | +        for objective_function in model.component_data_objects(ctype=Objective, active=use_only_active_components, | 
|  | 104 | +                                                               descend_into=True): | 
|  | 105 | +            numbered_objective = component_number_map[objective_function] | 
|  | 106 | + | 
|  | 107 | +            # Create a list of the variable numbers that occur in the given objective function | 
|  | 108 | +            numbered_variables_in_objective = [component_number_map[objective_variable] | 
|  | 109 | +                                               for objective_variable in identify_variables(objective_function)] | 
|  | 110 | + | 
|  | 111 | +            # Update constraint_variable_map | 
|  | 112 | +            constraint_variable_map[numbered_objective] = numbered_variables_in_objective | 
|  | 113 | + | 
|  | 114 | +            # Create a list of all the edges that need to be created based on the variables in the objective function | 
|  | 115 | +            edges_between_nodes = [(numbered_objective, numbered_variable_in_objective) | 
|  | 116 | +                                   for numbered_variable_in_objective in numbered_variables_in_objective] | 
|  | 117 | + | 
|  | 118 | +            # Update edge_set based on the determined edges between nodes | 
|  | 119 | +            edge_set.update(edges_between_nodes) | 
|  | 120 | + | 
|  | 121 | +    # Add edges to bipartite_model_graph (the order in which edges are added can affect community detection, so | 
|  | 122 | +    # sorting prevents any unpredictable changes) | 
|  | 123 | +    bipartite_model_graph.add_edges_from(sorted(edge_set)) | 
|  | 124 | + | 
|  | 125 | +    if type_of_graph == 'bipartite':  # This is the case where the user wants a bipartite graph, which we made above | 
|  | 126 | +        # Log important information with the following logger function | 
|  | 127 | +        _event_log(model, bipartite_model_graph, set(constraint_variable_map), type_of_graph, with_objective) | 
|  | 128 | + | 
|  | 129 | +        # Return the bipartite NetworkX graph, the dictionary of node numbers mapped to their respective Pyomo | 
|  | 130 | +        # components, and the map of constraints to the variables they contain | 
|  | 131 | +        return bipartite_model_graph, number_component_map, constraint_variable_map | 
|  | 132 | + | 
|  | 133 | +    # At this point of the code, we will create the projected version of the bipartite | 
|  | 134 | +    # model graph (based on the specific value of type_of_graph) | 
|  | 135 | + | 
|  | 136 | +    constraint_nodes = set(constraint_variable_map) | 
|  | 137 | +    if type_of_graph == 'constraint': | 
|  | 138 | +        graph_nodes = constraint_nodes | 
|  | 139 | +    else: | 
|  | 140 | +        variable_nodes = set(number_component_map) - constraint_nodes | 
|  | 141 | +        graph_nodes = variable_nodes | 
|  | 142 | + | 
|  | 143 | +    if weighted_graph: | 
|  | 144 | +        projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes) | 
|  | 145 | +    else: | 
|  | 146 | +        projected_model_graph = nx.bipartite.projected_graph(bipartite_model_graph, graph_nodes) | 
|  | 147 | + | 
|  | 148 | +    # Log important information with the following logger function | 
|  | 149 | +    _event_log(model, projected_model_graph, set(constraint_variable_map), type_of_graph, with_objective) | 
|  | 150 | + | 
|  | 151 | +    # Return the projected NetworkX graph, the dictionary of node numbers mapped to their respective Pyomo | 
|  | 152 | +    # components, and the map of constraints to the variables they contain | 
|  | 153 | +    return projected_model_graph, number_component_map, constraint_variable_map | 
0 commit comments