示例#1
0
def solve(
    instance: CVRPInstance,
    params: Optional[ORToolsParams] = None,
) -> Optional[CVRPSolution]:
    """Solves a CVRP instance using ORTools"""

    # Initialize parameters if not provided.
    params = params or ORToolsParams.get_baseline()

    # Number of points is the number of deliveries + the origin.
    num_points = len(instance.deliveries) + 1

    logger.info(f"Solving CVRP instance of size {num_points}.")

    # There's no limit of vehicles, or max(vehicles) = len(deliveries).
    num_vehicles = params.max_vehicles or len(instance.deliveries)

    manager = pywrapcp.RoutingIndexManager(
        num_points,
        num_vehicles,
        0,  # (Number of nodes, Number of vehicles, Origin index).
    )
    model = pywrapcp.RoutingModel(manager)

    # Unwrap the size index for every point.
    sizes = np.array([0] + [d.size for d in instance.deliveries],
                     dtype=np.int32)

    def capacity_callback(src):
        src = manager.IndexToNode(src)
        return sizes[src]

    capacity_callback_index = model.RegisterUnaryTransitCallback(
        capacity_callback)
    model.AddDimension(capacity_callback_index, 0, instance.vehicle_capacity,
                       True, "Capacity")

    # Unwrap the location/point for every point.
    locations = [instance.origin] + [d.point for d in instance.deliveries]

    # Compute the distance matrix between points.
    logger.info("Computing distance matrix.")
    distance_matrix = (calculate_distance_matrix_m(locations) * 10).astype(
        np.int32)

    def distance_callback(src, dst):
        x = manager.IndexToNode(src)
        y = manager.IndexToNode(dst)
        return distance_matrix[x, y]

    distance_callback_index = model.RegisterTransitCallback(distance_callback)
    model.SetArcCostEvaluatorOfAllVehicles(distance_callback_index)

    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = params.first_solution_strategy

    search_parameters.local_search_metaheuristic = (
        params.local_search_metaheuristic)

    if params.solution_limit:
        search_parameters.solution_limit = params.solution_limit

    search_parameters.time_limit.FromTimedelta(
        timedelta(microseconds=1e3 * params.time_limit_ms))

    logger.info("Solving CVRP with ORTools.")
    assignment = model.SolveWithParameters(search_parameters)

    # Checking if the feasible solution was found.
    # For more information about the type error:
    # https://developers.google.com/optimization/routing/routing_options
    if not assignment:
        return None

    def extract_solution(vehicle_id):
        # Get the start node for route.
        index = model.Start(vehicle_id)

        # Iterate while we don't reach an end node.
        while not model.IsEnd(assignment.Value(model.NextVar(index))):
            next_index = assignment.Value(model.NextVar(index))
            node = manager.IndexToNode(next_index)

            yield instance.deliveries[node - 1]
            index = next_index

    routes = [
        CVRPSolutionVehicle(
            origin=instance.origin,
            deliveries=list(extract_solution(i)),
        ) for i in range(num_vehicles)
    ]

    # Return only routes that actually leave the depot.
    return CVRPSolution(
        name=instance.name,
        vehicles=[v for v in routes if len(v.deliveries)],
    )
示例#2
0
def to_tsplib(instance: CVRPInstance,
              file_name: Optional[str] = None) -> Optional[str]:
    """Convert instance into TSPLIB format

    Parameters
    ----------
    instance
        CVRP instance

    file_name
        File with extension ".vrp" to save the instance. If not provided,
        return a string with the same data

    Returns
    -------
    A string with instance data in the TSPLIB format if no ``file_name`` was
    especified, or nothing otherwise
    """

    # Header information
    tspfile = (
        f"NAME : {instance.name}\n"
        "TYPE : ACVRP\n"
        f"DIMENSION : {len(instance.deliveries) + 1}\n"  # + 1 for the origin
        f"CAPACITY : {instance.vehicle_capacity}\n"
        "NODE_COORD_TYPE : TWOD_COORDS\n"
        "EDGE_WEIGHT_TYPE : EXPLICIT\n"
        "EDGE_WEIGHT_FORMAT : FULL_MATRIX\n")

    # Nodes section
    tspfile += (
        "NODE_COORD_SECTION\n"
        f"1 {instance.origin.lng} {instance.origin.lat}\n"  # origin
    )
    tspfile += "\n".join(
        f"{i} {delivery.point.lng} {delivery.point.lat}"
        for i, delivery in enumerate(instance.deliveries, start=2))
    tspfile += "\n"

    # Demand section
    tspfile += ("DEMAND_SECTION\n" "1 0\n")
    tspfile += "\n".join(
        f"{i} {delivery.size}"
        for i, delivery in enumerate(instance.deliveries, start=2))
    tspfile += "\n"

    # Depot section: ensure node 1 is the depot (-1 to terminate the list)
    tspfile += ("DEPOT_SECTION\n" "1\n" "-1\n")

    # Edge section:
    # Compute distance matrix
    locations = [instance.origin
                 ] + [delivery.point for delivery in instance.deliveries]
    distance_matrix = (calculate_distance_matrix_m(locations) * 10).astype(
        np.int)

    tspfile += "EDGE_WEIGHT_SECTION\n"

    def print_row(row):
        return " ".join(str(el) for el in row)

    tspfile += "\n".join(print_row(row) for row in distance_matrix)

    if not file_name:
        return tspfile

    with open(file_name, "w") as f:
        f.write(tspfile)