Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
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
Beispiel #5
0
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
Beispiel #6
0
    # 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]: