def _create_using(self, model, **kwds): """ Tranform a model to its Lagrangian dual. """ # Optional naming schemes for dual variables and constraints constraint_suffix = kwds.pop("dual_constraint_suffix", "_constraint") variable_prefix = kwds.pop("dual_variable_prefix", "p_") # Optional naming schemes to pass to StandardForm sf_kwds = {} sf_kwds["slack_names"] = kwds.pop("slack_names", "auxiliary_slack") sf_kwds["excess_names"] = kwds.pop("excess_names", "auxiliary_excess") sf_kwds["lb_names"] = kwds.pop("lb_names", "_lower_bound") sf_kwds["ub_names"] = kwds.pop("ub_names", "_upper_bound") sf_kwds["pos_suffix"] = kwds.pop("pos_suffix", "_plus") sf_kwds["neg_suffix"] = kwds.pop("neg_suffix", "_minus") # Get the standard form model sf_transform = StandardForm() sf = sf_transform(model, **sf_kwds) # Roughly, parse the objectives and constraints to form A, b, and c of # # min c'x # s.t. Ax = b # x >= 0 # # and create a new model from them. # We use sparse matrix representations # {constraint_name: {variable_name: coefficient}} A = _sparse(lambda: _sparse(0)) # {constraint_name: coefficient} b = _sparse(0) # {variable_name: coefficient} c = _sparse(0) # Walk constaints for (con_name, con_array) in sf.component_map(Constraint, active=True).items(): for con in (con_array[ndx] for ndx in con_array._index): # The qualified constraint name cname = "%s%s" % (variable_prefix, con.local_name) # Process the body of the constraint body_terms = process_canonical_repn( generate_standard_repn(con.body)) # Add a numeric constant to the 'b' vector, if present b[cname] -= body_terms.pop(None, 0) # Add variable coefficients to the 'A' matrix row = _sparse(0) for (vname, coef) in body_terms.items(): row["%s%s" % (vname, constraint_suffix)] += coef # Process the upper bound of the constraint. We rely on # StandardForm to produce equality constraints, thus # requiring us only to check the lower bounds. lower_terms = process_canonical_repn( generate_standard_repn(con.lower)) # Add a numeric constant to the 'b' matrix, if present b[cname] += lower_terms.pop(None, 0) # Add any variables to the 'A' matrix, if present for (vname, coef) in lower_terms.items(): row["%s%s" % (vname, constraint_suffix)] -= coef A[cname] = row # Walk objectives. Multiply all coefficients by the objective's 'sense' # to convert maximizing objectives to minimizing ones. for (obj_name, obj_array) in sf.component_map(Objective, active=True).items(): for obj in (obj_array[ndx] for ndx in obj_array._index): # The qualified objective name # Process the objective terms = process_canonical_repn( generate_standard_repn(obj.expr)) # Add coefficients for (name, coef) in terms.items(): c["%s%s" % (name, constraint_suffix)] += coef*obj_array.sense # Form the dual dual = AbstractModel() # Make constraint index set constraint_set_init = [] for (var_name, var_array) in sf.component_map(Var, active=True).items(): for var in (var_array[ndx] for ndx in var_array._index): constraint_set_init.append("%s%s" % (var.local_name, constraint_suffix)) # Make variable index set variable_set_init = [] dual_variable_roots = [] for (con_name, con_array) in sf.component_map(Constraint, active=True).items(): for con in (con_array[ndx] for ndx in con_array._index): dual_variable_roots.append(con.local_name) variable_set_init.append("%s%s" % (variable_prefix, con.local_name)) # Create the dual Set and Var objects dual.var_set = Set(initialize=variable_set_init) dual.con_set = Set(initialize=constraint_set_init) dual.vars = Var(dual.var_set) # Make the dual constraints def constraintRule(A, c, ndx, model): return sum(A[v][ndx] * model.vars[v] for v in model.var_set) <= \ c[ndx] dual.cons = Constraint(dual.con_set, rule=partial(constraintRule, A, c)) # Make the dual objective (maximizing) def objectiveRule(b, model): return sum(b[v] * model.vars[v] for v in model.var_set) dual.obj = Objective(rule=partial(objectiveRule, b), sense=maximize) return dual.create()
def _create_using(self, model, **kwds): """ Tranform a model to its Lagrangian dual. """ # Optional naming schemes for dual variables and constraints constraint_suffix = kwds.pop("dual_constraint_suffix", "_constraint") variable_prefix = kwds.pop("dual_variable_prefix", "p_") # Optional naming schemes to pass to StandardForm sf_kwds = {} sf_kwds["slack_names"] = kwds.pop("slack_names", "auxiliary_slack") sf_kwds["excess_names"] = kwds.pop("excess_names", "auxiliary_excess") sf_kwds["lb_names"] = kwds.pop("lb_names", "_lower_bound") sf_kwds["ub_names"] = kwds.pop("ub_names", "_upper_bound") sf_kwds["pos_suffix"] = kwds.pop("pos_suffix", "_plus") sf_kwds["neg_suffix"] = kwds.pop("neg_suffix", "_minus") # Get the standard form model sf_transform = StandardForm() sf = sf_transform(model, **sf_kwds) # Roughly, parse the objectives and constraints to form A, b, and c of # # min c'x # s.t. Ax = b # x >= 0 # # and create a new model from them. # We use sparse matrix representations # {constraint_name: {variable_name: coefficient}} A = _sparse(lambda: _sparse(0)) # {constraint_name: coefficient} b = _sparse(0) # {variable_name: coefficient} c = _sparse(0) # Walk constaints for (con_name, con_array) in sf.component_map(Constraint, active=True).items(): for con in (con_array[ndx] for ndx in con_array._index): # The qualified constraint name cname = "%s%s" % (variable_prefix, con.local_name) # Process the body of the constraint body_terms = process_canonical_repn( generate_canonical_repn(con.body)) # Add a numeric constant to the 'b' vector, if present b[cname] -= body_terms.pop(None, 0) # Add variable coefficients to the 'A' matrix row = _sparse(0) for (vname, coef) in body_terms.items(): row["%s%s" % (vname, constraint_suffix)] += coef # Process the upper bound of the constraint. We rely on # StandardForm to produce equality constraints, thus # requiring us only to check the lower bounds. lower_terms = process_canonical_repn( generate_canonical_repn(con.lower)) # Add a numeric constant to the 'b' matrix, if present b[cname] += lower_terms.pop(None, 0) # Add any variables to the 'A' matrix, if present for (vname, coef) in lower_terms.items(): row["%s%s" % (vname, constraint_suffix)] -= coef A[cname] = row # Walk objectives. Multiply all coefficients by the objective's 'sense' # to convert maximizing objectives to minimizing ones. for (obj_name, obj_array) in sf.component_map(Objective, active=True).items(): for obj in (obj_array[ndx] for ndx in obj_array._index): # The qualified objective name # Process the objective terms = process_canonical_repn( generate_canonical_repn(obj.expr)) # Add coefficients for (name, coef) in terms.items(): c["%s%s" % (name, constraint_suffix)] += coef*obj_array.sense # Form the dual dual = AbstractModel() # Make constraint index set constraint_set_init = [] for (var_name, var_array) in sf.component_map(Var, active=True).items(): for var in (var_array[ndx] for ndx in var_array._index): constraint_set_init.append("%s%s" % (var.local_name, constraint_suffix)) # Make variable index set variable_set_init = [] dual_variable_roots = [] for (con_name, con_array) in sf.component_map(Constraint, active=True).items(): for con in (con_array[ndx] for ndx in con_array._index): dual_variable_roots.append(con.local_name) variable_set_init.append("%s%s" % (variable_prefix, con.local_name)) # Create the dual Set and Var objects dual.var_set = Set(initialize=variable_set_init) dual.con_set = Set(initialize=constraint_set_init) dual.vars = Var(dual.var_set) # Make the dual constraints def constraintRule(A, c, ndx, model): return sum(A[v][ndx] * model.vars[v] for v in model.var_set) <= \ c[ndx] dual.cons = Constraint(dual.con_set, rule=partial(constraintRule, A, c)) # Make the dual objective (maximizing) def objectiveRule(b, model): return sum(b[v] * model.vars[v] for v in model.var_set) dual.obj = Objective(rule=partial(objectiveRule, b), sense=maximize) return dual.create()
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 _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 = var[ndx].lb ub = var[ndx].ub if lb is not None: lb = value(lb) if ub is not None: ub = value(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 isinstance(orig_domain[ndx], RealSet): for x in new_indices: domains[x] = NonNegativeReals elif isinstance(orig_domain[ndx], IntegerSet): for x in new_indices: domains[x] = NonNegativeIntegers elif isinstance(orig_domain[ndx], BooleanSet): for x in new_indices: domains[x] = Binary else: print ("Warning: domain '%s' not recognized, " + \ "defaulting to 'Reals'") % (str(var.domain)) for x in new_indices: domains[x] = Reals 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 = self._walk_expr(cdata.lower, var_map) body = self._walk_expr(cdata.body, var_map) upper = self._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] = self._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