class SolverGurobi(Solver): def __init__(self): Solver.__init__(self, GRB.BINARY, GRB.CONTINUOUS, GRB.INTEGER, GRB.OPTIMAL, GRB.INFEASIBLE, GRB.UNBOUNDED, GRB.INTERRUPTED, GRB.SUBOPTIMAL, GRB.EQUAL, GRB.LESS_EQUAL, GRB.GREATER_EQUAL, GRB.MAXIMIZE, GRB.MINIMIZE, GRB.INFINITY) setParam('OutputFlag', 0) setParam('IntFeasTol', 1e-9) self.problem = Model('gurobi_problem') def _add_variable(self, var): if var.var_type == self.BINARY: self.problem.addVar(name=var.name, vtype=var.var_type) else: self.problem.addVar(name=var.name, lb=var.lower_bound, ub=var.upper_bound, vtype=var.var_type) def _del_variable(self, name): self.problem.remove(self.problem.getVarByName(name)) def _add_constraint(self, const): lhs = LinExpr() for elem in const.lhs: var = self.problem.getVarByName(elem.name) lhs.add(var, elem.coefficient) self.problem.addConstr(lhs, const.sense, const.rhs, const.name) def _del_constraint(self, name): self.problem.remove(self.problem.getConstrByName(name)) def update(self): self.problem.update() def _set_objective(self, objective): expr = LinExpr() for elem in objective.expr: var = self.problem.getVarByName(elem.name) expr.add(var, elem.coefficient) self.problem.setObjective(expr, objective.sense) def get_variable_value(self, name): return self.problem.getVarByName(name).x def get_objective_value(self): return self.problem.objVal def get_solution_status(self): return self.problem.status def _optimize(self): self.problem.optimize()
class BendersSolver: def __init__(self, supply, agents, approximator, log): """ :param b: b of LP. If n=len(agents) then the first n values are 1./alpha and n+1 value is supply/alpha. :param agents: List of agents. """ # Setting up master problem self.m = Model("master-problem") self.m.params.LogToConsole = 0 # noinspection PyArgumentList,PyArgumentList,PyArgumentList self.z = self.m.addVar(lb=-GRB.INFINITY, ub=GRB.INFINITY, name="z") self.approximator = approximator self.agents = agents self.log = log self.allocations = {'X0': Allocation()} self.b = [(1. / self.approximator.gap) for i in range(0, len(self.agents))] self.b.append(supply / self.approximator.gap) # noinspection PyArgumentList,PyArgumentList,PyArgumentList self.price_var = self.m.addVar(lb=-GRB.INFINITY, ub=0, name="price") self.utility_vars = dict() for agent in self.agents: # noinspection PyArgumentList,PyArgumentList,PyArgumentList self.utility_vars[agent.id] = self.m.addVar(lb=-GRB.INFINITY, ub=0, name="u_%s" % agent.id) self.m.update() # Initial constraints for empty allocation self.add_benders_cut(Allocation(), "X0") self.old_price_constraint = 0. self.add_price_constraint(0.) self.m.setObjective(self.z, GRB.MAXIMIZE) self.price_changed = False self.give_second_chance = True self.old_z = 0. self.old_utilities = dict() @property def price(self): """ :return: Returns current price (positive). """ try: return math.fabs(self.price_var.x) except GurobiError: return None @property def utilities(self): """ :return: Returns current utilities (positive): dict(agent_id: utility) """ return dict((v[0], math.fabs(v[1].x)) for v in self.utility_vars.iteritems()) @property def objective(self): try: return self.z.x except GurobiError: return 0. def solve(self): while self.iterate(): pass return self.allocations def iterate(self): """ Performs one iteration of the Bender Auction. Optimizes current master problem, requests an approximate \ allocation based on current prices and utilities, calculates phi with current optimal values of master problem \ and then compares this with current z value. :return: False if auction is done and True if a Bender's cut has been added and the auction continues. """ iteration = len(self.allocations) self.log.log('') self.log.log('######## ITERATION %s ########' % iteration) self.optimize() no_change = self.old_z == self.objective and all( [any(old_utility == utility for old_utility in self.old_utilities) for utility in self.utilities]) self.log.log("no change ... %s" % no_change) self.old_z = self.objective self.old_utilities = self.utilities # allocation := X allocation = self.approximator.approximate(self.price, self.utilities) # first_term - second_term = w*b - (c + wA) * X # first_term is w*b first_term = sum([-w * b for w, b in zip(self.utilities.values() + [self.price], self.b)]) # second_term is (c + wA) * X second_term = 0 for assignment in allocation.assignments: # for each x_ij which is 1 we generate c + wA which is (for MUA): v_i(j) + price * j + u_i second_term += self.price * assignment.quantity second_term += -self.utilities[assignment.agent_id] second_term += assignment.valuation phi = first_term - second_term self.log.log('phi = %s - %s = %s' % (first_term, second_term, phi)) # check if phi with current result of master-problem is z (with tolerance) if math.fabs(phi - self.z.x) < epsilon or iteration > iteration_abort_threshold: self.remove_bad_cuts() self.set_allocation_probabilities() self.print_results() return False else: self.give_second_chance = True # otherwise continue and add cut based on this iteration's allocation allocation_name = 'X%s' % iteration self.allocations[allocation_name] = allocation self.add_benders_cut(allocation, allocation_name) self.set_allocation_probabilities() return True def add_price_constraint(self, new_price=None): if True: return None try: self.m.remove(self.m.getConstrByName("price_constraint")) except GurobiError: pass if new_price != None: self.old_price_constraint = new_price else: self.old_price_constraint -= .5 self.log.log(self.old_price_constraint) self.m.addConstr(self.price_var, GRB.EQUAL, self.old_price_constraint, name="price_constraint") def print_results(self): """ Prints results in console. """ self.log.log('') self.log.log('####### SUMMARY #######') self.log.log('') self.m.write("master-program.lp") for item in self.allocations.iteritems(): if item[1].probability > 0: # noinspection PyArgumentList self.log.log('%s (%s)' % (item[0], item[1].probability)) item[1].print_me(self.log) self.log.log('') if self.price_changed: self.log.log('Price has decreased at some point.') self.log.log('%s iterations needed' % len(self.allocations)) self.log.log('E[Social welfare] is %s' % -self.z.x) def optimize(self): """ Optimizes current master-problem and outputs optimal values and dual variables """ # for observation we save the current price current_price = self.price if self.price else 0. self.m.optimize() if current_price > self.price: self.price_changed = True for v in [v for v in self.m.getVars() if v.x != 0.]: self.log.log('%s %g' % (v.varName, v.x)) for l in self.m.getConstrs(): if l.Pi > 0: self.log.log('%s %g' % (l.constrName, l.Pi)) def remove_bad_cuts(self): for l in self.m.getConstrs(): if l.Pi == 0: self.m.remove(l) def add_benders_cut(self, allocation, name): """ Adds another cut z <= wb - (c + wA) * X. :param allocation: Allocation as list of Assignment. :param name: Name for new constraint. """ # wb part of cut expr = LinExpr(self.b, self.utility_vars.values() + [self.price_var]) for assignment in allocation.assignments: # c expr.addConstant(-assignment.valuation) # if w=(u, p) then this is the uA part (for columns where X is 1) expr.addTerms(-1, self.utility_vars[assignment.agent_id]) # if w=(u, p) then this is the pA part (for columns where X is 1) expr.addTerms(-assignment.quantity, self.price_var) # we get v_i(j) + u_i + j * price summed over all i,j where x_ij = 1 self.m.addConstr(self.z, GRB.LESS_EQUAL, expr, name=name) def set_allocation_probabilities(self): for item in self.allocations.iteritems(): # noinspection PyArgumentList constraint = self.m.getConstrByName(item[0]) item[1].probability = constraint.pi if constraint else 0