def add_indx_nonlinear_eq(constr, indx, pyo_vars, eq_expr, calc_jacs=True, jacs=None): """Adds a nonlinear equation with the given index (tuple) to the model constraint constr, generating and storing a corresponding CasADi jacobian in a dictionary for cut generation""" raise DeprecationWarning( 'This function is deprecated in preference of a new idiom given below. Comment out this line to continue using it.' ) name = constr.local_name def eq_rule(): # combines the LHS expression generated by eq_expr with a zero RHS to form either a Pyomo or CasADi equality expression. LHS = eq_expr(*indx, **pyo_vars) if isinstance( indx, tuple) else eq_expr(indx, **pyo_vars) if LHS is Constraint.NoConstraint or LHS is Constraint.Skip: return Constraint.NoConstraint else: return LHS == 0 eq_expression = eq_rule() if eq_expression is Constraint.NoConstraint: # Checks to see if the expression returned by the equation rule calls for skipping the constraint. If so, do that. return else: # Otherwise, add the expression to the indexed constraint constr.add(indx, expr=eq_rule()) if not calc_jacs: # If we should add the constraint but not calculate the jacobian, then simply exit at this point. return new_constr = constr[indx] # Create dictionaries for CasADi variables and flattened variables cas_vars = {} flat_vars = {} for var_name, pyo_var in pyo_vars.iteritems(): if isinstance(pyo_var, Mapping): # The passed value is not a variable, but rather a dictionary of variables. Flatten this dictionary appropriately. raise NotImplementedError() elif pyo_var.is_indexed(): # if the pyomo variable is indexed, generate a dictionary of CasADi variables cas_vars[var_name] = { indx: SX.sym(var_name + '[' + str(indx) + ']') for indx in pyo_var._index } # At the same time, generate a dictionary of flattened variables (with the indices enumerated) for future use flat_vars.update({ var_name + '[' + str(indx) + ']': (cas_vars[var_name][indx], pyo_var[indx]) for indx in pyo_var._index }) else: # If the pyomo variable is not indexed, generate a single corresponding CasADi variable and add both to the flat variables dictionary cas_vars[var_name] = SX.sym(var_name) flat_vars[var_name] = (cas_vars[var_name], pyo_var) # Using the flat variables dictionary, generate a corresponding lists of CasADi variable names, CasADi variables and Pyomo (flattened) variables cas_var_name_list, var_list = zip(*flat_vars.iteritems()) cas_var_list, pyo_var_list = zip(*var_list) f = eq_expr(*indx, **cas_vars) if isinstance(indx, tuple) else eq_expr( indx, **cas_vars) if f is Constraint.NoConstraint: # This would be a weird situation, where a non-indexed constraint returns a value indicating that no constraint is defined. In this case, nothing needs to be added to the jacobians dictionary. return # The options dictionary allows for more intuitive function calls later on. Note that the ordering of the CasADi variable name list matching the CasADi variable list is important. opt = {'input_scheme': cas_var_name_list, 'output_scheme': ['f']} # Generate a new CasADi function eqn = Function(name, cas_var_list, [f], opt) # Store the jacobian of the function with respect to each of the variables in an dictionary indexed by the corresponding Pyomo constraint. The stored value is an ordered dictionary so that the ordering of the variables will be preserved. This is important for future function calls. jacs[new_constr] = OrderedDict([(k, { 'jac': eqn.jacobian(k, 'f'), 'var': flat_vars[k][1] }) for k in cas_var_name_list])
def apply_OA(nl_set, oa_block, jacs): """Applies outer approximation with augmented Lagrangian/equality relaxation scheme Args: equip (Block): Pyomo block for process unit description nl_set (set): Set of nonlinear constraints oa_block (Block): Pyomo block to hold generated OA cuts jacs (dict): dictionary in which to store jacobian information Returns: None Raises: ValueError: if a variable naming conflict exists """ # Set up outer approximation iterations set oa_iter = oa_block.oa_iter = Set() for con_data in nl_set: if not con_data.active: continue # if constraint is inactive, skip it # process this constraint var_dict = OrderedDict() # Build CasADi expression to mirror existing constraint f = process_constraint(con_data, var_dict) eqn = Function( # constr[indx].local_name, # TODO: need to figure out how to name function properly con_data.parent_component().local_name, [cas_var for pyo_var, cas_var in itervalues(var_dict)], [f], { 'input_scheme': list(var_dict.keys()), 'output_scheme': ['f'] }) # Store the jacobian functions for future evaluation jacs[con_data] = { cname: eqn.jacobian(cname, 'f') for cname in iterkeys(var_dict) } if '_vars_' in jacs[con_data]: raise ValueError( 'Name conflict: cannot have a variable named "_vars_"') else: jacs[con_data]['_vars_'] = { pyo_var.local_name: pyo_var for pyo_var, cas_var in itervalues(var_dict) } # Generate empty constraints for holding OA constraints if not hasattr(oa_block, con_data.parent_component().local_name): if not con_data.parent_component().is_indexed(): new_constr = Constraint(oa_iter) else: new_constr = Constraint(oa_iter, con_data.parent_component()._index) setattr(oa_block, con_data.parent_component().local_name, new_constr) # Generate slack variables new_slack_var = Var(oa_iter, domain=NonNegativeReals, initialize=0, bounds=(0, 1000)) setattr(oa_block, '_slack_' + con_data.parent_component().local_name, new_slack_var)