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()