예제 #1
0
    def test_matrix_processing(self, origin, latitudes, longitudes):
        origin_lat = origin["latitude"]
        origin_lon = origin["longitude"]
        matrix = distance.create_matrix((origin_lat, origin_lon), latitudes,
                                        longitudes)

        assert len(matrix) == len(latitudes) + 1
예제 #2
0
def test_matrix():
    olat, olon = 41.4191, -87.7748
    dlats = common.TESTING_CSV_DF.latitude.tolist()
    dlons = common.TESTING_CSV_DF.longitude.tolist()
    matrix = distance.create_matrix((olat, olon), dlats, dlons)

    assert len(dlats) == len(dlons)

    i = randint(0, len(matrix) - 1)

    assert len(matrix) == len(matrix[i]) == len(dlats) + 1
예제 #3
0
    def test_create_vehicles(self, clusters, origin, latitudes, longitudes,
                             quantities):
        origin_lat = origin["latitude"]
        origin_lon = origin["longitude"]
        matrix = distance.create_matrix((origin_lat, origin_lon), latitudes,
                                        longitudes)

        demand = [int(d) for d in quantities]
        vehicles = model.create_vehicles(matrix, demand, np.array(clusters))

        load_factor = output_scoring.get_load_factor(vehicles["id"],
                                                     demand[1:])

        assert load_factor <= self.MAX_VEHICLE_CAPACITY_UNITS

        assert len(vehicles["id"]) == len(clusters)
예제 #4
0
    def test_vrp_bundle_case(self, origin, latitudes, longitudes, quantities):

        origin_lat = origin["latitude"]
        origin_lon = origin["longitude"]
        matrix = distance.create_matrix((origin_lat, origin_lon), latitudes,
                                        longitudes)

        demand = [int(d) for d in quantities]
        bndl = model.VrpBasicBundle(
            distance_matrix=matrix,
            demand_quantities=demand,
            max_vehicle_capacity_units=26,
            max_search_seconds=30,
        )

        vehicles = bndl.run().get_solution()

        assert len(vehicles["id"]) == len(latitudes)
예제 #5
0
def create_vehicles(
    origin_lat: float,
    origin_lon: float,
    dest_lats: List[float],
    dest_lons: List[float],
    demand_quantities: List[int],
    max_vehicle_capacity: int = 26,
) -> dict:  # TODO: tune in ortools-pyinteractive
    # demand including origin (ortools required)
    if len(demand_quantities) == len(dest_lats):
        ALL_DEMAND: List[int] = [0] + demand_quantities
    else:
        ALL_DEMAND: List[int] = demand_quantities

    # ad-hoc dynamic solve time TODO: utilize unique clusters and sizes, etc.
    if len(ALL_DEMAND) - 1 > 250:
        MAX_SEARCH_SECONDS: int = 120
    elif len(ALL_DEMAND) - 1 > 200 and len(ALL_DEMAND) - 1 <= 250:
        MAX_SEARCH_SECONDS: int = 60
    elif len(ALL_DEMAND) - 1 > 150 and len(ALL_DEMAND) - 1 <= 200:
        MAX_SEARCH_SECONDS: int = 30
    else:
        MAX_SEARCH_SECONDS: int = 5

    INT_PRECISION = 100
    MAX_VEHICLE_DIST = 3500 * INT_PRECISION
    MAX_VEHICLE_CAP: int = max_vehicle_capacity
    NUM_VEHICLES: int = len(ALL_DEMAND)
    SOFT_MAX_VEHICLE_DIST: int = int(MAX_VEHICLE_DIST * 0.75)
    SOFT_MAX_VEHICLE_COST: int = MAX_VEHICLE_DIST + 1  # cost feels ambiguous

    VEHICLE_CAPACITIES: List[int] = [MAX_VEHICLE_CAP for i in range(NUM_VEHICLES)]

    DIST_MATRIX: List[List[int]] = distance.create_matrix(
        origin_lat=origin_lat,
        origin_lon=origin_lon,
        dest_lats=dest_lats,
        dest_lons=dest_lons,
        int_precision=INT_PRECISION,
    )

    TIME_MATRIX: List[List[int]] = (
        (np.array(DIST_MATRIX) / 440 / INT_PRECISION).round(0).astype(int)
    )
    TIME_WINDOWS: List[Tuple[int, int]] = [(0, 23)] * len(DIST_MATRIX)

    CLUSTERS: List[int] = cluster.create_dbscan_clusters(lats=dest_lats, lons=dest_lons)

    CONSTRAINTS_TYPE = Tuple[int, int, int]
    Constraints: CONSTRAINTS_TYPE = namedtuple(
        "Constraint", ["dist_constraint", "soft_dist_constraint", "soft_dist_penalty"]
    )
    CONSTRAINTS: CONSTRAINTS_TYPE = Constraints(
        dist_constraint=MAX_VEHICLE_DIST,
        soft_dist_constraint=SOFT_MAX_VEHICLE_DIST,
        soft_dist_penalty=SOFT_MAX_VEHICLE_COST,
    )

    NODES_ARR: np.ndarray = np.array(
        [(0, origin_lat, origin_lon)]
        + list(zip(list(range(1, len(ALL_DEMAND) + 1)), dest_lats, dest_lons)),
        dtype=[("idx", int), ("lat", float), ("lon", float)],
    )

    DIST_MATRIX_ARR: np.ndarray = np.array(DIST_MATRIX)
    WINDOWS_MATRIX_ARR: np.ndarray = np.array(TIME_MATRIX)
    WINDOWS_ARR: np.ndarray = np.array(TIME_WINDOWS, dtype=object)
    DEMAND_ARR: np.ndarray = np.array(ALL_DEMAND)
    VEHICLE_CAP_ARR: np.ndarray = np.array(VEHICLE_CAPACITIES)

    # preprocess exceptions based on MAX_VEHICLE_DIST
    EXCEPTIONS = np.where(DIST_MATRIX_ARR[0] > MAX_VEHICLE_DIST)

    vehicle_count = 0
    vehicle_ids = [None] * len(dest_lats)
    stop_nums = [None] * len(dest_lats)
    for i, c in enumerate(np.unique(CLUSTERS)):

        # align with matrix
        is_cluster = np.where(CLUSTERS == c)[0]
        is_cluster = is_cluster + 1
        is_cluster = np.insert(is_cluster, 0, 0)
        is_cluster = is_cluster[~np.isin(is_cluster, EXCEPTIONS)]

        solution = solve(
            nodes=NODES_ARR[is_cluster],
            distance_matrix=DIST_MATRIX_ARR[is_cluster],
            time_matrix=WINDOWS_MATRIX_ARR[is_cluster],
            time_windows=WINDOWS_ARR[is_cluster],
            demand=DEMAND_ARR[is_cluster],
            vehicle_caps=VEHICLE_CAP_ARR[is_cluster],
            depot_index=0,
            constraints=CONSTRAINTS,
            max_search_seconds=MAX_SEARCH_SECONDS,
        )

        if not solution:
            continue

        for vehicle in solution:
            vehicle_count += 1

            for n, stop in enumerate(vehicle):
                if stop.idx != 0:
                    vehicle_ids[stop.idx - 1] = vehicle_count
                    stop_nums[stop.idx - 1] = n

    return {"id": vehicle_ids, "stops": stop_nums}
예제 #6
0
def vrp_procedure():
    """
    Main RPC endpoint for passing input data for optimized outputs.

    :origin_latitude:               float
    :origin_longitude:              float
    :unit:                          string; maps to unit of measure 
                                    key from POST
    :demand:                        list-like; contains ordered demand
                                    nodes represented as dict-like 
                                    objects
    :demand_longitude:              float
    :demand_quantity:               int
    :demand_cluster:                int
    :vehicle_max_capacity_quantity: int
    :vehicle_definitions':          list-like; int for vehicle max
                                    capacity overrides                                 
    """

    if request.is_json:
        body = ProcedureRequest.from_dict(request.get_json())  # noqa: E501
    else:
        return make_response(
            jsonify(
                InvalidUsageError(
                    f"Incorrect request format! Content type received '{request.content_type}' instead of 'application/json'"
                )),
            400,
        )
    demand = body.demand

    demand_latitudes = [d.latitude for d in demand]
    demand_longitudes = [d.longitude for d in demand]
    demand_quantities = [d.quantity for d in demand]

    # cluster by location (lat, lon)
    clusters = distance.create_dbscan_clusters(demand_latitudes,
                                               demand_longitudes)

    origin = body.origin
    # list of lists for all-to-all distances
    matrix = distance.create_matrix(
        (origin.latitude, origin.longitude),
        demand_latitudes,
        demand_longitudes,
    )

    # manage solve
    solution = model.create_vehicles(matrix, [0] + demand_quantities, clusters)

    response = {
        "origin": origin,
        "demand": demand,
        "unit": body.unit,
        "vehicle_capacity": body.vehicle_capacity,
    }

    response_solution = [{
        "cluster": clusters[i],
        "stop_id": solution["stops"][i],
        "vehicle_id": solution["id"][i],
    } for i in range(len(demand))]
    response["solution"] = response_solution

    return jsonify(response)