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
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
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
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)
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)
def declare_objective(model): model.TAC = Objective(rule=TAC_rule)
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