def populate_dual_subproblem(data, upper_cost=None, flow_cost=None): """ Function that populates the Benders Dual Subproblem, as suggested by the paper "Minimal Infeasible Subsystems and Bender's cuts" by Fischetti, Salvagnin and Zanette. :param data: Problem data structure :param upper_cost: Link setup decisions fixed in the master :param flow_cost: This is the cost of the continuous variables of the master problem, as explained in the paper :return: Numpy array of Gurobi model objects """ # Gurobi model objects subproblems = np.empty(shape=(data.periods, data.commodities), dtype=object) # Construct model for period/commodity 0. # Then, copy this and change the coefficients dual_subproblem = Model('dual_subproblem_(0,0)') # Ranges we are going to need arcs, periods, commodities = xrange(data.arcs.size), xrange( data.periods), xrange(data.commodities) # Origins and destinations of commodities origins, destinations = data.origins, data.destinations # We use arrays to store variable indexes and variable objects. Why use # both? Gurobi wont let us get the values of individual variables # within a callback.. We just get the values of a large array of # variables, in the order they were initially defined. To separate them # in variable categories, we will have to use index arrays flow_index = np.zeros(shape=data.nodes, dtype=int) flow_duals = np.empty_like(flow_index, dtype=object) ubounds_index = np.zeros(shape=len(arcs), dtype=int) ubounds_duals = np.empty_like(ubounds_index, dtype=object) # Makes sure we don't add variables more than once flow_duals_names = set() if upper_cost is None: upper_cost = np.zeros(shape=(len(periods), len(arcs)), dtype=float) if flow_cost is None: flow_cost = np.zeros(shape=(len(periods), len(commodities)), dtype=float) # Populate all variables in one loop, keep track of their indexes # Data for period = 0, com = 0 count = 0 for arc in arcs: ubounds_duals[arc] = dual_subproblem.addVar( obj=-upper_cost[0, arc], lb=0., name='ubound_dual_a{}'.format(arc)) ubounds_index[arc] = count count += 1 start_node, end_node = get_2d_index(data.arcs[arc], data.nodes) start_node, end_node = start_node - 1, end_node - 1 for node in (start_node, end_node): var_name = 'flow_dual_n{}'.format(node) if var_name not in flow_duals_names: flow_duals_names.add(var_name) obj, ub = 0., GRB.INFINITY if data.origins[0] == node: obj = 1. if data.destinations[0] == node: obj = -1. ub = 0. flow_duals[node] = \ dual_subproblem.addVar( obj=obj, lb=0., name=var_name) flow_index[node] = count count += 1 opt_var = dual_subproblem.addVar(obj=-flow_cost[0, 0], lb=0., name='optimality_var') dual_subproblem.params.threads = 2 dual_subproblem.params.LogFile = "" dual_subproblem.update() # Add constraints demand = data.demand[0, 0] for arc in arcs: start_node, end_node = get_2d_index(data.arcs[arc], data.nodes) start_node, end_node = start_node - 1, end_node - 1 lhs = flow_duals[start_node] - flow_duals[end_node] \ - ubounds_duals[arc] - \ opt_var * data.variable_cost[arc] * demand dual_subproblem.addConstr(lhs <= 0., name='flow_a{}'.format(arc)) # Original Fischetti model lhs = quicksum(ubounds_duals) + opt_var dual_subproblem.addConstr(lhs == 1, name='normalization_constraint') # Store variable indices dual_subproblem._ubounds_index = ubounds_index dual_subproblem._flow_index = flow_index dual_subproblem._all_variables = np.array(dual_subproblem.getVars()) dual_subproblem._flow_duals = np.take(dual_subproblem._all_variables, flow_index) dual_subproblem._ubound_duals = np.take(dual_subproblem._all_variables, ubounds_index) dual_subproblem.setParam('OutputFlag', 0) dual_subproblem.modelSense = GRB.MAXIMIZE dual_subproblem.update() subproblems[0, 0] = dual_subproblem for period, com in product(periods, commodities): if (period, com) != (0, 0): model = dual_subproblem.copy() optimality_var = model.getVarByName('optimality_var') optimality_var.Obj = -flow_cost[period, com] demand = data.demand[period, com] for node in xrange(data.nodes): variable = model.getVarByName('flow_dual_n{}'.format(node)) if origins[com] == node: obj = 1. elif destinations[com] == node: obj = -1. else: obj = 0. variable.obj = obj for arc in arcs: variable = model.getVarByName('ubound_dual_a{}'.format(arc)) variable.Obj = -np.sum(upper_cost[:period + 1, arc]) constraint = model.getConstrByName('flow_a{}'.format(arc)) model.chgCoeff(constraint, optimality_var, -demand * data.variable_cost[arc]) model._all_variables = np.array(model.getVars()) model.update() subproblems[period, com] = model return subproblems
def populate_dual_subproblem(data): """ Function that populates the Benders Dual Subproblem, as suggested by the paper "Minimal Infeasible Subsystems and Bender's cuts" by Fischetti, Salvagnin and Zanette. :param data: Problem data structure :param upper_cost: Link setup decisions fixed in the master :param flow_cost: This is the cost of the continuous variables of the master problem, as explained in the paper :return: Numpy array of Gurobi model objects """ # Gurobi model objects subproblems = np.empty(shape=(data.periods, data.commodities), dtype=object) # Construct model for period/commodity 0. # Then, copy this and change the coefficients subproblem = Model('subproblem_(0,0)') # Ranges we are going to need arcs, periods, commodities, nodes = xrange(data.arcs.size), xrange( data.periods), xrange(data.commodities), xrange(data.nodes) # Other data demand, var_cost = data.demand, data.variable_cost # Origins and destinations of commodities origins, destinations = data.origins, data.destinations # We use arrays to store variable indexes and variable objects. Why use # both? Gurobi wont let us get the values of individual variables # within a callback.. We just get the values of a large array of # variables, in the order they were initially defined. To separate them # in variable categories, we will have to use index arrays flow_vars = np.empty_like(arcs, dtype=object) # Populate all variables in one loop, keep track of their indexes # Data for period = 0, com = 0 for arc in arcs: flow_vars[arc] = subproblem.addVar(obj=demand[0, 0] * var_cost[arc], lb=0., ub=1., name='flow_a{}'.format(arc)) subproblem.update() # Add constraints for node in nodes: out_arcs = get_2d_index(data.arcs, data.nodes)[0] == node + 1 in_arcs = get_2d_index(data.arcs, data.nodes)[1] == node + 1 lhs = quicksum(flow_vars[out_arcs]) - quicksum(flow_vars[in_arcs]) subproblem.addConstr(lhs == 0., name='flow_bal{}'.format(node)) subproblem.update() # Store variables subproblem._all_variables = flow_vars.tolist() # Set parameters subproblem.setParam('OutputFlag', 0) subproblem.modelSense = GRB.MINIMIZE subproblem.params.threads = 2 subproblem.params.LogFile = "" subproblem.update() subproblems[0, 0] = subproblem for period, com in product(periods, commodities): if (period, com) != (0, 0): model = subproblem.copy() model.ModelName = 'subproblem_({},{})'.format(period, com) flow_cost = data.demand[period, com] * var_cost model.setObjective(LinExpr(flow_cost.tolist(), model.getVars())) model.setAttr('rhs', model.getConstrs(), [0.0] * data.nodes) model._all_variables = model.getVars() model.update() subproblems[period, com] = model return subproblems