def make_separation_objective_functions(model, config):
    """
    Inequality constraints referencing control variables, state variables, or uncertain parameters
    must be separated against in separation problem.
    """
    performance_constraints = []
    for c in model.component_data_objects(Constraint,
                                          active=True,
                                          descend_into=True):
        _vars = ComponentSet(identify_variables(expr=c.expr))
        uncertain_params_in_expr = list(
            v for v in model.util.uncertain_param_vars.values() if v in _vars)
        state_vars_in_expr = list(v for v in model.util.state_vars
                                  if v in _vars)
        second_stage_variables_in_expr = list(
            v for v in model.util.second_stage_variables if v in _vars)
        if not c.equality and (uncertain_params_in_expr or state_vars_in_expr
                               or second_stage_variables_in_expr):
            # This inequality constraint depends on uncertain parameters therefore it must be separated against
            performance_constraints.append(c)
        elif not c.equality and not (uncertain_params_in_expr
                                     or state_vars_in_expr
                                     or second_stage_variables_in_expr):
            c.deactivate(
            )  # These are x \in X constraints, not active in separation because x is fixed to x* from previous master
    model.util.performance_constraints = performance_constraints
    model.util.separation_objectives = []
    map_obj_to_constr = ComponentMap()

    if len(model.util.performance_constraints) == 0:
        raise ValueError(
            "No performance constraints identified for the postulated robust optimization problem."
        )

    for idx, c in enumerate(performance_constraints):
        # Separation objective constraints standardized to be MAXIMIZATION of <= constraints
        c.deactivate()
        if c.upper is not None:
            # This is an <= constraint, maximized in separation
            obj = Objective(expr=c.body - c.upper, sense=maximize)
            map_obj_to_constr[c] = obj
            model.add_component("separation_obj_" + str(idx), obj)
            model.util.separation_objectives.append(obj)
        elif c.lower is not None:
            # This is an >= constraint, not supported
            raise ValueError(
                "All inequality constraints in model must be in standard form (<= RHS)"
            )

    model.util.map_obj_to_constr = map_obj_to_constr
    for obj in model.util.separation_objectives:
        obj.deactivate()

    return
Exemplo n.º 2
0
    def __init__(
        self,
        input_vars,
        external_vars,
        residual_cons,
        external_cons,
        solver=None,
    ):
        if solver is None:
            solver = SolverFactory("ipopt")
        self._solver = solver

        # We only need this block to construct the NLP, which wouldn't
        # be necessary if we could compute Hessians of Pyomo constraints.
        self._block = create_subsystem_block(
            residual_cons + external_cons,
            input_vars + external_vars,
        )
        self._block._obj = Objective(expr=0.0)
        self._nlp = PyomoNLP(self._block)

        self._scc_list = list(
            generate_strongly_connected_components(external_cons,
                                                   variables=external_vars))

        assert len(external_vars) == len(external_cons)

        self.input_vars = input_vars
        self.external_vars = external_vars
        self.residual_cons = residual_cons
        self.external_cons = external_cons

        self.residual_con_multipliers = [None for _ in residual_cons]
        self.residual_scaling_factors = None
Exemplo n.º 3
0
def get_hessian_of_constraint(constraint, wrt1=None, wrt2=None, nlp=None):
    constraints = [constraint]
    if wrt1 is None and wrt2 is None:
        variables = list(
            identify_variables(constraint.expr, include_fixed=False))
        wrt1 = variables
        wrt2 = variables
    elif wrt1 is not None and wrt2 is not None:
        variables = wrt1 + wrt2
    elif wrt1 is not None:  # but wrt2 is None
        wrt2 = wrt1
        variables = wrt1
    else:
        # wrt2 is not None and wrt1 is None
        wrt1 = wrt2
        variables = wrt1

    if nlp is None:
        block = create_subsystem_block(constraints, variables=variables)
        # Could fix input_vars so I don't evaluate the Hessian with respect
        # to variables I don't care about...

        # HUGE HACK: Variables not included in a constraint are not written
        # to the nl file, so we cannot take the derivative with respect to
        # them, even though we know this derivative is zero. To work around,
        # we make sure all variables appear on the block in the form of a
        # dummy constraint. Then we can take derivatives of any constraint
        # with respect to them. Conveniently, the extract_submatrix_
        # call deals with extracting the variables and constraint we care
        # about, in the proper order.
        block._dummy_var = Var()
        block._dummy_con = Constraint(expr=sum(variables) == block._dummy_var)
        block._obj = Objective(expr=0.0)
        nlp = PyomoNLP(block)

    saved_duals = nlp.get_duals()
    saved_obj_factor = nlp.get_obj_factor()
    temp_duals = np.zeros(len(saved_duals))

    # NOTE: This makes some assumption about how the Lagrangian is constructed.
    # TODO: Define the convention we assume and convert if necessary.
    idx = nlp.get_constraint_indices(constraints)[0]
    temp_duals[idx] = 1.0
    nlp.set_duals(temp_duals)
    nlp.set_obj_factor(0.0)

    # NOTE: The returned matrix preserves explicit zeros. I.e. it contains
    # coordinates for every entry that could possibly be nonzero.
    submatrix = nlp.extract_submatrix_hessian_lag(wrt1, wrt2)

    nlp.set_obj_factor(saved_obj_factor)
    nlp.set_duals(saved_duals)
    return submatrix
Exemplo n.º 4
0
def get_numeric_incidence_matrix(variables, constraints):
    """
    This function gets the numeric incidence matrix (Jacobian) of Pyomo
    constraints with respect to variables.
    """
    # NOTE: There are several ways to get a numeric incidence matrix
    # from a Pyomo model. Here we get the numeric incidence matrix by
    # creating a temporary block and using the PyNumero ASL interface.
    comps = list(variables) + list(constraints)
    _check_unindexed(comps)
    block = create_subsystem_block(constraints, variables)
    block._obj = Objective(expr=0)
    nlp = PyomoNLP(block)
    return nlp.extract_submatrix_jacobian(variables, constraints)
Exemplo n.º 5
0
def get_numeric_incidence_matrix(variables, constraints):
    """
    This function gets the numeric incidence matrix (Jacobian) of Pyomo
    constraints with respect to variables.
    """
    # NOTE: There are several ways to get a numeric incidence matrix
    # from a Pyomo model. This function implements a somewhat roundabout
    # method, which is to construct a dummy Block with the necessary
    # variables and constraints, then construct a PyNumero PyomoNLP
    # from the block and have PyNumero evaluate the desired Jacobian
    # via ASL.
    comps = list(variables) + list(constraints)
    _check_unindexed(comps)
    M, N = len(constraints), len(variables)
    _block = Block()
    _block.construct()
    _block.obj = Objective(expr=0)
    _block.vars = Reference(variables)
    _block.cons = Reference(constraints)
    var_set = ComponentSet(variables)
    other_vars = []
    for con in constraints:
        for var in identify_variables(con.body, include_fixed=False):
            # Fixed vars will be ignored by the nl file write, so
            # there is no point to including them here.
            # A different method of assembling this matrix, e.g.
            # Pyomo's automatic differentiation, could support taking
            # derivatives with respect to fixed variables.
            if var not in var_set:
                other_vars.append(var)
                var_set.add(var)
    # These variables are necessary due to the nl writer's philosophy
    # about what constitutes a model. Note that we take derivatives with
    # respect to them even though this is not necessary. We could fix them
    # here to avoid doing this extra work, but that would alter the user's
    # model, which we would rather not do.
    _block.other_vars = Reference(other_vars)
    _nlp = PyomoNLP(_block)
    return _nlp.extract_submatrix_jacobian(variables, constraints)
Exemplo n.º 6
0
def declare_objective(model):
    model.TAC = Objective(rule=TAC_rule)
Exemplo n.º 7
0
    def __init__(
        self,
        input_vars,
        external_vars,
        residual_cons,
        external_cons,
        use_cyipopt=None,
        solver=None,
    ):
        """
        Arguments:
        ----------
        input_vars: list
            List of variables sent to this system by the outer solver
        external_vars: list
            List of variables that are solved for internally by this system
        residual_cons: list
            List of equality constraints whose residuals are exposed to
            the outer solver
        external_cons: list
            List of equality constraints used to solve for the external
            variables
        use_cyipopt: bool
            Whether to use CyIpopt to solve strongly connected components of
            the implicit function that have dimension greater than one.
        solver: Pyomo solver object
            Used to solve strongly connected components of the implicit function
            that have dimension greater than one. Only used if use_cyipopt
            is False.

        """
        if use_cyipopt is None:
            use_cyipopt = cyipopt_available
        if use_cyipopt and not cyipopt_available:
            raise RuntimeError(
                "Constructing an ExternalPyomoModel with CyIpopt unavailable. "
                "Please set the use_cyipopt argument to False.")
        if solver is not None and use_cyipopt:
            raise RuntimeError(
                "Constructing an ExternalPyomoModel with a solver specified "
                "and use_cyipopt set to True. Please set use_cyipopt to False "
                "to use the desired solver.")
        elif solver is None and not use_cyipopt:
            solver = SolverFactory("ipopt")
        # If use_cyipopt is True, this solver is None and will not be used.
        self._solver = solver
        self._use_cyipopt = use_cyipopt

        # We only need this block to construct the NLP, which wouldn't
        # be necessary if we could compute Hessians of Pyomo constraints.
        self._block = create_subsystem_block(
            residual_cons + external_cons,
            input_vars + external_vars,
        )
        self._block._obj = Objective(expr=0.0)
        self._nlp = PyomoNLP(self._block)

        self._scc_list = list(
            generate_strongly_connected_components(external_cons,
                                                   variables=external_vars))

        if use_cyipopt:
            # Using CyIpopt allows us to solve inner problems without
            # costly rewriting of the nl file. It requires quite a bit
            # of preprocessing, however, to construct the ProjectedNLP
            # for each block of the decomposition.

            # Get "vector-valued" SCCs, those of dimension > 0.
            # We will solve these with a direct IPOPT interface, which requires
            # some preprocessing.
            self._vector_scc_list = [(scc, inputs)
                                     for scc, inputs in self._scc_list
                                     if len(scc.vars) > 1]

            # Need a dummy objective to create an NLP
            for scc, inputs in self._vector_scc_list:
                scc._obj = Objective(expr=0.0)

                # I need scaling_factor so Pyomo NLPs I create from these blocks
                # don't break when ProjectedNLP calls get_primals_scaling
                scc.scaling_factor = Suffix(direction=Suffix.EXPORT)
                # HACK: scaling_factor just needs to be nonempty.
                scc.scaling_factor[scc._obj] = 1.0

            # These are the "original NLPs" that will be projected
            self._vector_scc_nlps = [
                PyomoNLP(scc) for scc, inputs in self._vector_scc_list
            ]
            self._vector_scc_var_names = [[
                var.name for var in scc.vars.values()
            ] for scc, inputs in self._vector_scc_list]
            self._vector_proj_nlps = [
                ProjectedNLP(nlp, names) for nlp, names in zip(
                    self._vector_scc_nlps, self._vector_scc_var_names)
            ]

            # We will solve the ProjectedNLPs rather than the original NLPs
            self._cyipopt_nlps = [
                CyIpoptNLP(nlp) for nlp in self._vector_proj_nlps
            ]
            self._cyipopt_solvers = [
                CyIpoptSolver(nlp) for nlp in self._cyipopt_nlps
            ]
            self._vector_scc_input_coords = [
                nlp.get_primal_indices(inputs) for nlp, (scc, inputs) in zip(
                    self._vector_scc_nlps, self._vector_scc_list)
            ]

        assert len(external_vars) == len(external_cons)

        self.input_vars = input_vars
        self.external_vars = external_vars
        self.residual_cons = residual_cons
        self.external_cons = external_cons

        self.residual_con_multipliers = [None for _ in residual_cons]
        self.residual_scaling_factors = None