def createInstance(self): model = Model() modelVars = {} # x variables are assigned. for i in range(self.m): for j in range(self.n): Vname = 'x_{}_{}'.format(i, j) modelVars[Vname] = model.addVar(vtype=GRB.BINARY, name=Vname) # Term 2: Objective Function obj = quicksum(self.v[j] * modelVars['x_{}_{}'.format(i, j)] for i in range(self.m) for j in range(self.n)) # Term 3: Each job is assigned to at most 1 machine. model.addConstrs( quicksum(modelVars['x_{}_{}'.format(i, j)] for i in range(self.m)) <= 1 for j in range(self.n)) # Term 4: Allows the assignment of at most R resources to a machine. model.addConstrs( quicksum(modelVars['x_{}_{}'.format(i, j)] * self.r[j] for j in self.J[h]) <= self.R for i in range(self.m) for h in range(1, self.k + 1)) # Term 5: Implicit in varaible definition. # Objective Function is set. model._vars = modelVars model.setObjective(obj, GRB.MAXIMIZE) #print(model.getAttr('ModelSense') == GRB.MINIMIZE) return model
def _mmp_solve(w1_ij, x_ij_keys, n, w2_ij=None): """A helper function that solves a weighted maximum matching problem. """ m = Model("MBSA") if __debug__: log(DEBUG, "") log( DEBUG, "Solving a weighted maximum matching problem with " + "%d savings weights." % len(w1_ij)) # build model x_ij = m.addVars(x_ij_keys, obj=w1_ij, vtype=GRB.BINARY, name='x') _mmp_add_cnts_sum(m, x_ij, x_ij_keys, w1_ij, n) m._vars = x_ij m.modelSense = GRB.MAXIMIZE m.update() # disable output m.setParam('OutputFlag', 0) m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME) m.setParam('Threads', MIP_SOLVER_THREADS) #m.write("out.lp") m.optimize() # restore SIGINT callback handler which is changed by gurobipy signal(SIGINT, default_int_handler) if __debug__: log(DEBUG - 1, "Gurobi runtime = %.2f" % m.Runtime) if m.Status == GRB.OPTIMAL: if w2_ij == None: max_wt, max_merge = max( (w1_ij[k], x_ij_keys[k]) for k, v in enumerate(m.X) if v) else: max_wt, _, max_merge = max((w1_ij[k], w2_ij[k], x_ij_keys[k]) for k, v in enumerate(m.X) if v) return max_wt, max_merge[0], max_merge[1] elif m.Status == GRB.TIME_LIMIT: raise GurobiError( 10023, "Gurobi timeout reached when attempting to solve GAP") elif m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() return None
def gurobi_tsp(distance_matrix): """ Solves tsp problem. :param distance_matrix: symmetric matrix of distances, where the i,j element is the distance between object i and j :return: matrix containing {0, 1}, 1 for each transition that is included in the tsp solution """ n = len(distance_matrix) m = Model() m.setParam("OutputFlag", False) m.setParam("Threads", 1) # Create variables vars = {} for i in range(n): for j in range(i + 1): vars[i, j] = m.addVar(obj=0.0 if i == j else distance_matrix[i][j], vtype=GRB.BINARY, name="e" + str(i) + "_" + str(j)) vars[j, i] = vars[i, j] m.update() # Add degree-2 constraint, and forbid loops for i in range(n): m.addConstr(quicksum(vars[i, j] for j in range(n)) == 2) vars[i, i].ub = 0 m.update() # Optimize model m._vars = vars m.params.LazyConstraints = 1 def subtour_fn(model, where): return subtourelim(n, model, where) m.optimize(subtour_fn) solution = m.getAttr("x", vars) selected = [(i, j) for i in range(n) for j in range(n) if solution[i, j] > 0.5] result = np.zeros_like(distance_matrix) for (i, j) in selected: result[i][j] = 1 return result
def tsp_gurobi(edges): """ Modeled using GUROBI python example. """ from gurobipy import Model, GRB, quicksum edges = populate_edge_weights(edges) incoming, outgoing, nodes = node_to_edge(edges) idx = dict((n, i) for i, n in enumerate(nodes)) nedges = len(edges) n = len(nodes) m = Model() step = lambda x: "u_{0}".format(x) # Create variables vars = {} for i, (a, b, w) in enumerate(edges): vars[i] = m.addVar(obj=w, vtype=GRB.BINARY, name=str(i)) for u in nodes[1:]: u = step(u) vars[u] = m.addVar(obj=0, vtype=GRB.INTEGER, name=u) m.update() # Bounds for step variables for u in nodes[1:]: u = step(u) vars[u].lb = 1 vars[u].ub = n - 1 # Add degree constraint for v in nodes: incoming_edges = incoming[v] outgoing_edges = outgoing[v] m.addConstr(quicksum(vars[x] for x in incoming_edges) == 1) m.addConstr(quicksum(vars[x] for x in outgoing_edges) == 1) # Subtour elimination edge_store = dict(((idx[a], idx[b]), i) for i, (a, b, w) in enumerate(edges)) # Given a list of edges, finds the shortest subtour def subtour(s_edges): visited = [False] * n cycles = [] lengths = [] selected = [[] for i in range(n)] for x, y in s_edges: selected[x].append(y) while True: current = visited.index(False) thiscycle = [current] while True: visited[current] = True neighbors = [x for x in selected[current] if not visited[x]] if len(neighbors) == 0: break current = neighbors[0] thiscycle.append(current) cycles.append(thiscycle) lengths.append(len(thiscycle)) if sum(lengths) == n: break return cycles[lengths.index(min(lengths))] def subtourelim(model, where): if where != GRB.callback.MIPSOL: return selected = [] # make a list of edges selected in the solution sol = model.cbGetSolution([model._vars[i] for i in range(nedges)]) selected = [edges[i] for i, x in enumerate(sol) if x > .5] selected = [(idx[a], idx[b]) for a, b, w in selected] # find the shortest cycle in the selected edge list tour = subtour(selected) if len(tour) == n: return # add a subtour elimination constraint c = tour incident = [edge_store[a, b] for a, b in pairwise(c + [c[0]])] model.cbLazy(quicksum(model._vars[x] for x in incident) <= len(tour) - 1) m.update() m._vars = vars m.params.LazyConstraints = 1 m.optimize(subtourelim) selected = [v.varName for v in m.getVars() if v.x > .5] selected = [int(x) for x in selected if x[:2] != "u_"] results = sorted(x for i, x in enumerate(edges) if i in selected) \ if selected else None return results
def _solve_gap(N, D_s, d, C, K, L=None, L_ctr_multipiler=1.0): """A helper function that Solves VRP as a Generalized Assignment Problem to assign customers to vehicles with a objective function that the delivery cost as described in (Fisher & Jaikumar 1981). D_s is the distance matrix complemented with distances to K seed points. That is: [D_0] D_s = [S ], where D_0 is the first row of the full distance matrix and S is the distances from seed points to node points d is the list of customer demands with d[0]=0 being the depot node C is the capacity of the K identical trucks also, additional (and optional) constraints can be given: L is the maximum tour cost/duration/length L_ctr_multipiler allows iteratively adjusting the max route cost approximation constraint in order to avoid producing assignments that are ruled infeasible by the feasibility checker. -- Fisher, M. L. and Jaikumar, R. (1981), A generalized assignment heuristic for vehicle routing. Networks, 11: 109-124. """ ## build the cost approximation matrix "insertion_cost" # it is ~ a insertion cost matrix, where each coefficient is the cost # of inserting customer i to the route consisting visit to seed k. # # we assume that distances are symmetric, but if asymmetric # distances are to be used, take min # d_{ik} = min(c_{0i}+c_{i{i_k}}+c_{i{i_k}}, # c_{0{i_k}}+c_[{i_k}i}+c_{i0}) # -(c_{0{i_k}}+c_{{i_k}0}) m = Model("GAPCVRP") # the order of the keys is important when we interpret the results Y_ik_keys = [(i, k) for k in range(K) for i in range(1, N)] # delivery cost approximation coefficients for the objective function insertion_cost = {(i,k): D_s[0,i]+D_s[k,i]-D_s[k,0] \ for i,k in Y_ik_keys} # variables and the objective Y_ik = m.addVars(Y_ik_keys, obj=insertion_cost, vtype=GRB.BINARY, name='y') ## constraints # c1, the capacity constraint and optional tour cost constraint cl approx_route_cost_constraints = [] if C: c1_coeffs = d[1:] for k in range(K): ck_vars = [Y_ik[i, k] for i in range(1, N)] if C: c1_lhs = LinExpr(c1_coeffs, ck_vars) #c1_lhs = Y_ik.prod(c1_coeffs, '*', k) m.addConstr(c1_lhs <= C, "c1_k%d" % k) # ct = optional tour cost constraints # it is a bit hidden, but the additional side constraint can be found # from Fisher & Jaikumar (1981) p121, 2. paragraph. # However, for whatever reason, this does not seem to produce the # same results as reported in their paper as the constraint easily # starts to make the problem infeasible and the exact mechanism to # recover that is not specified in the paper. if L: ct_coeffs = [ insertion_cost[(i, k)] * L_ctr_multipiler for i in range(1, N) ] ct_lhs = LinExpr(ct_coeffs, ck_vars) #ct_lhs = Y_ik.prod(ct_coeffs, '*', k) constr_l = m.addConstr(ct_lhs <= L, "cl_k%d" % k) approx_route_cost_constraints.append(constr_l) # c2, the assignment constraints for i in range(1, N): # c2_1..N every node assigned only to 1 route m.addConstr(Y_ik.sum(i, '*') == 1, "c1_i%d" % i) ## update the model and solve m._vars = Y_ik m.modelSense = GRB.MINIMIZE m.update() #m.write("gapvrp_model.lp") # disable output m.setParam('OutputFlag', 0) m.setParam('Threads', MIP_SOLVER_THREADS) # REMOVEME m.setParam('MIPFocus', 3) m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME) m.optimize() # restore SIGINT callback handler which is changed by gurobipy signal(SIGINT, default_int_handler) if __debug__: log(DEBUG - 1, "Gurobi runtime = %.2f" % m.Runtime) if m.Status == GRB.OPTIMAL: return _decision_variables_to_assignments(m, Y_ik, N, K) elif m.Status == GRB.INFEASIBLE and L: # relax the model and allow violating minimal number of the approximate # route length constraints pens = [1.0] * len(approx_route_cost_constraints) m.feasRelax(1, True, None, None, None, approx_route_cost_constraints, pens) # TODO: not sure if feasRelax can change Status, test it someday if m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() # pass it on m.optimize() # restore SIGINT callback handler which is changed by gurobipy signal(SIGINT, default_int_handler) status = m.Status if __debug__: log(DEBUG - 1, "Relaxed problem Gurobi runtime = %.2f" % m.Runtime) if status == GRB.OPTIMAL: return _decision_variables_to_assignments(m, Y_ik, N, K) elif status == GRB.TIME_LIMIT: raise GurobiError( 10023, "Gurobi timeout reached when attempting to solve relaxed SCPCVRP" ) elif m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() # pass it on return None elif m.Status == GRB.TIME_LIMIT: raise GurobiError( 10023, "Gurobi timeout reached when attempting to solve GAP") elif m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() # pass it on return None
# vars = tupledict() # for i,j in dist.keys(): # vars[i,j] = m.addVar(obj=dist[i,j], vtype=GRB.BINARY, # name='e[%d,%d]'%(i,j)) # Add degree-2 constraint m.addConstrs(vars.sum(i, '*') == 2 for i in range(n)) # Using Python looping constructs, the preceding would be... # # for i in range(n): # m.addConstr(sum(vars[i,j] for j in range(n)) == 2) # Optimize model m._vars = vars m.Params.lazyConstraints = 1 m.optimize(subtourelim) vals = m.getAttr('x', vars) selected = tuplelist((i, j) for i, j in vals.keys() if vals[i, j] > 0.5) tour = subtour(selected) assert len(tour) == n print('') print('Optimal tour: %s' % str(tour)) print('Optimal cost: %g' % m.objVal) print('')
def make_model(strong_inequalities=False, relax=False, callback=False, hascapacity=1): # Relabel data commodities = data.commodities arcs = data.arcs capacity = data.capacity variable_cost = data.variable_cost fixed_cost = data.fixed_cost nodes = data.nodes demand = data.demand periods = data.periods # Create optimization model env = Env(logfilename="") m = Model('multi-period-netflow', env) # Create variables flow, arc_open = {}, {} for t in periods: for i, j in arcs: arc_open[i, j, t] = m.addVar(vtype=GRB.BINARY, lb=0.0, ub=1.0, obj=fixed_cost[(i, j), t], name='open_{0:d}_{1:d}_{2:d}'.format( i, j, t)) for h in commodities: origin, destination = [ key_val[1] for key_val in demand.keys() if key_val[0] == h ][0] upper = capacity[i, j] if has_capacity else demand[(h, (origin, destination), t)] flow[h, i, j, t] = m.addVar(obj=variable_cost[i, j], name='flow_{0:d}_{1:d}_{2:d}_{3:d}'.format( h, i, j, t)) m.update() # Arc capacity constraints and unique arc setup constraints constrs = [] for (i, j) in arcs: m.addConstr( quicksum(arc_open[i, j, l] for l in range(1, len(data.periods) + 1)) <= 1, 'unique_setup{0:d}_{1:d}'.format(i, j)) for t in periods: if not hascapacity: capacity[i, j] = sum(demand[i] for i in demand.keys() if i[2] == t) m.addConstr( quicksum(flow[h, i, j, t] for h in commodities) <= capacity[i, j] * quicksum(arc_open[i, j, s] for s in xrange(1, t + 1)), 'cap_{0:d}_{1:d}_{2:d}'.format(i, j, t)) if not callback and strong_inequalities: for (commodity, (origin, destination), period) in demand: if period == t: constrs.append( m.addConstr( flow[commodity, i, j, t] <= demand[commodity, (origin, destination), period] * quicksum(arc_open[i, j, l] for l in range(1, t + 1)), name='strong_com{0:d}_{1:d}-{2:d}_per{3:d}'. format(commodity, i, j, t))) # Flow conservation constraints for (commodity, (origin, destination), period) in demand: for j in nodes: if j == origin: node_demand = demand[commodity, (origin, destination), period] elif j == destination: node_demand = -demand[commodity, (origin, destination), period] else: node_demand = 0 h = commodity m.addConstr( -quicksum(flow[h, i, j, period] for i, j in arcs.select('*', j)) + quicksum(flow[h, j, k, period] for j, k in arcs.select(j, '*')) == node_demand, 'node_{0:d}_{1:d}_{2:d}'.format(h, j, period)) m.update() # Compute optimal solution m.setParam("TimeLimit", 7200) # m.params.NodeLimit = 1 # m.params.cuts = 0 # m.setParam("Threads", 2) m.setAttr('Lazy', constrs, [3] * len(constrs)) # m.write("eyes.lp") # try: if strong_inequalities: if not relax: # m.setParam("NodeLimit", 1000000) # m.params.Cuts = 0 if callback: print 'callback in action! :)' m.params.preCrush = 1 m.update() m._vars = m.getVars() m.optimize(strong_inequalities_callback) else: m.optimize(time_callback) else: m = m.relax() m.optimize(time_callback) else: m.optimize(time_callback) if PRINT_VARS: for var in m.getVars(): if str(var.VarName[0]) == 'f' and var.X > 0.0001: name = var.VarName.split('_') print 'arc: \t {} \t commodity: {} \t period: {} \t value: \t {}'.format( (int(name[2]), int(name[3])), int(name[1]), int(name[4]), var.x) # Grab the positive flows and see how many variables open during the first period positive_flows = [ var for var in m.getVars() if var.VarName[0] == 'o' and var.X > 0.5 ] first_period_arcs = sum([ var.X for var in positive_flows if int(var.VarName.split('_')[3]) == 1 ]) print '% of arcs that open in first period: {}%'.format( 100 * first_period_arcs / len(positive_flows)) print '% of arcs that are utilized: {}%'.format( (100. * len(positive_flows)) / len(data.arcs)) objective = m.getObjective().getValue() fixed_cost_percentage = sum([ fixed_cost[(i, j), t] * arc_open[i, j, t].X for i, j in data.arcs for t in data.periods ]) / objective print 'Fixed cost percentage: {}%'.format(fixed_cost_percentage * 100.) for var in m.getVars(): if str(var.VarName[0]) == 'o' and var.X > 0.0001: name = var.VarName.split('_') print 'Arc: \t {} \t Period: {} \t Value: \t {}'.format( (int(name[1]), int(name[2])), int(name[3]), var.X) # m.write('trial2.lp') except: if m.status == GRB.status.INFEASIBLE and DEBUG: print 'Infeasible model. Computing IIS..' m.computeIIS() m.write('trial.ilp')
def _solve_set_covering(N, active_ptls, relaxed_ptls, forbidden_combos, allow_infeasible=True, D=None, K=None): """A helper function that solves a set covering problem. The inputs are a list of node sets and corresponding costs for the set. -- Fisher, M. L. and Jaikumar, R. (1981), A generalized assignment heuristic for vehicle routing. Networks, 11: 109-124. """ m = Model("SCPCVRP") nactive = len(active_ptls.routes) nrelaxed = len(relaxed_ptls.routes) nforbidden = len(forbidden_combos) # the order of the keys is important when we interpret the results X_j_keys = range(nactive + nrelaxed) # variables and the objective X_j_costs = active_ptls.costs + relaxed_ptls.costs X_j_node_sets = active_ptls.nodes + relaxed_ptls.nodes #update forbidden indices to match the current active petal set X_j_forbidden_combos = [] for fc in forbidden_combos: if all(i < nactive for i in fc): X_j_forbidden_combos.append( [i if i >= 0 else -i - 1 + nactive for i in fc]) #print("REMOVEME: Solving with K=%d, %d node sets, and %d solutions forbidden" % (K, nactive+nrelaxed,nforbidden)) if __debug__: log( DEBUG, "Solving over-constrained VRP as a set covering problem " + "with %d petals, where %d of the possible configurations are forbidden." % (nactive + nrelaxed, nforbidden)) if nforbidden > 0: log(DEBUG - 2, " and with following solutions forbidden:") log(DEBUG - 3, "(petal indices = %s)" % str(X_j_forbidden_combos)) for fc in X_j_forbidden_combos: fc_sol = [0] for i in fc: if i < nactive: fc_sol.extend(active_ptls.routes[i][1:]) else: fc_sol.extend(relaxed_ptls.routes[i - nactive][1:]) fc_sol = without_empty_routes(fc_sol) log(DEBUG - 2, "%s (%.2f)" % (fc_sol, objf(fc_sol, D))) X_j = m.addVars(X_j_keys, obj=X_j_costs, vtype=GRB.BINARY, name='x') ## constraints c1_constrs, c2_constrs, c3_constrs = _add_set_covering_constraints( m, N, K, X_j, X_j_keys, X_j_node_sets, X_j_forbidden_combos) ## update the model and solve m._vars = X_j m.modelSense = GRB.MINIMIZE m.update() # disable output m.setParam('OutputFlag', 0) m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME) m.setParam('Threads', MIP_SOLVER_THREADS) #m.write("petalout.lp") m.optimize() # restore SIGINT callback handler which is changed by gurobipy signal(SIGINT, default_int_handler) if __debug__: log(DEBUG - 2, "Gurobi runtime = %.2f" % m.Runtime) if m.Status == GRB.OPTIMAL: log(DEBUG - 3, "Gurobi objective = %.2f" % m.getObjective().getValue()) if m.Status == GRB.OPTIMAL: return _decision_variables_to_petals(m.X, active_ptls, relaxed_ptls), True elif m.Status == GRB.TIME_LIMIT: raise GurobiError( 10023, "Gurobi timeout reached when attempting to solve SCPCVRP") elif m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() # Sometimes the solution is infeasible, try to relax it a little. elif m.Status == GRB.INFEASIBLE and allow_infeasible: return _relax_customer_constraints_with_feasRelax( m, c1_constrs, active_ptls, relaxed_ptls) return None, False
def solve_tsp_gurobi(D, selected_idxs): #print #print "============== GUROBI =============" n = len(selected_idxs) if selected_idxs[0] == selected_idxs[-1]: n = n - 1 # no need to invoke Gurobi for tiny TSP cases if n <= 3: sol = None obj_f = 0.0 if n > 1: sol = list(selected_idxs) if sol[0] != sol[-1]: sol.append(sol[0]) obj_f = sum(D[sol[i - 1], sol[i]] for i in range(1, len(sol))) return sol, obj_f m = Model("TSP") m.params.OutputFlag = 0 # Create variables edgevars = {} for i in range(n): for j in range(i + 1): from_node = selected_idxs[i] to_node = selected_idxs[j] edgevars[i, j] = m.addVar(obj=D[from_node, to_node], vtype=GRB.BINARY, name='e' + str(i) + '_' + str(j)) edgevars[j, i] = edgevars[i, j] m.update() # Add degree-2 constraint, and forbid loops for i in range(n): m.addConstr(quicksum(edgevars[i, j] for j in range(n)) == 2) edgevars[i, i].ub = 0 m.update() # Optimize model m._vars = edgevars m.params.LazyConstraints = 1 m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME) m.setParam('Threads', MIP_SOLVER_THREADS) m.optimize(_subtourelim) # restore SIGINT callback handler which is changed by gurobipy signal(SIGINT, default_int_handler) status = m.Status if status == GRB.TIME_LIMIT: raise GurobiError( 10023, "Gurobi timeout reached when attempting to solve TSP") elif m.Status == GRB.INTERRUPTED: raise KeyboardInterrupt() solution = m.getAttr('x', edgevars) selected = [(i, j) for i in range(n) for j in range(n) if solution[i, j] > 0.5] cycles = _subtour(selected, n) assert len(cycles) == n # make the route always start from the 1st index sol = [selected_idxs[i] for i in cycles] + [selected_idxs[0]] obj_f = m.objVal return sol, obj_f
def __init__(self, n_vertices, edges, constraints, k, minsize, verbosity=0, symmetry_breaking=True, overlap=False, single_cut=False, timeout=None): self.check_graph(n_vertices, edges) self.n_vertices = n_vertices self.k = k self.verbosity = verbosity self.timeout = timeout model = Model('graph_clustering') mvars = [] for i in range(k): cvars = [] for j in range(n_vertices): v = model.addVar(lb=0.0, ub=1.0, vtype=GRB.BINARY) cvars.append(v) mvars.append(cvars) model.update() ineq_sense = GRB.GREATER_EQUAL if overlap else GRB.EQUAL # constraint: each vertex in exactly/at least one cluster for v in range(n_vertices): model.addConstr(quicksum([mvars[i][v] for i in range(k)]), ineq_sense, 1) # symmetry-breaking constraints if symmetry_breaking: model.addConstr(mvars[0][0], GRB.EQUAL, 1) for i in range(2, k): model.addConstr( quicksum([mvars[i - 1][j] for j in range(n_vertices)]) <= quicksum([mvars[i][j] for j in range(n_vertices)])) # size constraint for i in range(k): model.addConstr( quicksum([mvars[i][v] for v in range(n_vertices)]) >= minsize) obj_expr = LinExpr() # indicators for violation of cl constraints for (u, v, w) in constraints: for i in range(k): y = model.addVar(lb=0.0, ub=1.0, vtype=GRB.BINARY) model.update() model.addConstr(y >= mvars[i][u] + mvars[i][v] - 1) obj_expr.add(y, w) model.setObjective(obj_expr, GRB.MINIMIZE) model.params.OutputFlag = self.verbosity model.Params.PreCrush = 1 model.Params.LazyConstraints = 1 model._cutfinder = Cut_Finder(n_vertices, edges) model._vars = mvars model._k = k model._relobj = None model._impcounter = 0 model._single_cut = single_cut # runtime information model._root_cuttime = 0 model._tree_cuttime = 0 self.model = model