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
def solve(self, max_runtime = 120): n, V = len(self.adj_mat), set(range(len(self.adj_mat))) H = set(range(len(self.house_names))) model = Model() model.threads = -1 model.max_mip_gap = 0.05 """ Define Parameters d: {0, 1} whether or not an edge is selected y: for connectivity constraint s: whether or not a node is visited """ d = {} for i in V: for j in V: #if self.adj_mat[i][j] > 0: if self.full_mat[i][j] > 0: d[(i, j)] = model.add_var(var_type=BINARY) z = [model.add_var() for i in V] y = [model.add_var(var_type=BINARY) for i in V] # i is node, j is house/pedestrian c = [] s = [] for i in V: c.append(self.closest_nodes(10, self.node_names[i])) temp_row = [] for j in H: temp_row.append(model.add_var(var_type=BINARY)) s.append(temp_row) """ Define optimization function """ def cost_func(): total = 0 for i in V: for j in V: if self.full_mat[i][j] > 0: total += self.full_mat[i][j] * d[(i,j)] * 2.0 / 3.0 for i in V: for j in H: total += c[i][j] * s[i][j] return total model.objective = minimize(cost_func()) """ Add constraints """ model += y[self.node_names.index(self.start)] == 1 # Will need if skimp on distance calulation with closest_nodes #for i in H: # model += xsum(s[j][i] * c[j][i] for j in V) >= 0.2 for i in H: model += xsum(s[j][i] for j in V) == 1 for i in V: for j in H: model += s[i][j] <= y[i] for i in V: model += xsum(d[(i,j)] for j in V -{i} if self.full_mat[i][j] > 0) == 1 * y[i] for i in V: model+= xsum(d[(j,i)] for j in V - {i} if self.full_mat[i][j] > 0) == 1 * y[i] for (i, j) in product(V - {0}, V - {0}): if i != j and self.full_mat[i][j] > 0: model += z[i] - (n+1)*d[(i,j)] >= z[j] - n status = model.optimize(max_seconds = max_runtime) n = 0 for i in V: for j in V: if i != j and d[(i, j)].x > 0.99: n += 1 print('oofus doofus', n) if model.num_solutions: nc = self.node_names.index(self.start) solution = [] # visited is to make sure we don't terminate the path too quickly visited = {} for _ in range(500): solution.append(self.node_names[nc]) visited[nc] = len(visited) all_visited = True #nc_ls = [] print('abcdefgh') for i in V: if (nc, i) in d and d[(nc, i)].x > 0.99: nc = i if nc not in visited: print('BROKE') all_visited = False break #nc_ls.append(i) #break if all_visited: first_visit = len(visited) for k in visited: if visited[k] < first_visit: first_visit = visited[k] nc = k if nc == self.node_names.index(self.start): solution.append(self.start) break solution = self.make_valid_path(solution) print(solution) return solution, status, model.gap else: return None, status, model.gap
print("Optimization process started.") # get time limit in seconds for optimization timeInSeconds = optimization_parameters.loc["timeInSeconds", "Value"] # get mip gap mipGap = optimization_parameters.loc["mipGap", "Value"] # if time limit was given use this time limit else default value: +inf if not pd.isna(timeInSeconds): model.max_seconds = timeInSeconds print("Maximal solution time: " + str(timeInSeconds) + " seconds") # if mip gap was given use this gap else default value: 1e-4 if not pd.isna(mipGap): model.max_mip_gap = mipGap print("MIP gap set to " + str(mipGap * 100) + "%") # start optimizing status = model.optimize() solved = False # optimal solution found if status == OptimizationStatus.OPTIMAL: if model.gap < 1e-4: print("Optimal solution found.") else: print("Found a solution with gap <= set mip gap.") gap = round(model.gap * 100, 3)