示例#1
0
def finish(instance: CVRPInstance, model: QRPModel) -> CVRPSolution:

    subinstances = [
        CVRPInstance(
            name="",
            region="",
            deliveries=vehicle.deliveries,
            origin=vehicle.origin,
            vehicle_capacity=3 * instance.vehicle_capacity,  # More relaxed.
        )
        for idx, subinstance in enumerate(model.cluster_subsolutions.values())
        for vehicle in subinstance
    ]

    logger.info("Reordering routes.")
    subsolutions = [
        ortools_solve(subinstance, model.params.ortools_tsp_params)
        for subinstance in subinstances
    ]

    return CVRPSolution(
        name=instance.name,
        vehicles=[
            v for subsolution in subsolutions for v in subsolution.vehicles
        ],
    )
def solve(
    instance: CVRPInstance,
    params: Optional[KmeansPartitionORToolsParams] = None,
) -> Optional[CVRPSolution]:

    params = params or KmeansPartitionORToolsParams.get_baseline()

    num_deliveries = len(instance.deliveries)
    num_clusters = int(
        params.fixed_num_clusters
        or np.ceil(
            num_deliveries / (params.variable_num_clusters or num_deliveries)
        )
    )

    logger.info(f"Clustering instance into {num_clusters} subinstances")
    clustering = KMeans(num_clusters, random_state=params.seed)

    points = np.array(
        [[d.point.lng, d.point.lat] for d in instance.deliveries]
    )
    clusters = clustering.fit_predict(points)

    delivery_array = np.array(instance.deliveries)

    subsinstance_deliveries = [
        delivery_array[clusters == i] for i in range(num_clusters)
    ]

    subinstances = [
        CVRPInstance(
            name=instance.name,
            deliveries=subinstance.tolist(),
            origin=instance.origin,
            vehicle_capacity=instance.vehicle_capacity,
        )
        for subinstance in subsinstance_deliveries
    ]

    subsolutions = [
        ortools_solve(subinstance, params.ortools_params)
        for subinstance in subinstances
    ]

    return CVRPSolution(
        name=instance.name,
        vehicles=[v for sol in subsolutions for v in sol.vehicles],
    )
示例#3
0
def solve(
    instance: CVRPInstance,
    params: Optional[KmeansAggregateORToolsParams] = None,
) -> Optional[CVRPSolution]:

    params = params or KmeansAggregateORToolsParams.get_baseline()

    num_deliveries = len(instance.deliveries)
    num_clusters = int(params.fixed_num_clusters
                       or np.ceil(num_deliveries /
                                  (params.variable_num_clusters or 1)))

    logger.info(f"Clustering instance into {num_clusters} subinstances")
    clustering = MiniBatchKMeans(num_clusters, random_state=params.seed)

    points = np.array([[d.point.lng, d.point.lat]
                       for d in instance.deliveries])
    clusters = clustering.fit_predict(points)

    delivery_array = np.array(instance.deliveries)

    deliveries_per_cluster = [
        delivery_array[clusters == i] for i in range(num_clusters)
    ]

    def solve_cluster(deliveries):
        if len(deliveries) < 2:
            return [deliveries]

        cluster_instance = CVRPInstance(
            name=instance.name,
            deliveries=deliveries,
            origin=instance.origin,
            vehicle_capacity=instance.vehicle_capacity,
        )

        cluster_solution = ortools_solve(cluster_instance,
                                         params.cluster_ortools_params)

        return [v.deliveries for v in cluster_solution.vehicles]

    def aggregate_deliveries(idx, deliveries):
        return Delivery(
            id=str(idx),
            point=deliveries[0].point,
            size=sum([d.size for d in deliveries]),
        )

    subsolutions = [
        deliveries for group in deliveries_per_cluster
        for deliveries in solve_cluster(group.tolist()) if group.any()
    ]

    aggregated_deliveries = [
        aggregate_deliveries(idx, s) for idx, s in enumerate(subsolutions)
    ]

    aggregated_instance = CVRPInstance(
        name=instance.name,
        deliveries=aggregated_deliveries,
        origin=instance.origin,
        vehicle_capacity=instance.vehicle_capacity,
    )

    aggregated_solution = ortools_solve(aggregated_instance)

    vehicles = [
        CVRPSolutionVehicle(
            origin=v.origin,
            deliveries=[
                d for v in solve_cluster([
                    d for groups in v.deliveries
                    for d in subsolutions[int(groups.id)]
                ]) for d in v
            ],
        ) for v in aggregated_solution.vehicles
    ]

    return CVRPSolution(
        name=instance.name,
        vehicles=vehicles,
    )
示例#4
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)],
    )
示例#5
0
def _unwrap_lkh_solution(
    instance: CVRPInstance, lkh_solution: List[int]
) -> CVRPSolution:
    """Read the files generated by the solver

    The output is stored in a TSPLIB-like format. Here is a typical example.

    Suppose a problem with depot at node 1 and deliveries at 2, 3, 4, 5 and 6.
    Now, suppose the solution has two routes such as:
        - Route 1: [1, 2, 3]
        - Route 2: [1, 4, 5, 6]

    The output would be written as a sequence like:
        1
        2
        3
        7 <---
        4
        5
        6


    The first node is 1, the depot, and the following are deliveries in the
    first route. Then, we reach a node 7, which is greater than all nodes in
    the problem. This actually marks the start of another route, and if we had
    more routes, it would be split with an 8, and so on.

    The reading goes on until a -1 is obtained, thus marking the end of all
    routes.
    """

    num_deliveries = len(instance.deliveries)

    # To retrieve the delivery indices, we have to subtract two, that is the
    # same as ignoring the depot and reindexing from zero.
    delivery_indices = np.array(lkh_solution[0]) - 2

    # Now we split the sequence into vehicles using a simple generator.
    def route_gen(seq):
        route = []

        for el in seq[1:]:
            if el < num_deliveries:
                route.append(el)

            elif route:
                yield np.array(route)
                route = []

        # Output last route if any
        if route:
            yield np.array(route)

    delivery_indices = list(route_gen(delivery_indices))

    # To enable multi-integer indexing, we convert the deliveries into an
    # object np.array.
    np_deliveries = np.array(instance.deliveries, dtype=object)

    def build_vehicle(route_delivery_indices):
        deliveries = np_deliveries[route_delivery_indices]

        return CVRPSolutionVehicle(
            origin=instance.origin, deliveries=deliveries.tolist()
        )

    routes = [build_vehicle(indices) for indices in delivery_indices]

    return CVRPSolution(name=instance.name, vehicles=routes)
示例#6
0
def read_solution(instance: CVRPInstance, params: LKHParams) -> CVRPSolution:
    """Read the files generated by the solver

    Notes
    -----
    The output is stored in a TSPLIB-like format. Here is a typical example.

    Suppose a problem with depot at node 1 and deliveries at 2, 3, 4, 5 and 6.
    Now, suppose the solution has two routes such as:
        - Route 1: [1, 2, 3]
        - Route 2: [1, 4, 5, 6]

    The output would be written in a file with the following:

        TOUR_SECTION
        1
        2
        3
        7 <---
        4
        5
        6
        -1

    The first node is 1, the depot, and the following are deliveries in the
    first route. Then, we reach a node 7, which is greater than all nodes in
    the problem. This actually marks the start of another route, and if we had
    more routes, it would be split with an 8, and so on.

    The reading goes on until a -1 is obtained, thus marking the end of all
    routes.

    Our reading process goes like:
        1. Read all output lines until the `TOUR_SECTION` begins;
        2. Read the nodes, converting each node larger than the number of
        locations into 1 until we reach a -1.
        3. The final list would be [1, 2, 3, 1, 4, 5, 6]. Then, split this
        list into subgroups with respect to the DEPOT_NODE 1, such as
        [2, 3], [4, 5, 6]. These will be the nodes of final routes.
    """

    num_locations = len(instance.deliveries) + 1
    with open(params.output_tour_file, "r") as f:
        # Ignore the header until we reach `TOUR_SECTION`
        for line in f:
            if line.startswith("TOUR_SECTION"):
                break

        # Read nodes in order replacing large numbers with DEPOT_NODE
        def read_delivery_nodes_gen():
            for line in f:
                node = int(line.rstrip("\n"))
                if node == -1:
                    break

                yield node if node <= num_locations else DEPOT_NODE

        all_route_nodes = list(read_delivery_nodes_gen())

        # Split the previous list with respect to `DEPOT_NODE`
        def write_route(nodes_group):
            # Notice the deliveries start at 2 in the output file but at 0 in
            # the instance
            deliveries = [
                instance.deliveries[node - 2] for node in nodes_group
            ]
            return CVRPSolutionVehicle(origin=instance.origin,
                                       deliveries=deliveries)

        nodes_groups = groupby(all_route_nodes, key=lambda x: x == DEPOT_NODE)
        routes = [
            write_route(nodes_group) for key, nodes_group in nodes_groups
            if not key
        ]

    return CVRPSolution(name=instance.name, vehicles=routes)