def _add_warm_start_assignment( m: Model, v_list: List[Vertex], slot_to_idx: Dict[Slot, Tuple[int, int, int]], warm_start_assignment: Dict[Vertex, Slot], v2var_x: Dict[Vertex, Var], v2var_y1: Dict[Vertex, Var], v2var_y2: Dict[Vertex, Var], ) -> None: """ provide an existing solution as a warm start to the ILP solver WARNING: not useful at the moment """ assert all(v in warm_start_assignment for v in v_list), 'ERROR: incomplete initial solution' warm_start: List[Tuple[Var, int]] = [] for v, init_sol_slot in warm_start_assignment.items(): y1, y2, x = slot_to_idx[init_sol_slot] warm_start += [(v2var_y1[v], y1), (v2var_y2[v], y2), (v2var_x[v], x)] m.start = warm_start
def test_tsp_mipstart(solver: str): """tsp related tests""" N = ["a", "b", "c", "d", "e", "f", "g"] n = len(N) i0 = N[0] A = { ("a", "d"): 56, ("d", "a"): 67, ("a", "b"): 49, ("b", "a"): 50, ("d", "b"): 39, ("b", "d"): 37, ("c", "f"): 35, ("f", "c"): 35, ("g", "b"): 35, ("b", "g"): 25, ("a", "c"): 80, ("c", "a"): 99, ("e", "f"): 20, ("f", "e"): 20, ("g", "e"): 38, ("e", "g"): 49, ("g", "f"): 37, ("f", "g"): 32, ("b", "e"): 21, ("e", "b"): 30, ("a", "g"): 47, ("g", "a"): 68, ("d", "c"): 37, ("c", "d"): 52, ("d", "e"): 15, ("e", "d"): 20, } # input and output arcs per node Aout = {n: [a for a in A if a[0] == n] for n in N} Ain = {n: [a for a in A if a[1] == n] for n in N} m = Model(solver_name=solver) m.verbose = 0 x = { a: m.add_var(name="x({},{})".format(a[0], a[1]), var_type=BINARY) for a in A } m.objective = xsum(c * x[a] for a, c in A.items()) for i in N: m += xsum(x[a] for a in Aout[i]) == 1, "out({})".format(i) m += xsum(x[a] for a in Ain[i]) == 1, "in({})".format(i) # continuous variable to prevent subtours: each # city will have a different "identifier" in the planned route y = {i: m.add_var(name="y({})".format(i), lb=0.0) for i in N} # subtour elimination for (i, j) in A: if i0 not in [i, j]: m.add_constr(y[i] - (n + 1) * x[(i, j)] >= y[j] - n) route = ["a", "g", "f", "c", "d", "e", "b", "a"] m.start = [(x[route[i - 1], route[i]], 1.0) for i in range(1, len(route))] m.optimize() assert m.status == OptimizationStatus.OPTIMAL assert abs(m.objective_value - 262) <= TOL
def branch_and_cut(distance_matrix: Matrix, max_seconds=20) -> Tuple[Matrix, int]: """Solves a distance matrix for the shortest path :param distance_matrix: Distance maxtrix such as returned from `compute_euclidean_distance_matrix` :type distance_matrix: Matrix :param max_seconds: The max execution time in seconds :type max_seconds: int :return: A matrix indicating which edges contain the optimal route, the distance of the route :rtype: Tuple[Matrix, int] """ n = len(distance_matrix) model = Model() # binary variables indicating if arc (i,j) is used on the route or not x = [[ model.add_var(name=f'x({i},{j})', var_type=BINARY) for j in range(n) ] for i in range(n)] # continuous variable to prevent subtours: each city will have a # different sequential id in the planned route except the first one y = [model.add_var(name=f'y({i})') for i in range(n)] # objective function: minimize the distance model.objective = minimize( xsum(distance_matrix[i][j] * x[i][j] for i in range(n) for j in range(n))) # constraint : enter each city coming from another city for i in range(n): model += xsum(x[j][i] for j in range(n) if j != i) == 1 # constraint : leave each city coming from another city for i in range(n): model += xsum(x[i][j] for j in range(n) if j != i) == 1 # subtour elimination for i in range(1, n): for j in [x for x in range(1, n) if x != i]: model += y[i] - (n + 1) * x[i][j] >= y[j] - n, 'noSub({},{})'.format( i, j) # Use nearest neighbors to find an initial feasible solution feasible_rm, feasible_distance = nearest_neighbor_path(distance_matrix, closed=True) model.start = [(x[i][j], float(feasible_rm[i][j])) for i in range(n) for j in range(n)] # optimizing model.optimize(max_seconds=max_seconds) if model.status in [ OptimizationStatus.OPTIMAL, OptimizationStatus.FEASIBLE ]: logging.info(f'Best route found has length {model.objective_value}.') route_matrix = [[int(x[i][j].x) for j in range(n)] for i in range(n)] return route_matrix, model.objective_value else: logging.info('No solution found.') route_matrix = [[0 for j in range(n)] for i in range(n)] return route_matrix, 0
def solve(self, list_of_locations, list_of_homes, starting_car_location, adjacency_matrix, input_file, params=[]): """ Solve the problem using an MST/DFS approach. Input: list_of_locations: A list of locations such that node i of the graph corresponds to name at index i of the list list_of_homes: A list of homes starting_car_location: The name of the starting location for the car adjacency_matrix: The adjacency matrix from the input file Output: A cost of how expensive the current solution is A list of locations representing the car path A dictionary mapping drop-off location to a list of homes of TAs that got off at that particular location NOTE: all outputs should be in terms of indices not the names of the locations themselves """ conn = sqlite3.connect('models.sqlite') c = conn.cursor() seen = c.execute( 'SELECT best_objective_bound FROM models WHERE input_file = (?)', (input_file, )).fetchone() self.log_new_entry(input_file) home_indices = convert_locations_to_indices(list_of_homes, list_of_locations) location_indices = convert_locations_to_indices( list_of_locations, list_of_locations) edge_scale = 1.0 if "--approx" in params: edge_scale = 1 / 10000 G, message = adjacency_matrix_to_graph(adjacency_matrix, edge_scale) E = list(G.to_directed().edges(data='weight')) starting_car_index = list_of_locations.index(starting_car_location) start_paths = [ convert_locations_to_indices([starting_car_location], list_of_locations) ] num_random_paths = 5 if "-r" in params: num_random_paths = int(params[params.index("-r") + 1]) for i in range(num_random_paths): start_paths.append(self.generate_random(G, starting_car_index)) if seen: output_file = 'submissions/submission_final/{}.out'.format( input_file.split('.')[0]) print(output_file) if not "--no-prev" in params and os.path.isfile(output_file): start_paths.append( convert_locations_to_indices( utils.read_file(output_file)[0], list_of_locations)) best_start_path_cost = float('inf') best_start_path_index = -1 for i, path in enumerate(start_paths): walk_cost, dropoffs = self.find_best_dropoffs( G, home_indices, path) cost, msg = cost_of_solution(G, path, dropoffs) if cost < best_start_path_cost: best_start_path_cost = cost best_start_path_index = i start_path = start_paths[best_start_path_index] print("Starting path:") if best_start_path_index == num_random_paths: print("SAVED PATH:", start_path) elif best_start_path_index >= 0: print("RANDOM PATH:", start_path) else: print("No start path found") print("Starting cost:", best_start_path_cost) # number of nodes and list of vertices, not including source or sink n, V, H = len(list_of_locations), location_indices, home_indices bigNum = (2 * n) model = Model() # does the car drive from i to j? x = [model.add_var(var_type=BINARY) for e in E] # does the kth TA walk from i to j? over all num_homes TAs t = [[model.add_var(var_type=BINARY) for e in E] for k in H] # car flow from vertex u to vertex v f = [model.add_var(var_type=INTEGER) for e in E] \ + [model.add_var(var_type=INTEGER) for v in V] \ + [model.add_var(var_type=INTEGER)] # kth TA flow from vertex u to vertex v f_t = [[model.add_var(var_type=BINARY) for e in E] + [model.add_var(var_type=BINARY) for v in V] for k in H] for i in range(len(f)): model += f[i] >= 0 # For each vertex v where v != source and v != sink, Sum{x_(u, v)} = Sum{x_(v, w)} for v in V: model += xsum([x[i] for i in range(len(E)) if E[i][1] == v]) == xsum( [x[i] for i in range(len(E)) if E[i][0] == v]) # For each vertex v where v != sink, Sum{f_(u, v)} = Sum{f_(v, w)} for j in range(len(V)): model += xsum([f[i] for i in range(len(E)) if E[i][1] == V[j]]) + (f[-1] if V[j] == starting_car_index else 0) \ == xsum([f[i] for i in range(len(E)) if E[i][0] == V[j]]) + f[len(E) + j] # For each edge (u, v) where u != source and v != sink, f_(u, v) <= (big number) * x_(u, v) for i in range(len(E)): model += f[i] <= bigNum * x[i] # For edge (source, start vertex), f_(source, start vertex) <= (big number) model += f[-1] <= bigNum # For each edge (u, sink), f_(u, sink) <= Sum{x_(w, u)} for j in range(len(V)): model += f[j + len(E)] \ <= xsum([x[i] for i in range(len(E)) if E[i][1] == V[j]]) # For just the source vertex, f_(source,start vertex)} = Sum{x_(a, b)} model += f[-1] == xsum(x) # For every TA for every edge, can't flow unless edge is walked along for i in range(len(t)): for j in range(len(E)): model += f_t[i][j] <= t[i][j] # For every TA for every non-home vertex, flow in equals flow out for i in range(len(H)): for j in range(len(V)): if V[j] != H[i]: model += xsum(f_t[i][k] for k in range(len(E)) if E[k][1] == V[j]) + f_t[i][len(E) + j] \ == xsum(f_t[i][k] for k in range(len(E)) if E[k][0] == V[j]) # For every TA, flow out of the source vertex is exactly 1 for k in f_t: model += xsum(k[len(E) + i] for i in range(len(V))) == 1 # For every TA for every edge out of source, can't flow unless car visits vertex for k in f_t: for i in range(len(V)): model += k[len(E) + i] <= xsum( x[j] for j in range(len(E)) if E[j][1] == V[i]) # For every TA, flow into the home vertex is exactly 1 for i in range(len(H)): model += xsum(f_t[i][j] for j in range(len(E)) if E[j][1] == H[i]) + f_t[i][len(E) + H[i]] == 1 # objective function: minimize the distance model.objective = minimize(2.0/3.0 * xsum([x[i] * E[i][2] for i in range(len(E))]) \ + xsum([xsum([t[i][j] * E[j][2] for j in range(len(E))]) for i in range(len(t))])) # WINNING ONLINE model.max_gap = 0.00001 model.emphasis = 2 model.symmetry = 2 if "--no-model-start" not in params: model.start = self.construct_starter(x, t, G, home_indices, start_path) timeout = 300 if "-t" in params: timeout = int(params[params.index("-t") + 1]) if timeout != -1: status = model.optimize(max_seconds=timeout) else: status = model.optimize() objective_value = model.objective_value / edge_scale objective_bound = model.objective_bound / edge_scale if status == OptimizationStatus.OPTIMAL: print('optimal solution cost {} found'.format(objective_value)) self.log_update_entry(Fore.GREEN + "Optimal cost={}.".format(objective_value) + Style.RESET_ALL) else: print("!!!! TIMEOUT !!!!") self.log_update_entry(Fore.RED + "Timeout!" + Style.RESET_ALL) if status == OptimizationStatus.FEASIBLE: print('sol.cost {} found, best possible: {}'.format( objective_value, objective_bound)) self.log_update_entry("Feasible cost={}, bound={}.".format( objective_value, objective_bound)) elif status == OptimizationStatus.NO_SOLUTION_FOUND: print('no feasible solution found, lower bound is: {}'.format( objective_bound)) self.log_update_entry( "Failed, bound={}.".format(objective_bound)) # if no solution found, return inf cost if model.num_solutions == 0: conn.close() return float('inf'), [], {} # printing the solution if found out.write('Route with total cost %g found. \n' % (objective_value)) if "-v" in params: out.write('\nEdges (In, Out, Weight):\n') for i in E: out.write(str(i) + '\t') out.write('\n\nCar - Chosen Edges:\n') for i in x: out.write(str(i.x) + '\t') out.write('\n\nCar - Flow Capacities:\n') for i in f: out.write(str(i.x) + '\t') out.write('\n\nTAs - Home Indices:\n') for i in H: out.write(str(i) + '\n') out.write('\nTAs - Chosen Edges:\n') for i in t: for j in range(len(i)): out.write(str(i[j].x) + '\t') out.write('\n') out.write('\nTAs - Flow Capacities:\n') for i in f_t: for j in range(len(i)): out.write(str(i[j].x) + '\t') out.write('\n') out.write('\nActive Edges:\n') for i in range(len(x)): if (x[i].x >= 1.0): out.write('Edge from %i to %i with weight %f \n' % (E[i][0], E[i][1], E[i][2])) out.write('\n') list_of_edges = [E[i] for i in range(len(x)) if x[i].x >= 1.0] car_path_indices = self.construct_path(starting_car_index, list_of_edges, input_file) walk_cost, dropoffs_dict = self.find_best_dropoffs( G, home_indices, car_path_indices) updated = False if not seen: print("SAVING", input_file) c.execute('INSERT INTO models (input_file, best_objective_bound, optimal) VALUES (?, ?, ?)', \ (input_file, objective_value, status == OptimizationStatus.OPTIMAL)) conn.commit() elif objective_value <= seen[0]: updated = True print("UPDATING", input_file) c.execute('UPDATE models SET best_objective_bound = ?, optimal = ? WHERE input_file = ?', \ (objective_value, status == OptimizationStatus.OPTIMAL, input_file)) conn.commit() if not "-s" in params: print("Walk cost =", walk_cost, "\n") if updated: self.log_update_entry(Fore.GREEN + "Updated" + Style.RESET_ALL) else: self.log_update_entry(Fore.RED + "Not Updated" + Style.RESET_ALL) conn.close() return objective_value, car_path_indices, dropoffs_dict
def solve(G: nx.Graph, max_c, max_k, timeout, existing_solution, target_distance): """ Args: G: networkx.Graph Returns: c: list of cities to remove k: list of edges to remove """ model = Model() model.threads = int(os.getenv("THREAD_COUNT", "24")) model.max_mip_gap = 1e-12 skipped_nodes = [model.add_var(var_type=BINARY) for node in G] flow_over_edge = [[model.add_var(var_type=CONTINUOUS) for j in G] for i in G] # no flow over nonexistent edges for i in G: for j in G: if not G.has_edge(i, j): model += flow_over_edge[i][j] == 0 model += xsum(flow_over_edge[0][other_node] for other_node in G) == (len(G) - 1) - xsum(skipped_nodes) for other_node in G: model += flow_over_edge[other_node][0] == 0 distance_to_node = [model.add_var(var_type=CONTINUOUS) for node in G] model += distance_to_node[0] == 0 # in any connected subset of G, there will always be a shortest path w/ length <= sum of all edges # so a fake_infinity will never be better than any other option fake_infinity = sum([weight for _, _, weight in G.edges().data("weight")]) skipped_edges = [] skipped_edge_map = {} model += skipped_nodes[0] == 0 model += skipped_nodes[len(G) - 1] == 0 for node_a, node_b, weight in G.edges().data("weight"): edge_skip_var = model.add_var(var_type=BINARY) skipped_edges.append((edge_skip_var, (node_a, node_b))) model += edge_skip_var >= skipped_nodes[node_a] model += edge_skip_var >= skipped_nodes[node_b] model += distance_to_node[node_a] <= distance_to_node[node_b] + weight + edge_skip_var * fake_infinity model += distance_to_node[node_b] <= distance_to_node[node_a] + weight + edge_skip_var * fake_infinity # no flow if edge or node removed model += flow_over_edge[node_a][node_b] <= (len(G) - 1) - edge_skip_var * (len(G) - 1) model += flow_over_edge[node_b][node_a] <= (len(G) - 1) - edge_skip_var * (len(G) - 1) # results in binary variable leakage for node in G: # immediately force the distance to infinity if we skip the node model += distance_to_node[node] >= skipped_nodes[node] * fake_infinity model += distance_to_node[node] <= fake_infinity for node in G: if node != 0: flow_into_node = xsum(flow_over_edge[other_node][node] for other_node in G) flow_out_of_node = xsum(flow_over_edge[node][other_node] for other_node in G) model += flow_into_node - flow_out_of_node == 1 - skipped_nodes[node] model += xsum([var for var, _ in skipped_edges]) - xsum([skipped_nodes[i] * len(G[i]) for i in G]) <= max_k model += xsum(skipped_nodes) <= max_c if target_distance: model += distance_to_node[len(G) - 1] >= target_distance model += distance_to_node[len(G) - 1] <= target_distance model.objective = maximize(distance_to_node[len(G) - 1]) # these cuts are used more often but aggressively using them doesn't seem to help # model.solver.set_int_param("FlowCoverCuts", 2) # model.solver.set_int_param("MIRCuts", 2) if existing_solution: solution_variables = [] for node in G: solution_variables.append((skipped_nodes[node], 1.0 if node in existing_solution[0] else 0.0)) for edge_var, edge in skipped_edges: is_skipped = (edge in existing_solution[1]) or ((edge[1], edge[0]) in existing_solution[1]) or \ (edge[0] in existing_solution[0]) or (edge[1] in existing_solution[0]) solution_variables.append((edge_var, 1.0 if is_skipped else 0.0)) model.start = solution_variables status = model.optimize(max_seconds=timeout) if model.num_solutions > 0: c = [] for node in G: if skipped_nodes[node].x > 0.99: c.append(node) k = [] for skip_var, edge in skipped_edges: if skip_var.x > 0.99 and not ((edge[0] in c) or (edge[1] in c)): k.append(edge) return c, k, status == OptimizationStatus.OPTIMAL, model.gap else: return None
# applying the Late Acceptance Hill Climbing rnd.seed(0) L = [cost for i in range(50)] sl, cur_cost, best = seq.copy(), cost, cost for it in range(5000000): (i, j) = rnd.randint(1, len(sl) - 2), rnd.randint(1, len(sl) - 2) dlt = delta(c, sl, i, j) if cur_cost + dlt <= L[it % len(L)]: sl[i], sl[j], cur_cost = sl[j], sl[i], cur_cost + dlt if cur_cost < best: seq, best = sl.copy(), cur_cost L[it % len(L)] = cur_cost print('improved cost %g' % best) model.start = [(x[seq[i]][seq[i + 1]], 1) for i in range(len(seq) - 1)] model.max_seconds = timeLimit model.threads = threads model.optimize() end = time() print(model.status) print(model.solver_name) objv = 1e20 gap = 1e20 n_nodes = 0 if model.status in [OptimizationStatus.OPTIMAL, OptimizationStatus.FEASIBLE]: