def _create_using(self, model, **kwds): """ Force all variables to lie in the nonnegative orthant. Required arguments: model The model to transform. Optional keyword arguments: pos_suffix The suffix applied to the 'positive' component of converted variables. Default is '_plus'. neg_suffix The suffix applied to the 'positive' component of converted variables. Default is '_minus'. """ # # Optional naming schemes # pos_suffix = kwds.pop("pos_suffix", "_plus") neg_suffix = kwds.pop("neg_suffix", "_minus") # # We first perform an abstract problem transformation. Then, if model # data is available, we instantiate the new model. If not, we construct # a mapping that can later be used to populate the new model. # nonneg = model.clone() components = collectAbstractComponents(nonneg) # Map from variable base names to a {index, rule} map constraint_rules = {} # Map from variable base names to a rule defining the domains for that # variable domain_rules = {} # Map from variable base names to its set of indices var_indices = {} # Map from fully qualified variable names to replacement expressions. # For now, it is actually a map from a variable name to a closure that # must later be evaulated with a model containing the replacement # variables. var_map = {} # # Get the constraints that enforce the bounds and domains of each # variable # for var_name in components["Var"]: var = nonneg.__getattribute__(var_name) # Individual bounds and domains orig_bounds = {} orig_domain = {} # New indices indices = set() # Map from constraint names to a constraint rule. constraints = {} # Map from variable indices to a domain domains = {} for ndx in var: # Fully qualified variable name vname = create_name(str(var_name), ndx) # We convert each index to a string to avoid difficult issues # regarding appending a suffix to tuples. # # If the index is None, this casts the index to a string, # which doesn't match up with how Pyomo treats None indices # internally. Replace with "" to be consistent. if ndx is None: v_ndx = "" else: v_ndx = str(ndx) # Get the variable bounds lb = value(var[ndx].lb) ub = value(var[ndx].ub) orig_bounds[ndx] = (lb, ub) # Get the variable domain if var[ndx].domain is not None: orig_domain[ndx] = var[ndx].domain else: orig_domain[ndx] = var.domain # Determine the replacement expression. Either a new single # variable with the same attributes, or a sum of two new # variables. # # If both the bounds and domain allow for negative values, # replace the variable with the sum of nonnegative ones. bounds_neg = (orig_bounds[ndx] == (None, None) or orig_bounds[ndx][0] is None or orig_bounds[ndx][0] < 0) domain_neg = (orig_domain[ndx] is None or orig_domain[ndx].bounds()[0] is None or orig_domain[ndx].bounds()[0] < 0) if bounds_neg and domain_neg: # Make two new variables. posVarSuffix = "%s%s" % (v_ndx, pos_suffix) negVarSuffix = "%s%s" % (v_ndx, neg_suffix) new_indices = (posVarSuffix, negVarSuffix) # Replace the original variable with a sum expression expr_dict = {posVarSuffix: 1, negVarSuffix: -1} else: # Add the new index. Lie if is 'None', since Pyomo treats # 'None' specially as a key. # # More lies: don't let a blank index exist. Replace it with # '_'. I don't actually have a justification for this other # than that allowing "" as a key will eventually almost # certainly lead to a strange bug. if v_ndx is None: t_ndx = "None" elif v_ndx == "": t_ndx = "_" else: t_ndx = v_ndx new_indices = (t_ndx, ) # Replace the original variable with a sum expression expr_dict = {t_ndx: 1} # Add the new indices for x in new_indices: indices.add(x) # Replace the original variable with an expression var_map[vname] = partial(self.sumRule, var_name, expr_dict) # Enforce bounds as constraints if orig_bounds[ndx] != (None, None): cname = "%s_%s" % (vname, "bounds") tmp = orig_bounds[ndx] constraints[cname] = partial(self.boundsConstraintRule, tmp[0], tmp[1], var_name, expr_dict) # Enforce the bounds of the domain as constraints if orig_domain[ndx] != None: cname = "%s_%s" % (vname, "domain_bounds") tmp = orig_domain[ndx].bounds() constraints[cname] = partial(self.boundsConstraintRule, tmp[0], tmp[1], var_name, expr_dict) # Domain will either be NonNegativeReals, NonNegativeIntegers, # or Binary. We consider Binary because some solvers may # optimize over binary variables. if var[ndx].is_continuous(): for x in new_indices: domains[x] = NonNegativeReals elif var[ndx].is_binary(): for x in new_indices: domains[x] = Binary elif var[ndx].is_integer(): for x in new_indices: domains[x] = NonNegativeIntegers else: logger.warning("Warning: domain '%s' not recognized, " "defaulting to 'NonNegativeReals'" % (var.domain, )) for x in new_indices: domains[x] = NonNegativeReals constraint_rules[var_name] = constraints domain_rules[var_name] = partial(self.exprMapRule, domains) var_indices[var_name] = indices # Remove all existing variables. toRemove = [] for (attr_name, attr) in nonneg.__dict__.items(): if isinstance(attr, Var): toRemove.append(attr_name) for attr_name in toRemove: nonneg.__delattr__(attr_name) # Add the sets defining the variables, then the variables for (k, v) in var_indices.items(): sname = "%s_indices" % k nonneg.__setattr__(sname, Set(initialize=v)) nonneg.__setattr__( k, Var(nonneg.__getattribute__(sname), domain=domain_rules[k], bounds=(0, None))) # Construct the model to get the variables and their indices # recognized in the model ##nonneg = nonneg.create() # Safe to evaluate the modifiedVars mapping for var in var_map: var_map[var] = var_map[var](nonneg) # Map from constraint base names to maps from indices to expressions constraintExprs = {} # # Convert all modified variables in all constraints in the original # problem # for conName in components["Constraint"]: con = nonneg.__getattribute__(conName) # Map from constraint indices to a corrected expression exprMap = {} for (ndx, cdata) in con._data.items(): lower = _walk_expr(cdata.lower, var_map) body = _walk_expr(cdata.body, var_map) upper = _walk_expr(cdata.upper, var_map) # Lie if ndx is None. Pyomo treats 'None' indices specially. if ndx is None: ndx = "None" # Cast indices to strings, otherwise tuples ruin everything exprMap[str(ndx)] = (lower, body, upper) # Add to list of expression maps constraintExprs[conName] = exprMap # Map from constraint base names to maps from indices to expressions objectiveExprs = {} # # Convert all modified variables in all objectives in the original # problem # for objName in components["Objective"]: obj = nonneg.__getattribute__(objName) # Map from objective indices to a corrected expression exprMap = {} for (ndx, odata) in obj._data.items(): exprMap[ndx] = _walk_expr(odata.expr, var_map) # Add to list of expression maps objectiveExprs[objName] = exprMap # Make the modified original constraints for (conName, ruleMap) in constraintExprs.items(): # Make the set of indices sname = conName + "_indices" _set = Set(initialize=ruleMap.keys()) nonneg.__setattr__(sname, _set) _set.construct() # Define the constraint _con = Constraint(nonneg.__getattribute__(sname), rule=partial(self.exprMapRule, ruleMap)) nonneg.__setattr__(conName, _con) _con.construct() # Make the bounds constraints for (varName, ruleMap) in constraint_rules.items(): conName = varName + "_constraints" # Make the set of indices sname = conName + "_indices" _set = Set(initialize=ruleMap.keys()) nonneg.__setattr__(sname, _set) _set.construct() # Define the constraint _con = Constraint(nonneg.__getattribute__(sname), rule=partial(self.delayedExprMapRule, ruleMap)) nonneg.__setattr__(conName, _con) _con.construct() # Make the objectives for (objName, ruleMap) in objectiveExprs.items(): # Make the set of indices sname = objName + "_indices" _set = Set(initialize=ruleMap.keys()) nonneg.__setattr__(sname, _set) _set.construct() # Define the constraint _obj = Objective(nonneg.__getattribute__(sname), rule=partial(self.exprMapRule, ruleMap)) nonneg.__setattr__(objName, _obj) _obj.construct() return nonneg
def form_linearized_objective_constraints(instance_name, instance, scenario_tree, linearize_nonbinary_penalty_terms, breakpoint_strategy, tolerance): # keep track and return what was added to the instance, so # it can be cleaned up if necessary. new_instance_attributes = [] linearization_index_set_name = "PH_LINEARIZATION_INDEX_SET" linearization_index_set = instance.find_component(linearization_index_set_name) if linearization_index_set is None: linearization_index_set = Set(initialize=range(0, linearize_nonbinary_penalty_terms*2), dimen=1, name=linearization_index_set_name) instance.add_component(linearization_index_set_name, linearization_index_set) scenario = scenario_tree.get_scenario(instance_name) nodeid_to_vardata_map = instance._ScenarioTreeSymbolMap.bySymbol for tree_node in scenario._node_list[:-1]: xbar_dict = tree_node._xbars # if linearizing, then we have previously defined a variable # associated with the result of the linearized approximation # of the penalty term - this is simply added to the objective # function. linearized_cost_variable_name = "PHQUADPENALTY_"+str(tree_node._name) linearized_cost_variable = instance.find_component(linearized_cost_variable_name) # grab the linearization constraint associated with the # linearized cost variable, if it exists. otherwise, create it # - but an empty variety. the constraints are stage-specific - # we could index by constraint, but we don't know if that is # really worth the additional effort. linearization_constraint_name = "PH_LINEARIZATION_"+str(tree_node._name) linearization_constraint = instance.find_component(linearization_constraint_name) if linearization_constraint is not None: # clear whatever constraint components are there - there # may be fewer breakpoints, due to tolerances, and we # don't want to the old pieces laying around. linearization_constraint.clear() else: # this is the first time the constraint is being added - # add it to the list of PH-specific constraints for this # instance. new_instance_attributes.append(linearization_constraint_name) nodal_index_set_name = "PHINDEX_"+str(tree_node._name) nodal_index_set = instance.find_component(nodal_index_set_name) assert nodal_index_set is not None linearization_constraint = \ Constraint(nodal_index_set, linearization_index_set, name=linearization_constraint_name) linearization_constraint.construct() instance.add_component(linearization_constraint_name, linearization_constraint) for variable_id in tree_node._variable_ids: # don't add weight terms for derived variables at the tree # node. if variable_id in tree_node._derived_variable_ids: continue if variable_id not in tree_node._minimums: variable_name, index = tree_node._variable_ids[variable_id] raise RuntimeError("No minimum value statistic found for variable=%s " "on tree node=%s; cannot form linearized PH objective" % (variable_name+indexToString(index), tree_node._name)) if variable_id not in tree_node._maximums: variable_name, index = tree_node._variable_ids[variable_id] raise RuntimeError("No maximums value statistic found for " "variable=%s on tree node=%s; cannot " "form linearized PH objective" % (variable_name+indexToString(index), tree_node._name)) xbar = xbar_dict[variable_id] node_min = tree_node._minimums[variable_id] node_max = tree_node._maximums[variable_id] instance_vardata = nodeid_to_vardata_map[variable_id] if (instance_vardata.stale is False) and (instance_vardata.fixed is False): # binaries have already been dealt with in the process of PH objective function formation. if isinstance(instance_vardata.domain, BooleanSet) is False: x = instance_vardata if x.lb is None or x.ub is None: msg = "Missing bound for variable '%s'\n" \ 'Both lower and upper bounds required when' \ ' piece-wise approximating quadratic ' \ 'penalty terms' raise ValueError(msg % instance_vardata.name) lb = value(x.lb) ub = value(x.ub) # compute the breakpoint sequence according to the specified strategy. try: strategy = (compute_uniform_breakpoints, compute_uniform_between_nodestat_breakpoints, compute_uniform_between_woodruff_breakpoints, compute_exponential_from_mean_breakpoints, )[ breakpoint_strategy ] args = ( lb, node_min, xbar, node_max, ub, \ linearize_nonbinary_penalty_terms, tolerance ) breakpoints = strategy( *args ) except ValueError: e = sys.exc_info()[1] msg = 'A breakpoint distribution strategy (%s) ' \ 'is currently not supported within PH!' raise ValueError(msg % breakpoint_strategy) for i in xrange(len(breakpoints)-1): this_lb = breakpoints[i] this_ub = breakpoints[i+1] segment_tuple = create_piecewise_constraint_tuple(this_lb, this_ub, x, xbar, linearized_cost_variable[variable_id], tolerance) linearization_constraint.add((variable_id,i), segment_tuple) return new_instance_attributes