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
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
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)
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)
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}
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)