Exemplo n.º 1
0
    def _apply_to(self, instance, **kwds):
        if not instance.ctype in (Block, Disjunct):
            raise GDP_Error(
                "Transformation called on %s of type %s. 'instance'"
                " must be a ConcreteModel, Block, or Disjunct (in "
                "the case of nested disjunctions)." %
                (instance.name, instance.ctype))
        try:
            self._config = self.CONFIG(kwds.pop('options', {}))
            self._config.set_value(kwds)
            self._transformation_blocks = {}

            if not self._config.assume_fixed_vars_permanent:
                fixed_vars = ComponentMap()
                for v in get_vars_from_components(instance,
                                                  Constraint,
                                                  include_fixed=True,
                                                  active=True,
                                                  descend_into=(Block,
                                                                Disjunct)):
                    if v.fixed:
                        fixed_vars[v] = value(v)
                        v.fixed = False

            self._apply_to_impl(instance)

        finally:
            # restore fixed variables
            if not self._config.assume_fixed_vars_permanent:
                for v, val in fixed_vars.items():
                    v.fix(val)

            del self._config
            del self._transformation_blocks
Exemplo n.º 2
0
    def generate_structured_model(self):
        """
        Using the community map and the original model used to create this community map, we will create
        structured_model, which will be based on the original model but will place variables, constraints, and
        objectives into or outside of various blocks (communities) based on the community map.

        Returns
        -------
        structured_model: Block
            a Pyomo model that reflects the nature of the community map
        """

        # Initialize a new model (structured_model) which will contain variables and constraints in blocks based on
        # their respective communities within the CommunityMap
        structured_model = ConcreteModel()

        # Create N blocks (where N is the number of communities found within the model)
        structured_model.b = Block([0, len(self.community_map) - 1,
                                    1])  # values given for (start, stop, step)

        # Initialize a ComponentMap that will map a variable from the model (for example, old_model.x1) used to
        # create the CommunityMap to a list of variables in various blocks that were created based on this
        # variable (for example, [structured_model.b[0].x1, structured_model.b[3].x1])
        blocked_variable_map = ComponentMap()
        # Example key-value pair -> {original_model.x1 : [structured_model.b[0].x1, structured_model.b[3].x1]}

        # TODO - Consider changing structure of the next two for loops to be more efficient (maybe loop through
        #  constraints and add variables as you go) (but note that disconnected variables would be
        #  missed with this strategy)

        # First loop through community_map to add all the variables to structured_model before we add constraints
        # that use those variables
        for community_key, community in self.community_map.items():
            _, variables_in_community = community

            # Loop through all of the variables (from the original model) in the given community
            for stored_variable in variables_in_community:
                # Construct a new_variable whose attributes are determined by querying the variable from the
                # original model
                new_variable = Var(domain=stored_variable.domain,
                                   bounds=stored_variable.bounds)

                # Add this new_variable to its block/community and name it using the string of the variable from the
                # original model
                structured_model.b[community_key].add_component(
                    str(stored_variable), new_variable)

                # Since there could be multiple variables 'x1' (such as
                # structured_model.b[0].x1, structured_model.b[3].x1, etc), we need to create equality constraints
                # for all of the variables 'x1' within structured_model (this is the purpose of blocked_variable_map)

                # Here we update blocked_variable_map to keep track of what equality constraints need to be made
                variable_in_new_model = structured_model.find_component(
                    new_variable)
                blocked_variable_map[
                    stored_variable] = blocked_variable_map.get(
                        stored_variable, []) + [variable_in_new_model]

        # Now that we have all of our variables within the model, we will initialize a dictionary that used to
        # replace variables within constraints to other variables (in our case, this will convert variables from the
        # original model into variables from the new model (structured_model))
        replace_variables_in_expression_map = dict()

        # Loop through community_map again, this time to add constraints (with replaced variables)
        for community_key, community in self.community_map.items():
            constraints_in_community, _ = community

            # Loop through all of the constraints (from the original model) in the given community
            for stored_constraint in constraints_in_community:

                # Now, loop through all of the variables within the given constraint expression
                for variable_in_stored_constraint in identify_variables(
                        stored_constraint.expr):

                    # Loop through each of the "blocked" variables that a variable is mapped to and update
                    # replace_variables_in_expression_map if a variable has a "blocked" form in the given community

                    # What this means is that if we are looping through constraints in community 0, then it would be
                    # best to change a variable x1 into b[0].x1 as opposed to b[2].x1 or b[5].x1 (assuming all of these
                    # blocked versions of the variable x1 exist (which depends on the community map))

                    variable_in_current_block = False
                    for blocked_variable in blocked_variable_map[
                            variable_in_stored_constraint]:
                        if 'b[%d]' % community_key in str(blocked_variable):
                            # Update replace_variables_in_expression_map accordingly
                            replace_variables_in_expression_map[
                                id(variable_in_stored_constraint
                                   )] = blocked_variable
                            variable_in_current_block = True

                    if not variable_in_current_block:
                        # Create a version of the given variable outside of blocks then add it to
                        # replace_variables_in_expression_map

                        new_variable = Var(
                            domain=variable_in_stored_constraint.domain,
                            bounds=variable_in_stored_constraint.bounds)

                        # Add the new variable just as we did above (but now it is not in any blocks)
                        structured_model.add_component(
                            str(variable_in_stored_constraint), new_variable)

                        # Update blocked_variable_map to keep track of what equality constraints need to be made
                        variable_in_new_model = structured_model.find_component(
                            new_variable)
                        blocked_variable_map[
                            variable_in_stored_constraint] = blocked_variable_map.get(
                                variable_in_stored_constraint,
                                []) + [variable_in_new_model]

                        # Update replace_variables_in_expression_map accordingly
                        replace_variables_in_expression_map[
                            id(variable_in_stored_constraint
                               )] = variable_in_new_model

                # TODO - Is there a better way to check whether something is actually an objective? (as done below)
                # Check to see whether 'stored_constraint' is actually an objective (since constraints and objectives
                # grouped together)
                if self.with_objective and isinstance(
                        stored_constraint, (_GeneralObjectiveData, Objective)):
                    # If the constraint is actually an objective, we add it to the block as an objective
                    new_objective = Objective(expr=replace_expressions(
                        stored_constraint.expr,
                        replace_variables_in_expression_map))
                    structured_model.b[community_key].add_component(
                        str(stored_constraint), new_objective)

                else:
                    # Construct a constraint based on the expression within stored_constraint and the dict we have
                    # created for the purpose of replacing the variables within the constraint expression
                    new_constraint = Constraint(expr=replace_expressions(
                        stored_constraint.expr,
                        replace_variables_in_expression_map))

                    # Add this new constraint to the corresponding community/block with its name as the string of the
                    # constraint from the original model
                    structured_model.b[community_key].add_component(
                        str(stored_constraint), new_constraint)

        # If with_objective was set to False, that means we might have missed an objective function within the
        # original model
        if not self.with_objective:
            # Construct a new dictionary for replacing the variables (replace_variables_in_objective_map) which will
            # be specific to the variables in the objective function, since there is the possibility that the
            # objective contains variables we have not yet seen (and thus not yet added to our new model)
            for objective_function in self.model.component_data_objects(
                    ctype=Objective,
                    active=self.use_only_active_components,
                    descend_into=True):

                for variable_in_objective in identify_variables(
                        objective_function):
                    # Add all of the variables in the objective function (not within any blocks)

                    # Check to make sure a form of the variable has not already been made outside of the blocks
                    if structured_model.find_component(
                            str(variable_in_objective)) is None:

                        new_variable = Var(domain=variable_in_objective.domain,
                                           bounds=variable_in_objective.bounds)
                        structured_model.add_component(
                            str(variable_in_objective), new_variable)

                        # Again we update blocked_variable_map to keep track of what
                        # equality constraints need to be made
                        variable_in_new_model = structured_model.find_component(
                            new_variable)
                        blocked_variable_map[
                            variable_in_objective] = blocked_variable_map.get(
                                variable_in_objective,
                                []) + [variable_in_new_model]

                        # Update the dictionary that we will use to replace the variables
                        replace_variables_in_expression_map[id(
                            variable_in_objective)] = variable_in_new_model

                    else:
                        for version_of_variable in blocked_variable_map[
                                variable_in_objective]:
                            if 'b[' not in str(version_of_variable):
                                replace_variables_in_expression_map[
                                    id(variable_in_objective
                                       )] = version_of_variable

                # Now we will construct a new objective function based on the one from the original model and then
                # add it to the new model just as we have done before
                new_objective = Objective(expr=replace_expressions(
                    objective_function.expr,
                    replace_variables_in_expression_map))
                structured_model.add_component(str(objective_function),
                                               new_objective)

        # Now, we need to create equality constraints for all of the different "versions" of a variable (such
        # as x1, b[0].x1, b[2].x2, etc.)

        # Create a constraint list for the equality constraints
        structured_model.equality_constraint_list = ConstraintList(
            doc="Equality Constraints for the different "
            "forms of a given variable")

        # Loop through blocked_variable_map and create constraints accordingly
        for variable, duplicate_variables in blocked_variable_map.items():
            # variable -> variable from the original model
            # duplicate_variables -> list of variables in the new model

            # Create a list of all the possible equality constraints that need to be made
            equalities_to_make = combinations(duplicate_variables, 2)

            # Loop through the list of two-variable tuples and create an equality constraint for those two variables
            for variable_1, variable_2 in equalities_to_make:
                structured_model.equality_constraint_list.add(
                    expr=variable_1 == variable_2)

        # Return 'structured_model', which is essentially identical to the original model but now has all of the
        # variables, constraints, and objectives placed into blocks based on the nature of the CommunityMap

        return structured_model
Exemplo n.º 3
0
def generate_model_graph(model, type_of_graph, with_objective=True, weighted_graph=True,
                         use_only_active_components=True):
    """
    Creates a networkX graph of nodes and edges based on a Pyomo optimization model

    This function takes in a Pyomo optimization model, then creates a graphical representation of the model with
    specific features of the graph determined by the user (see Parameters below).

    (This function is designed to be called by detect_communities, but can be used solely for the purpose of
    creating model graphs as well.)

    Parameters
    ----------
    model: Block
        a Pyomo model or block to be used for community detection
    type_of_graph: str
        a string that specifies the type of graph that is created from the model
        'constraint' creates a graph based on constraint nodes,
        'variable' creates a graph based on variable nodes,
        'bipartite' creates a graph based on constraint and variable nodes (bipartite graph).
    with_objective: bool, optional
        a Boolean argument that specifies whether or not the objective function is included in the graph; the
        default is True
    weighted_graph: bool, optional
        a Boolean argument that specifies whether a weighted or unweighted graph is to be created from the Pyomo
        model; the default is True (type_of_graph='bipartite' creates an unweighted graph regardless of this parameter)
    use_only_active_components: bool, optional
        a Boolean argument that specifies whether inactive constraints/objectives are included in the networkX graph

    Returns
    -------
    bipartite_model_graph/projected_model_graph: nx.Graph
        a NetworkX graph with nodes and edges based on the given Pyomo optimization model
    number_component_map: dict
        a dictionary that (deterministically) maps a number to a component in the model
    constraint_variable_map: dict
        a dictionary that maps a numbered constraint to a list of (numbered) variables that appear in the constraint
    """

    # Start off by making a bipartite graph (regardless of the value of type_of_graph), then if
    # type_of_graph = 'variable' or 'constraint', we will "collapse" this bipartite graph into a variable node
    # or constraint node graph

    # Initialize the data structure needed to keep track of edges in the graph (this graph will be made
    # without edge weights, because edge weights are not useful for this bipartite graph)
    edge_set = set()

    bipartite_model_graph = nx.Graph()  # Initialize NetworkX graph for the bipartite graph
    constraint_variable_map = {}  # Initialize map of the variables in constraint equations

    # Make a dict of all the components we need for the NetworkX graph (since we cannot use the components directly
    # in the NetworkX graph)
    if with_objective:
        component_number_map = ComponentMap((component, number) for number, component in enumerate(
            model.component_data_objects(ctype=(Constraint, Var, Objective), active=use_only_active_components,
                                         descend_into=True,
                                         sort=SortComponents.deterministic)))
    else:
        component_number_map = ComponentMap((component, number) for number, component in enumerate(
            model.component_data_objects(ctype=(Constraint, Var), active=use_only_active_components, descend_into=True,
                                         sort=SortComponents.deterministic)))

    # Create the reverse of component_number_map, which will be used in detect_communities to convert the node numbers
    # to their corresponding Pyomo modeling components
    number_component_map = dict((number, comp) for comp, number in component_number_map.items())

    # Add the components as nodes to the bipartite graph
    bipartite_model_graph.add_nodes_from([node_number for node_number in range(len(component_number_map))])

    # Loop through all constraints in the Pyomo model to determine what edges need to be created
    for model_constraint in model.component_data_objects(ctype=Constraint, active=use_only_active_components,
                                                         descend_into=True):
        numbered_constraint = component_number_map[model_constraint]

        # Create a list of the variable numbers that occur in the given constraint equation
        numbered_variables_in_constraint_equation = [component_number_map[constraint_variable]
                                                     for constraint_variable in
                                                     identify_variables(model_constraint.body)]

        # Update constraint_variable_map
        constraint_variable_map[numbered_constraint] = numbered_variables_in_constraint_equation

        # Create a list of all the edges that need to be created based on the variables in this constraint equation
        edges_between_nodes = [(numbered_constraint, numbered_variable_in_constraint)
                               for numbered_variable_in_constraint in numbered_variables_in_constraint_equation]

        # Update edge_set based on the determined edges between nodes
        edge_set.update(edges_between_nodes)

    # This if statement will be executed if the user chooses to include the objective function as a node in
    # the model graph
    if with_objective:

        # Use a loop to account for the possibility of multiple objective functions
        for objective_function in model.component_data_objects(ctype=Objective, active=use_only_active_components,
                                                               descend_into=True):
            numbered_objective = component_number_map[objective_function]

            # Create a list of the variable numbers that occur in the given objective function
            numbered_variables_in_objective = [component_number_map[objective_variable]
                                               for objective_variable in identify_variables(objective_function)]

            # Update constraint_variable_map
            constraint_variable_map[numbered_objective] = numbered_variables_in_objective

            # Create a list of all the edges that need to be created based on the variables in the objective function
            edges_between_nodes = [(numbered_objective, numbered_variable_in_objective)
                                   for numbered_variable_in_objective in numbered_variables_in_objective]

            # Update edge_set based on the determined edges between nodes
            edge_set.update(edges_between_nodes)

    # Add edges to bipartite_model_graph (the order in which edges are added can affect community detection, so
    # sorting prevents any unpredictable changes)
    bipartite_model_graph.add_edges_from(sorted(edge_set))

    if type_of_graph == 'bipartite':  # This is the case where the user wants a bipartite graph, which we made above
        # Log important information with the following logger function
        _event_log(model, bipartite_model_graph, set(constraint_variable_map), type_of_graph, with_objective)

        # Return the bipartite NetworkX graph, the dictionary of node numbers mapped to their respective Pyomo
        # components, and the map of constraints to the variables they contain
        return bipartite_model_graph, number_component_map, constraint_variable_map

    # At this point of the code, we will create the projected version of the bipartite
    # model graph (based on the specific value of type_of_graph)

    constraint_nodes = set(constraint_variable_map)
    if type_of_graph == 'constraint':
        graph_nodes = constraint_nodes
    else:
        variable_nodes = set(number_component_map) - constraint_nodes
        graph_nodes = variable_nodes

    if weighted_graph:
        projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes)
    else:
        projected_model_graph = nx.bipartite.projected_graph(bipartite_model_graph, graph_nodes)

    # Log important information with the following logger function
    _event_log(model, projected_model_graph, set(constraint_variable_map), type_of_graph, with_objective)

    # Return the projected NetworkX graph, the dictionary of node numbers mapped to their respective Pyomo
    # components, and the map of constraints to the variables they contain
    return projected_model_graph, number_component_map, constraint_variable_map