From 8c8792916c89065135bc1fc2ac377bd79eb28acf Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Fri, 3 May 2024 15:43:24 +0200 Subject: [PATCH 1/8] Added MinimalPassengerTravelTimeDispatcher for Python --- src/ridepy/util/dispatchers/__init__.py | 1 + src/ridepy/util/dispatchers/ridepooling.py | 212 ++++++++++++++++++++- 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/src/ridepy/util/dispatchers/__init__.py b/src/ridepy/util/dispatchers/__init__.py index 5b0faf21..a47fb87d 100644 --- a/src/ridepy/util/dispatchers/__init__.py +++ b/src/ridepy/util/dispatchers/__init__.py @@ -1,6 +1,7 @@ from ridepy.util.dispatchers.taxicab import TaxicabDispatcherDriveFirst from ridepy.util.dispatchers.ridepooling import ( BruteForceTotalTravelTimeMinimizingDispatcher, + MinimalPassengerTravelTimeDispatcher, ) """ diff --git a/src/ridepy/util/dispatchers/ridepooling.py b/src/ridepy/util/dispatchers/ridepooling.py index e07d55a4..fa797355 100644 --- a/src/ridepy/util/dispatchers/ridepooling.py +++ b/src/ridepy/util/dispatchers/ridepooling.py @@ -1,5 +1,5 @@ from copy import deepcopy - +from itertools import tee import numpy as np from ridepy.data_structures import ( @@ -209,6 +209,27 @@ def is_timewindow_violated_or_violation_worsened_due_to_insertion( return False +def pairwise(iterable): + # A pairwise iterator. + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + +def is_between(network, a, u, v): + """ + checks if a is on a shortest path between u and v + """ + dist_to = network.t(u, a) + dist_from = network.t(a, v) + dist_direct = network.t(u, v) + is_inbetween = dist_to + dist_from == dist_direct + if is_inbetween: + return True, dist_to, dist_from, dist_direct + else: + return False, dist_to, dist_from, dist_direct + + @dispatcherclass def BruteForceTotalTravelTimeMinimizingDispatcher( request: TransportationRequest, @@ -386,3 +407,192 @@ def BruteForceTotalTravelTimeMinimizingDispatcher( return min_cost, new_stoplist, (EAST_pu, LAST_pu, EAST_do, LAST_do) else: return min_cost, None, (np.nan, np.nan, np.nan, np.nan) + + +@dispatcherclass +def MinimalPassengerTravelTimeDispatcher( + request: TransportationRequest, + stoplist: Stoplist, + space: TransportSpace, + seat_capacity: int, +) -> DispatcherSolution: + """ + #FIXME Add description + """ + min_cost = np.inf + boolInsertEnd = True + best_pickup_idx = len(stoplist) - 1 + best_dropoff_idx = len(stoplist) - 1 + + for counter, (stop_before_pickup, stop_after_pickup) in enumerate( + pairwise(stoplist) + ): + listResultInBetweenTest = is_between( + space, + request.origin, + stop_before_pickup.location, + stop_after_pickup.location, + ) + if listResultInBetweenTest[0] == True and listResultInBetweenTest[2] != 0: + if stop_before_pickup.occupancy_after_servicing == seat_capacity: + continue + time_to_pickup = space.t(stop_before_pickup.location, request.origin) + CPAT_pu = cpat_of_inserted_stop(stop_before_pickup, time_to_pickup) + EAST_pu = request.pickup_timewindow_min + if CPAT_pu > request.pickup_timewindow_max: + continue + + best_pickup_idx = counter + boolDropOffEnroute = False + boolContinuePickUpLoop = False + boolBreakPickUpLoop = False + + # Check if drop-off is between pick-up and next stop + + listResultInBetweenTestFollowingPickUp = is_between( + space, request.destination, request.origin, stop_after_pickup.location + ) + if ( + listResultInBetweenTestFollowingPickUp[0] == True + and listResultInBetweenTestFollowingPickUp[2] != 0 + ): + # Time violation and seat capacity error not possilbe + # As long as now drop-off time restriction is used + # Ganz grosse Skepsis hier ... + best_dropoff_idx = counter + min_cost = CPAT_pu + boolDropOffEnroute = True + boolInsertEnd = False + break + + for counter_drop_off, ( + stop_before_dropoff, + stop_after_dropoff, + ) in enumerate(pairwise(stoplist[best_pickup_idx:])): + if counter_drop_off == 0: + continue + listResultInBetweenTestDropOff = is_between( + space, + request.destination, + stop_before_dropoff.location, + stop_after_dropoff.location, + ) + if ( + listResultInBetweenTestDropOff[0] == True + and listResultInBetweenTestDropOff[1] != 0 + ): + best_dropoff_idx = counter + counter_drop_off + time_to_dropoff = space.t( + stop_before_dropoff.location, request.destination + ) + CPAT_do = cpat_of_inserted_stop( + stop_before_dropoff, time_to_dropoff, delta_cpat=0 + ) + stoplist_request_in_vehicle = stoplist[ + best_pickup_idx : best_dropoff_idx + 1 + ] + # occupancies_ausschnitt = list(map(lambda x: x.occupancy_after_servicing, stoplist_request_in_vehicle)) + + occupancies_ausschnitt = [] + for x in stoplist_request_in_vehicle: + occupancies_ausschnitt.append(x.occupancy_after_servicing) + + if seat_capacity in occupancies_ausschnitt: + boolContinuePickUpLoop = True + break + if CPAT_do > request.delivery_timewindow_max: + # Pick-Up kann direkt fortgesetzt werden, da in diesem Fall auch der Drop-Off am Ende sicher scheitern wird + boolContinuePickUpLoop = True + break + boolInsertEnd = False + min_cost = CPAT_do + boolDropOffEnroute = True + boolBreakPickUpLoop = True + break + + if boolBreakPickUpLoop: + break + + if boolContinuePickUpLoop: + best_pickup_idx = len(stoplist) - 1 + best_dropoff_idx = len(stoplist) - 1 + continue + + if not boolDropOffEnroute: + best_dropoff_idx = len(stoplist) - 1 + stop_before_dropoff = stoplist[-1] + time_to_dropoff = space.t( + stop_before_dropoff.location, request.destination + ) + CPAT_do = cpat_of_inserted_stop( + stop_before_dropoff, time_to_dropoff, delta_cpat=0 + ) + # Check time violations? + # Hier muss ueberprueft werden, ob druch das einfügen irgendwo die seat capacity verletzt wird + stoplist_request_in_vehicle = stoplist[ + best_pickup_idx : best_dropoff_idx + 1 + ] + # occupancies_ausschnitt = list(map(lambda x: x.occupancy_after_servicing, stoplist_request_in_vehicle)) + + occupancies_ausschnitt = [] + for x in stoplist_request_in_vehicle: + occupancies_ausschnitt.append(x.occupancy_after_servicing) + + if seat_capacity in occupancies_ausschnitt: + best_pickup_idx = len(stoplist) - 1 + best_dropoff_idx = len(stoplist) - 1 + continue + if CPAT_do > request.delivery_timewindow_max: + best_pickup_idx = len(stoplist) - 1 + best_dropoff_idx = len(stoplist) - 1 + continue + boolInsertEnd = False + min_cost = CPAT_do + break + else: + continue + + if boolInsertEnd: + time_to_pickup = space.t(stoplist[-1].location, request.origin) + CPAT_pu = cpat_of_inserted_stop(stoplist[-1], time_to_pickup) + EAST_pu = request.pickup_timewindow_min + CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request.origin, request.destination) + if CPAT_pu > request.pickup_timewindow_max: + min_cost = np.inf + else: + min_cost = CPAT_do + + if min_cost < np.inf: + new_stoplist = insert_request_to_stoplist_drive_first( + stoplist=stoplist, + request=request, + pickup_idx=best_pickup_idx, + dropoff_idx=best_dropoff_idx, + space=space, + ) + EAST_pu, LAST_pu = ( + new_stoplist[best_pickup_idx + 1].time_window_min, + new_stoplist[best_pickup_idx + 1].time_window_max, + ) + EAST_do, LAST_do = ( + new_stoplist[best_dropoff_idx + 2].time_window_min, + new_stoplist[best_dropoff_idx + 2].time_window_max, + ) + + listOccupanciesNewStopList = list( + map(lambda x: x.occupancy_after_servicing, new_stoplist) + ) + for item in listOccupanciesNewStopList: + if item > seat_capacity: + print("Seat capacity error!!") + + for item in new_stoplist[1:]: + request_item = item.request + if item.action.name == "pickup": + if item.estimated_arrival_time > request_item.pickup_timewindow_max: + print("Time violation!!") + continue + + return min_cost, new_stoplist, (EAST_pu, LAST_pu, EAST_do, LAST_do) + else: + return min_cost, None, (np.nan, np.nan, np.nan, np.nan) From 9bc161519c48855925d67e5974be38f194672721 Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Fri, 3 May 2024 17:27:29 +0200 Subject: [PATCH 2/8] Added MinimalPassengerTravelTimeDispatcher for Cython --- .../util/dispatchers_cython/__init__.py | 1 + .../util/dispatchers_cython/cdispatchers.h | 215 ++++++++++++++++++ .../util/dispatchers_cython/cdispatchers.pxd | 8 + .../dispatchers_cython/cdispatchers_utils.h | 31 ++- .../util/dispatchers_cython/dispatchers.pyx | 13 ++ 5 files changed, 267 insertions(+), 1 deletion(-) diff --git a/src/ridepy/util/dispatchers_cython/__init__.py b/src/ridepy/util/dispatchers_cython/__init__.py index b96790dc..5b52d089 100644 --- a/src/ridepy/util/dispatchers_cython/__init__.py +++ b/src/ridepy/util/dispatchers_cython/__init__.py @@ -1,4 +1,5 @@ from .dispatchers import ( BruteForceTotalTravelTimeMinimizingDispatcher, + MinimalPassengerTravelTimeDispatcher, SimpleEllipseDispatcher, ) diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers.h b/src/ridepy/util/dispatchers_cython/cdispatchers.h index f70f644e..8fc1fa83 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers.h +++ b/src/ridepy/util/dispatchers_cython/cdispatchers.h @@ -369,6 +369,208 @@ template class AbstractDispatcher { virtual ~AbstractDispatcher(){}; }; +template +InsertionResultminimal_passenger_travel_time_dispatcher( + std::shared_ptr> request, + vector> &stoplist, + TransportSpace &space, int seat_capacity, + bool debug = false) { + + double min_cost = INFINITY; + bool boolInsertEnd = true; + int best_pickup_idx = stoplist.size() - 1; + int best_dropoff_idx = stoplist.size() - 1; + + // printf("------------ New request ------------\n"); + // printf("Request origin: %i\n", request->origin); + // printf("Request destination: %i\n", request->destination); + // printf("Stoplist: "); + // for (auto i: stoplist) { + // std::cout << i.location << ' '; + // } + // printf("\n"); + + for (int counter = 0; counter < stoplist.size() - 1; counter++) { + // printf("Counter: %i\n", counter); + Stop stop_before_pickup = stoplist[counter]; + Stop stop_after_pickup = stoplist[counter + 1]; + auto listResultInBetweenTest = is_between(space, request->origin, stop_before_pickup, stop_after_pickup); + if (std::get<0>(listResultInBetweenTest) == true && std::get<2>(listResultInBetweenTest) != 0) { + // printf("stop_before_pickup.occupancy_after_servicing %i\n", stop_before_pickup.occupancy_after_servicing); + // printf("seat_capacity %i\n", seat_capacity); + if (stop_before_pickup.occupancy_after_servicing == seat_capacity) { + continue; + } + double time_to_pickup = space.t(stop_before_pickup.location, request->origin); + double CPAT_pu = cpat_of_inserted_stop(stop_before_pickup, time_to_pickup); + double EAST_pu = request->pickup_timewindow_min; + if (CPAT_pu > request->pickup_timewindow_max) { + continue; + } + best_pickup_idx = counter; + // printf("Best pickup idx: %i\n", best_pickup_idx); + bool boolDropOffEnroute = false; + bool boolContinuePickUpLoop = false; + bool boolBreakPickUpLoop = false; + + auto listResultInBetweenTestFollowingPickUp = is_between_2(space, request->destination, request->origin, stop_after_pickup); + if (std::get<0>(listResultInBetweenTestFollowingPickUp) == true && std::get<2>(listResultInBetweenTestFollowingPickUp) != 0) { + best_dropoff_idx = counter; + min_cost = CPAT_pu; + boolDropOffEnroute = true; + boolInsertEnd = false; + break; + } + + for (int counter_drop_off = best_pickup_idx; counter_drop_off < stoplist.size() - 1; counter_drop_off++) { + // printf("Counter drop off: %i\n", counter_drop_off); + Stop stop_before_dropoff = stoplist[counter_drop_off]; + Stop stop_after_dropoff = stoplist[counter_drop_off + 1]; + if (counter_drop_off == counter) { + continue; + } + auto listResultInBetweenTestDropOff = is_between(space, request->destination, stop_before_dropoff, stop_after_dropoff); + //Changed from 2 to 1 + + if (std::get<0>(listResultInBetweenTestDropOff) == true && std::get<2>(listResultInBetweenTestDropOff) != 0) { + best_dropoff_idx = counter_drop_off; + double time_to_dropoff = space.t(stop_before_dropoff.location, request->destination); + double CPAT_do = cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); + std::vector>stoplist_request_in_vehicle = {stoplist.begin() + best_pickup_idx, stoplist.begin() + best_dropoff_idx + 1}; + + // printf("Stoplist Request In Vehicle: "); + // for (auto i: stoplist_request_in_vehicle) { + // std::cout << i.location << ' '; + // } + // printf("\n"); + + std::vector occupancies_ausschnitt; + std::transform(stoplist_request_in_vehicle.begin(), stoplist_request_in_vehicle.end(), std::back_inserter(occupancies_ausschnitt), [](auto x){return x.occupancy_after_servicing;}); + + // printf("Occupancies: "); + // for (auto i: occupancies_ausschnitt) { + // std::cout << i << ' '; + // } + // printf("\n"); + + if (std::count(occupancies_ausschnitt.begin(), occupancies_ausschnitt.end(), seat_capacity)) { + // printf("True \n"); + boolContinuePickUpLoop = true; + break; + } + + if (CPAT_do > request->delivery_timewindow_max) { + boolContinuePickUpLoop = true; + break; + } + boolInsertEnd = false; + min_cost = CPAT_do; + boolDropOffEnroute = true; + boolBreakPickUpLoop = true; + break; + } + } + + if (boolBreakPickUpLoop == true){ + break; + } + + if (boolContinuePickUpLoop == true){ + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + + if (boolDropOffEnroute == false){ + // printf("boolDropOffEnroute"); + // printf("\n"); + best_dropoff_idx = stoplist.size() - 1; + Stop stop_before_dropoff = stoplist[best_dropoff_idx]; + double time_to_dropoff = space.t(stop_before_dropoff.location, request->destination); + double CPAT_do = cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); + std::vector>stoplist_request_in_vehicle = {stoplist.begin() + best_pickup_idx, stoplist.begin() + best_dropoff_idx + 1}; + + // printf("Stoplist Request In Vehicle: "); + // for (auto i: stoplist_request_in_vehicle) { + // std::cout << i.location << ' '; + // } + // printf("\n"); + + std::vector occupancies_ausschnitt; + std::transform(stoplist_request_in_vehicle.begin(), stoplist_request_in_vehicle.end(), std::back_inserter(occupancies_ausschnitt), [](auto x){return x.occupancy_after_servicing;}); + + // printf("Occupancies: "); + // for (auto i: occupancies_ausschnitt) { + // std::cout << i << ' '; + // } + // printf("\n"); + + if (std::count(occupancies_ausschnitt.begin(), occupancies_ausschnitt.end(), seat_capacity)) { + // printf("True \n"); + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + + if (CPAT_do > request->delivery_timewindow_max) { + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + boolInsertEnd = false; + min_cost = CPAT_do; + break; + } + } else { + continue; + } + } + + if (boolInsertEnd == true) { + // both pickup and dropoff have to be appended + // printf("boolInsertEnd"); + // printf("\n"); + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + double time_to_pickup = space.t(stoplist[best_pickup_idx].location, request->origin); + double CPAT_pu = cpat_of_inserted_stop(stoplist[best_pickup_idx], time_to_pickup); + double EAST_pu = request->pickup_timewindow_min; + double CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request->origin, request->destination); + if (CPAT_pu > request->pickup_timewindow_max) { + min_cost = INFINITY; + } else{ + min_cost = CPAT_do; + } + } + + // printf("best_pickup_idx %i\n", best_pickup_idx); + // printf("best_dropoff_idx %i\n", best_dropoff_idx); + // printf("min_cost %f\n", min_cost); + + if (min_cost < INFINITY) { + auto new_stoplist = insert_request_to_stoplist_drive_first( + stoplist, request, best_pickup_idx, best_dropoff_idx, space); + + // printf("Reached insert_request_to_stoplist_drive_first\n"); + + // printf("New stoplist: "); + // for (auto i: new_stoplist) { + // std::cout << i.location << ' '; + // } + // printf("\n"); + + auto EAST_pu = new_stoplist[best_pickup_idx + 1].time_window_min; + auto LAST_pu = new_stoplist[best_pickup_idx + 1].time_window_max; + + auto EAST_do = new_stoplist[best_dropoff_idx + 2].time_window_min; + auto LAST_do = new_stoplist[best_dropoff_idx + 2].time_window_max; + return InsertionResult{new_stoplist, min_cost, EAST_pu, + LAST_pu, EAST_do, LAST_do}; + } else { + return InsertionResult{{}, min_cost, NAN, NAN, NAN, NAN}; + } +}; + // Pattern motivated by: https://stackoverflow.com/a/2592270 template @@ -384,6 +586,19 @@ class BruteForceTotalTravelTimeMinimizingDispatcher } }; +template +class MinimalPassengerTravelTimeDispatcher + : public AbstractDispatcher { +public: + InsertionResult + operator()(std::shared_ptr> request, + vector> &stoplist, TransportSpace &space, + int seat_capacity, bool debug = false) { + return minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity, debug); + } +}; + template class SimpleEllipseDispatcher : public AbstractDispatcher { public: diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers.pxd b/src/ridepy/util/dispatchers_cython/cdispatchers.pxd index 828b5d11..8502ea53 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers.pxd +++ b/src/ridepy/util/dispatchers_cython/cdispatchers.pxd @@ -12,6 +12,11 @@ cdef extern from "cdispatchers.h" namespace 'ridepy': shared_ptr[TransportationRequest[Loc]] request, vector[Stop[Loc]] &stoplist, const TransportSpace &space, int seat_capacity, bint debug) + + InsertionResult[Loc] minimal_passenger_travel_time_dispatcher[Loc]( + shared_ptr[TransportationRequest[Loc]] request, + vector[Stop[Loc]] &stoplist, + const TransportSpace &space, int seat_capacity, bint debug) InsertionResult[Loc] simple_ellipse_dispatcher[Loc]( shared_ptr[TransportationRequest[Loc]] request, @@ -28,5 +33,8 @@ cdef extern from "cdispatchers.h" namespace 'ridepy': cdef cppclass BruteForceTotalTravelTimeMinimizingDispatcher[Loc](AbstractDispatcher[Loc]): BruteForceTotalTravelTimeMinimizingDispatcher() + cdef cppclass MinimalPassengerTravelTimeDispatcher[Loc](AbstractDispatcher[Loc]): + MinimalPassengerTravelTimeDispatcher() + cdef cppclass SimpleEllipseDispatcher[Loc](AbstractDispatcher[Loc]): SimpleEllipseDispatcher(double) diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h b/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h index b7cf0b9f..042f3d7f 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h +++ b/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h @@ -6,6 +6,7 @@ #define RIDEPY_CDISPATCHERS_UTILS_H #include "../../data_structures_cython/cdata_structures.h" +#include "../spaces_cython/cspaces.h" namespace ridepy { @@ -219,6 +220,34 @@ bool is_timewindow_violated_dueto_insertion( return false; } +template +std::tuple is_between( + TransportSpace &space, const Loc a, Stop &stop_before, Stop &stop_after) { + double dist_to = space.t(stop_before.location, a); + double dist_from = space.t(a, stop_after.location); + double dist_direct = space.t(stop_before.location, stop_after.location); + bool is_inbetween = dist_to + dist_from == dist_direct; + if (is_inbetween) { + return std::make_tuple(true, dist_to, dist_from, dist_direct); + } else { + return std::make_tuple(false, dist_to, dist_from, dist_direct); + } + } + +template +std::tuple is_between_2( + TransportSpace &space, const Loc a, const Loc u, Stop &stop_after) { + double dist_to = space.t(u, a); + double dist_from = space.t(a, stop_after.location); + double dist_direct = space.t(u, stop_after.location); + bool is_inbetween = dist_to + dist_from == dist_direct; + if (is_inbetween) { + return std::make_tuple(true, dist_to, dist_from, dist_direct); + } else { + return std::make_tuple(false, dist_to, dist_from, dist_direct); + } + } + } // namespace ridepy -#endif // RIDEPY_CDISPATCHERS_UTILS_H +#endif // RIDEPY_CDISPATCHERS_UTILS_H \ No newline at end of file diff --git a/src/ridepy/util/dispatchers_cython/dispatchers.pyx b/src/ridepy/util/dispatchers_cython/dispatchers.pyx index 52b5398b..04d3cf13 100644 --- a/src/ridepy/util/dispatchers_cython/dispatchers.pyx +++ b/src/ridepy/util/dispatchers_cython/dispatchers.pyx @@ -5,6 +5,7 @@ from ridepy.data_structures_cython.data_structures cimport LocType, R2loc from .cdispatchers cimport ( AbstractDispatcher as CAbstractDispatcher, BruteForceTotalTravelTimeMinimizingDispatcher as CBruteForceTotalTravelTimeMinimizingDispatcher, +MinimalPassengerTravelTimeDispatcher as CMinimalPassengerTravelTimeDispatcher, SimpleEllipseDispatcher as CSimpleEllipseDispatcher, ) @@ -38,6 +39,18 @@ cdef class BruteForceTotalTravelTimeMinimizingDispatcher(Dispatcher): else: raise ValueError("This line should never have been reached") +cdef class MinimalPassengerTravelTimeDispatcher(Dispatcher): + def __cinit__(self, loc_type): + if loc_type == LocType.R2LOC: + self.u_dispatcher.dispatcher_r2loc_ptr = ( + new CMinimalPassengerTravelTimeDispatcher[R2loc]() + ) + elif loc_type == LocType.INT: + self.u_dispatcher.dispatcher_int_ptr = ( + new CMinimalPassengerTravelTimeDispatcher[int]() + ) + else: + raise ValueError("This line should never have been reached") cdef class SimpleEllipseDispatcher(Dispatcher): def __cinit__(self, loc_type, max_relative_detour=0): From 70589ccdd79fd881b0592df4cf40ae32f9056324 Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Tue, 7 May 2024 16:16:15 +0200 Subject: [PATCH 3/8] Unittests added & small Bugfix in Dispatcher --- src/ridepy/util/dispatchers/ridepooling.py | 20 +- .../util/dispatchers_cython/cdispatchers.h | 12 +- src/ridepy/util/testing_utils.py | 55 +++ .../util/testing_utils_cython/__init__.py | 1 + .../util/testing_utils_cython/dispatchers.pyx | 39 ++ ...inimal_passenger_travel_time_dispatcher.py | 428 ++++++++++++++++++ ...passenger_travel_time_dispatcher_cython.py | 261 +++++++++++ 7 files changed, 810 insertions(+), 6 deletions(-) create mode 100644 test/test_minimal_passenger_travel_time_dispatcher.py create mode 100644 test/test_minimal_passenger_travel_time_dispatcher_cython.py diff --git a/src/ridepy/util/dispatchers/ridepooling.py b/src/ridepy/util/dispatchers/ridepooling.py index fa797355..11afac98 100644 --- a/src/ridepy/util/dispatchers/ridepooling.py +++ b/src/ridepy/util/dispatchers/ridepooling.py @@ -424,9 +424,9 @@ def MinimalPassengerTravelTimeDispatcher( best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 - for counter, (stop_before_pickup, stop_after_pickup) in enumerate( - pairwise(stoplist) - ): + for counter in range(0, len(stoplist) - 1): + stop_before_pickup = stoplist[counter] + stop_after_pickup = stoplist[counter + 1] listResultInBetweenTest = is_between( space, request.origin, @@ -441,6 +441,11 @@ def MinimalPassengerTravelTimeDispatcher( EAST_pu = request.pickup_timewindow_min if CPAT_pu > request.pickup_timewindow_max: continue + CPAT_do = max(EAST_pu, CPAT_pu) + space.t( + request.origin, request.destination + ) + if CPAT_do > request.delivery_timewindow_max: + continue best_pickup_idx = counter boolDropOffEnroute = False @@ -491,6 +496,7 @@ def MinimalPassengerTravelTimeDispatcher( stoplist_request_in_vehicle = stoplist[ best_pickup_idx : best_dropoff_idx + 1 ] + # occupancies_ausschnitt = list(map(lambda x: x.occupancy_after_servicing, stoplist_request_in_vehicle)) occupancies_ausschnitt = [] @@ -553,12 +559,16 @@ def MinimalPassengerTravelTimeDispatcher( continue if boolInsertEnd: - time_to_pickup = space.t(stoplist[-1].location, request.origin) - CPAT_pu = cpat_of_inserted_stop(stoplist[-1], time_to_pickup) + best_pickup_idx = len(stoplist) - 1 + best_dropoff_idx = len(stoplist) - 1 + time_to_pickup = space.t(stoplist[best_pickup_idx].location, request.origin) + CPAT_pu = cpat_of_inserted_stop(stoplist[best_pickup_idx], time_to_pickup) EAST_pu = request.pickup_timewindow_min CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request.origin, request.destination) if CPAT_pu > request.pickup_timewindow_max: min_cost = np.inf + elif CPAT_do > request.delivery_timewindow_max: + min_cost = np.inf else: min_cost = CPAT_do diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers.h b/src/ridepy/util/dispatchers_cython/cdispatchers.h index 8fc1fa83..4482f9f7 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers.h +++ b/src/ridepy/util/dispatchers_cython/cdispatchers.h @@ -407,6 +407,11 @@ InsertionResultminimal_passenger_travel_time_dispatcher( if (CPAT_pu > request->pickup_timewindow_max) { continue; } + // dropoff immediately + double CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request->origin, request->destination); + + if (CPAT_do > request->delivery_timewindow_max) + continue; best_pickup_idx = counter; // printf("Best pickup idx: %i\n", best_pickup_idx); bool boolDropOffEnroute = false; @@ -539,7 +544,12 @@ InsertionResultminimal_passenger_travel_time_dispatcher( if (CPAT_pu > request->pickup_timewindow_max) { min_cost = INFINITY; } else{ - min_cost = CPAT_do; + if (CPAT_do > request->delivery_timewindow_max) { + min_cost = INFINITY; + } + else{ + min_cost = CPAT_do; + } } } diff --git a/src/ridepy/util/testing_utils.py b/src/ridepy/util/testing_utils.py index 636f6edc..aca1a58c 100644 --- a/src/ridepy/util/testing_utils.py +++ b/src/ridepy/util/testing_utils.py @@ -4,6 +4,7 @@ from ridepy.util.spaces_cython import TransportSpace as CyTransportSpace from ridepy.util.testing_utils_cython import ( BruteForceTotalTravelTimeMinimizingDispatcher as CyBruteForceTotalTravelTimeMinimizingDispatcher, + MinimalPassengerTravelTimeDispatcher as CyMinimalPassengerTravelTimeDispatcher, ) from ridepy.util.spaces_cython import spaces as cyspaces from typing import Literal, Iterable, Union, Callable, Sequence @@ -13,6 +14,7 @@ from ridepy.util import spaces as pyspaces from ridepy.util.dispatchers.ridepooling import ( BruteForceTotalTravelTimeMinimizingDispatcher, + MinimalPassengerTravelTimeDispatcher, ) @@ -133,3 +135,56 @@ def setup_insertion_data_structures( ) return space, request, stoplist, dispatcher(loc_type=space.loc_type) + + +def setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + *, + stoplist_properties: Iterable[Sequence[Union[Location, float]]], + request_properties, + space_type: str, + kind: str, +) -> tuple[ + Union[TransportSpace, CyTransportSpace], + Union[pyds.TransportationRequest, cyds.TransportationRequest], + Union[Stoplist, cyds.Stoplist], + Dispatcher, +]: + """ + Function is specified for testing the MinimalPassengerTravelTimeDispatcher + #FIXME Merge functions or rename above function + + Parameters + ---------- + stoplist_properties + request_properties + space_type + kind + 'cython' or 'python' + + Returns + ------- + space, request, stoplist, dispatcher + """ + + if kind == "python": + spaces = pyspaces + ds = pyds + dispatcher = MinimalPassengerTravelTimeDispatcher + elif kind == "cython": + spaces = cyspaces + ds = cyds + dispatcher = CyMinimalPassengerTravelTimeDispatcher + else: + raise ValueError(f"Supplied invalid {kind=}, must be 'python' or 'cython'") + + space = getattr(spaces, space_type)() + + # set up the request + request = ds.TransportationRequest(**request_properties) + + # set up the stoplist + stoplist = stoplist_from_properties( + stoplist_properties=stoplist_properties, space=space, kind=kind + ) + + return space, request, stoplist, dispatcher(loc_type=space.loc_type) diff --git a/src/ridepy/util/testing_utils_cython/__init__.py b/src/ridepy/util/testing_utils_cython/__init__.py index b96790dc..5b52d089 100644 --- a/src/ridepy/util/testing_utils_cython/__init__.py +++ b/src/ridepy/util/testing_utils_cython/__init__.py @@ -1,4 +1,5 @@ from .dispatchers import ( BruteForceTotalTravelTimeMinimizingDispatcher, + MinimalPassengerTravelTimeDispatcher, SimpleEllipseDispatcher, ) diff --git a/src/ridepy/util/testing_utils_cython/dispatchers.pyx b/src/ridepy/util/testing_utils_cython/dispatchers.pyx index 94f9769e..db8b4808 100644 --- a/src/ridepy/util/testing_utils_cython/dispatchers.pyx +++ b/src/ridepy/util/testing_utils_cython/dispatchers.pyx @@ -19,6 +19,7 @@ from ridepy.data_structures_cython.cdata_structures cimport ( from ridepy.util.dispatchers_cython.cdispatchers cimport ( brute_force_total_traveltime_minimizing_dispatcher as c_brute_force_total_traveltime_minimizing_dispatcher, + minimal_passenger_travel_time_dispatcher as c_minimal_passenger_travel_time_dispatcher, simple_ellipse_dispatcher as c_simple_ellipse_dispatcher ) @@ -84,6 +85,44 @@ cdef class BruteForceTotalTravelTimeMinimizingDispatcher: else: raise ValueError("This line should never have been reached") +cdef class MinimalPassengerTravelTimeDispatcher: + cdef LocType loc_type + + def __init__(self, loc_type): + self.loc_type = loc_type + + def __call__( + self, + TransportationRequest cy_request, + Stoplist stoplist, + TransportSpace space, + int seat_capacity, + bint debug=False + ): + cdef InsertionResult[R2loc] insertion_result_r2loc + cdef InsertionResult[int] insertion_result_int + + if self.loc_type == LocType.R2LOC: + insertion_result_r2loc = c_minimal_passenger_travel_time_dispatcher[R2loc]( + dynamic_pointer_cast[CTransportationRequest[R2loc], CRequest[R2loc]](cy_request._ureq._req_r2loc), + stoplist.ustoplist._stoplist_r2loc, + dereference(space.u_space.space_r2loc_ptr), seat_capacity, debug + ) + return insertion_result_r2loc.min_cost, Stoplist.from_c_r2loc(insertion_result_r2loc.new_stoplist), \ + (insertion_result_r2loc.EAST_pu, insertion_result_r2loc.LAST_pu, + insertion_result_r2loc.EAST_do, insertion_result_r2loc.LAST_do) + elif self.loc_type == LocType.INT: + insertion_result_int = c_minimal_passenger_travel_time_dispatcher[int]( + dynamic_pointer_cast[CTransportationRequest[int], CRequest[int]](cy_request._ureq._req_int), + stoplist.ustoplist._stoplist_int, + dereference(space.u_space.space_int_ptr), seat_capacity, debug + ) + return insertion_result_int.min_cost, Stoplist.from_c_int(insertion_result_int.new_stoplist), \ + (insertion_result_int.EAST_pu, insertion_result_int.LAST_pu, + insertion_result_int.EAST_do, insertion_result_int.LAST_do) + else: + raise ValueError("This line should never have been reached") + cdef class SimpleEllipseDispatcher: cdef LocType loc_type diff --git a/test/test_minimal_passenger_travel_time_dispatcher.py b/test/test_minimal_passenger_travel_time_dispatcher.py new file mode 100644 index 00000000..b1778a72 --- /dev/null +++ b/test/test_minimal_passenger_travel_time_dispatcher.py @@ -0,0 +1,428 @@ +import numpy as np +import pytest + +import itertools as it + +from numpy import inf, isclose + +from ridepy.events import ( + RequestRejectionEvent, + PickupEvent, + DeliveryEvent, +) +from ridepy.util.dispatchers.ridepooling import ( + MinimalPassengerTravelTimeDispatcher, +) +from ridepy.extras.spaces import make_nx_grid +from ridepy.util.request_generators import RandomRequestGenerator +from ridepy.util.spaces import Euclidean2D, Graph +from ridepy.fleet_state import SlowSimpleFleetState +from ridepy.util.testing_utils import ( + setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher, +) +from ridepy.vehicle_state import VehicleState + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_append_to_empty_stoplist(kind): + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(0, 1), + destination=(0, 2), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [[(0, 0), 0, 0, inf]] + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert new_stoplist[-2].location == request.origin + assert new_stoplist[-1].location == request.destination + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_no_solution_found(kind): + """Test that if no solution exists, none is returned""" + # FIXME: Unclear, how to use the Manhatten graph with shortest paths. + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 1, 1], + [(0, 3), 3, 3, 3] + ] + # fmt: on + eps = 1e-4 + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(eps, 1), + destination=(eps, 2), + pickup_timewindow_min=0, + pickup_timewindow_max=eps / 2, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Manhattan2D", + kind=kind, + ) + + ( + min_cost, + new_stoplist, + timewindows, + ) = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert np.isinf(min_cost) + assert not new_stoplist # an empty `Stoplist` for cython, None for python + assert np.isnan(timewindows).all() + + # But the same shouldn't occur if the tw_max were higher: + # FIXME: It is unclear, if this is true for the minimal passenger travel time dispatcher + stoplist_properties = [ + [(0, 1), 1, 1, 0], + [(0, 3), 3, 3, 3 + 3 * eps], # tw_max just enough + ] + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(eps, 1), + destination=(eps, 2), + pickup_timewindow_min=0, + pickup_timewindow_max=1 + 2 * eps, # just enough + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Manhattan2D", + kind=kind, + ) + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + # assert not np.isinf(min_cost) + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_append_due_to_pickup_not_on_shortest_path(kind): + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 0, inf], + [(0, 3), 3, 3, 3] + ] + # fmt: on + eps = 1e-4 + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(eps, 2), + destination=(0, 4), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert np.allclose(new_stoplist[-2].location, request.origin) + assert np.allclose(new_stoplist[-1].location, request.destination) + + assert [s.occupancy_after_servicing for s in new_stoplist] == [0, 0, 1, 0] + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_inserted_at_the_middle(kind): + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 0, inf], + [(0, 3), 3, 0, 6], + ] + # fmt: on + eps = 1e-4 + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(0, 1.5), + destination=(0, 2.5), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert new_stoplist[1].location == request.origin + assert new_stoplist[2].location == request.destination + assert [s.occupancy_after_servicing for s in new_stoplist] == [0, 1, 0, 0] + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_inserted_separately(kind): + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 0, inf], + [(0, 3), 3, 0, inf], + [(0, 5), 5, 0, inf], + [(0, 7), 7, 0, inf], + ] + # fmt: on + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(0, 2), + destination=(0, 4), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert new_stoplist[1].location == request.origin + assert new_stoplist[3].location == request.destination + assert [s.occupancy_after_servicing for s in new_stoplist] == [0, 1, 1, 0, 0, 0] + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_not_inserted_separately_dueto_capacity_constraint(kind): + """ + Forces the pickup and dropoff to be inserted together solely because + of seat_capacity=1 + """ + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 0, inf], + [(0, 3), 3, 0, inf], + [(0, 5), 5, 0, inf], + [(0, 7), 7, 0, inf], + ] + # fmt: on + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(0, 1.5), + destination=(0, 2.5), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + # the best insertion would be [s0, +, -, s1, s2, s3] + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + + for s, cap in zip(stoplist, [0, 1, 0, 1]): + s.occupancy_after_servicing = cap + + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=1 + ) + assert new_stoplist[1].location == request.origin + assert new_stoplist[2].location == request.destination + assert [s.occupancy_after_servicing for s in new_stoplist] == [0, 1, 0, 1, 0, 1] + + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + + # the best insertion would be [s0, s1, s2, s3, +, -,] + for s, cap in zip(stoplist, [1, 1, 1, 0]): + s.occupancy_after_servicing = cap + + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=1 + ) + assert new_stoplist[4].location == request.origin + assert new_stoplist[5].location == request.destination + assert [s.occupancy_after_servicing for s in new_stoplist] == [1, 1, 1, 0, 1, 0] + + +@pytest.mark.parametrize("kind", ["python", "cython"]) +def test_stoplist_not_modified_inplace(kind): + # fmt: off + # location, cpat, tw_min, tw_max, occupancy + stoplist_properties = [ + [(0, 1), 1, 0, inf], + [(0, 3), 3, 0, 6], + ] + # fmt: on + eps = 0 + request_properties = dict( + request_id=42, + creation_timestamp=1, + origin=(eps, 1.5), + destination=(eps, 2.5), + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + ( + space, + request, + stoplist, + minimal_passenger_travel_time_dispatcher, + ) = setup_insertion_data_structures_minimal_passenger_travel_time_dispatcher( + stoplist_properties=stoplist_properties, + request_properties=request_properties, + space_type="Euclidean2D", + kind=kind, + ) + + min_cost, new_stoplist, *_ = minimal_passenger_travel_time_dispatcher( + request, stoplist, space, seat_capacity=10 + ) + assert new_stoplist[1].location == request.origin + assert new_stoplist[2].location == request.destination + assert new_stoplist[3].estimated_arrival_time == 3 + 2 * eps + assert stoplist[1].estimated_arrival_time == 3 + + +def test_sanity_in_graph(): + """ + Insert a request, note delivery time. + Handle more requests so that there's no pooling. + Assert that the delivery time is not changed. + + Or more simply, assert that the vehicle moves at either the space's velocity or 0. + """ + + for velocity in [0.9, 1, 1.1]: + space = Graph.from_nx(make_nx_grid(), velocity=velocity) + + rg = RandomRequestGenerator( + rate=10, + space=space, + max_delivery_delay_abs=0, + ) + + transportation_requests = list(it.islice(rg, 10000)) + + fs = SlowSimpleFleetState( + initial_locations={k: 0 for k in range(50)}, + seat_capacities=10, + space=space, + dispatcher=MinimalPassengerTravelTimeDispatcher(), + vehicle_state_class=VehicleState, + ) + + events = list(fs.simulate(transportation_requests)) + + rejections = set( + ev["request_id"] + for ev in events + if ev["event_type"] == "RequestRejectionEvent" + ) + pickup_times = { + ev["request_id"]: ev["timestamp"] + for ev in events + if ev["event_type"] == "PickupEvent" + } + delivery_times = { + ev["request_id"]: ev["timestamp"] + for ev in events + if ev["event_type"] == "DeliveryEvent" + } + + assert len(transportation_requests) > len(rejections) + for req in transportation_requests: + if (rid := req.request_id) not in rejections: + assert isclose(req.delivery_timewindow_max, delivery_times[rid]) + assert isclose( + delivery_times[rid] - pickup_times[rid], + space.t(req.origin, req.destination), + ) + + +if __name__ == "__main__": + pytest.main(args=[__file__]) diff --git a/test/test_minimal_passenger_travel_time_dispatcher_cython.py b/test/test_minimal_passenger_travel_time_dispatcher_cython.py new file mode 100644 index 00000000..1ff99336 --- /dev/null +++ b/test/test_minimal_passenger_travel_time_dispatcher_cython.py @@ -0,0 +1,261 @@ +import random +import pytest + +import numpy as np +import itertools as it + +from numpy import inf, isclose +from time import time +from pandas.core.common import flatten + +from ridepy.data_structures_cython import Stoplist as CyStoplist + +from ridepy import data_structures_cython as cyds +from ridepy import data_structures as pyds + +from ridepy.events import ( + RequestRejectionEvent, + PickupEvent, + DeliveryEvent, +) + +from ridepy.data_structures_cython.data_structures import LocType +from ridepy.util import spaces as pyspaces +from ridepy.util.spaces_cython import spaces as cyspaces +from ridepy.util.request_generators import RandomRequestGenerator + +from ridepy.util.dispatchers.ridepooling import ( + MinimalPassengerTravelTimeDispatcher, +) +from ridepy.util.dispatchers_cython import ( + MinimalPassengerTravelTimeDispatcher as CMinimalPassengerTravelTimeDispatcher, +) +from ridepy.util.testing_utils_cython import ( + MinimalPassengerTravelTimeDispatcher as CyMinimalPassengerTravelTimeDispatcher, +) +from ridepy.util.testing_utils import stoplist_from_properties +from ridepy.vehicle_state import VehicleState as py_VehicleState +from ridepy.vehicle_state_cython import VehicleState as cy_VehicleState + +from ridepy.fleet_state import SlowSimpleFleetState +from ridepy.extras.spaces import make_nx_grid + + +def test_equivalence_cython_and_python_minimal_passenger_travel_time_dispatcher( + seed=42, +): + """ + Tests that the pure pythonic and cythonic minimal passenger travel time dispatcher produces identical results. + """ + len_stoplist = 100 + seat_capacity = 4 + rnd = np.random.RandomState(seed) + stop_locations = rnd.uniform(low=0, high=100, size=(len_stoplist, 2)) + arrival_times = np.cumsum( + [np.linalg.norm(x - y) for x, y in zip(stop_locations[:-1], stop_locations[1:])] + ) + arrival_times = np.insert(arrival_times, 0, 0) + # location, CPAT, tw_min, tw_max, + stoplist_properties = [ + [stop_loc, CPAT, 0, inf] + for stop_loc, CPAT in zip(stop_locations, arrival_times) + ] + origin = list(rnd.uniform(low=0, high=100, size=2)) + destination = list(rnd.uniform(low=0, high=100, size=2)) + + # first call the pure pythonic dispatcher + request = pyds.TransportationRequest( + request_id=100, + creation_timestamp=1, + origin=origin, + destination=destination, + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + space = pyspaces.Euclidean2D() + stoplist = stoplist_from_properties( + stoplist_properties=stoplist_properties, kind="python", space=space + ) + + tick = time() + # min_cost, new_stoplist, (EAST_pu, LAST_pu, EAST_do, LAST_do) + pythonic_solution = MinimalPassengerTravelTimeDispatcher()( + request, stoplist, space, seat_capacity + ) + py_min_cost, _, py_timewindows = pythonic_solution + tock = time() + print( + f"Computing insertion into {len_stoplist}-element stoplist with pure pythonic dispatcher took: {tock - tick} seconds" + ) + + # then call the cythonic dispatcher + request = cyds.TransportationRequest( + request_id=100, + creation_timestamp=1, + origin=origin, + destination=destination, + pickup_timewindow_min=0, + pickup_timewindow_max=inf, + delivery_timewindow_min=0, + delivery_timewindow_max=inf, + ) + + # Note: we need to create a Cythonic stoplist object here because we cannot pass a python list to + # CyMinimalPassengerTravelTimeDispatcher + space = cyspaces.Euclidean2D() + stoplist = stoplist_from_properties( + stoplist_properties=stoplist_properties, kind="cython", space=space + ) + tick = time() + # vehicle_id, new_stoplist, (min_cost, EAST_pu, LAST_pu, EAST_do, LAST_do) + cythonic_solution = CyMinimalPassengerTravelTimeDispatcher(LocType.R2LOC)( + request, stoplist, space, seat_capacity + ) + cy_min_cost, _, cy_timewindows = cythonic_solution + tock = time() + print( + f"Computing insertion into {len_stoplist}-element stoplist with cythonic dispatcher took: {tock-tick} seconds" + ) + + assert np.isclose(py_min_cost, cy_min_cost) + assert np.allclose(py_timewindows, cy_timewindows) + + +def test_equivalence_simulator_cython_and_python_minimal_passenger_travel_time_dispatcher( + seed=42, +): + """ + Tests that the simulation runs with pure pythonic and cythonic minimal passenger travel time dispatcher produces identical events. + """ + for py_space, cy_space in ( + (pyspaces.Euclidean2D(), cyspaces.Euclidean2D()), + # DO NOT test graph spaces for now, as we use different methods for computing the shortest path + # (floyd-warshall in Python and dijkstra in C++. Therefore differences in interpolation arise.) + # ( + # pyspaces.Graph.from_nx(make_nx_grid()), + # cyspaces.Graph.from_nx(make_nx_grid()), + # ), + ): + n_reqs = 100 + + random.seed(seed) + init_loc = py_space.random_point() + random.seed(seed) + assert init_loc == cy_space.random_point() + + ###################################################### + # PYTHON + ###################################################### + + ssfs = SlowSimpleFleetState( + initial_locations={7: init_loc}, + seat_capacities=10, + space=py_space, + dispatcher=MinimalPassengerTravelTimeDispatcher(), + vehicle_state_class=py_VehicleState, + ) + rg = RandomRequestGenerator( + space=py_space, + request_class=pyds.TransportationRequest, + seed=seed, + rate=1.5, + ) + py_reqs = list(it.islice(rg, n_reqs)) + py_events = list(ssfs.simulate(py_reqs)) + + ###################################################### + # CYTHON + ###################################################### + + ssfs = SlowSimpleFleetState( + initial_locations={7: init_loc}, + seat_capacities=10, + space=cy_space, + dispatcher=CMinimalPassengerTravelTimeDispatcher( + loc_type=cy_space.loc_type + ), + vehicle_state_class=cy_VehicleState, + ) + rg = RandomRequestGenerator( + space=cy_space, + request_class=cyds.TransportationRequest, + seed=seed, + rate=1.5, + ) + cy_reqs = list(it.islice(rg, n_reqs)) + cy_events = list(ssfs.simulate(cy_reqs)) + + ###################################################### + # COMPARE + ###################################################### + # assert that the returned events are the same + assert len(cy_events) == len(py_events) + for num, (cev, pev) in enumerate(zip(cy_events, py_events)): + assert type(cev) == type(pev) + assert np.allclose( + list(flatten([v for k, v in pev.items() if k != "event_type"])), + list(flatten([v for k, v in cev.items() if k != "event_type"])), + rtol=1e-4, + ) + + +def test_sanity_in_graph(): + """ + Insert a request, note delivery time. + Handle more requests so that there's no pooling. + Assert that the delivery time is not changed. + + Or more simply, assert that the vehicle moves at either the space's velocity or 0. + """ + + for velocity in [0.9, 1, 1.1]: + space = cyspaces.Graph.from_nx(make_nx_grid(), velocity=velocity) + # max_pickup_delay=0, + rg = RandomRequestGenerator( + rate=10, + space=space, + max_delivery_delay_abs=0, + request_class=cyds.TransportationRequest, + ) + + transportation_requests = list(it.islice(rg, 1000)) + + fs = SlowSimpleFleetState( + initial_locations={k: 0 for k in range(50)}, + seat_capacities=10, + space=space, + dispatcher=CMinimalPassengerTravelTimeDispatcher(LocType.INT), + vehicle_state_class=cy_VehicleState, + ) + + events = list(fs.simulate(transportation_requests)) + + rejections = set( + ev["request_id"] + for ev in events + if ev["event_type"] == "RequestRejectionEvent" + ) + pickup_times = { + ev["request_id"]: ev["timestamp"] + for ev in events + if ev["event_type"] == "PickupEvent" + } + delivery_times = { + ev["request_id"]: ev["timestamp"] + for ev in events + if ev["event_type"] == "DeliveryEvent" + } + + for req in transportation_requests: + if (rid := req.request_id) not in rejections: + assert isclose(req.delivery_timewindow_max, delivery_times[rid]) + assert isclose( + delivery_times[rid] - pickup_times[rid], + space.t(req.origin, req.destination), + ) + + +if __name__ == "__main__": + pytest.main(args=[__file__]) From 3d189b0cb35238da3d6cd389d22a1007511bb79a Mon Sep 17 00:00:00 2001 From: Felix Jung Date: Wed, 15 May 2024 11:21:28 +0200 Subject: [PATCH 4/8] clang-format --- .../util/dispatchers_cython/cdispatchers.h | 272 ++++++++++-------- .../dispatchers_cython/cdispatchers_utils.h | 52 ++-- 2 files changed, 177 insertions(+), 147 deletions(-) diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers.h b/src/ridepy/util/dispatchers_cython/cdispatchers.h index 4482f9f7..33e0e304 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers.h +++ b/src/ridepy/util/dispatchers_cython/cdispatchers.h @@ -370,11 +370,10 @@ template class AbstractDispatcher { }; template -InsertionResultminimal_passenger_travel_time_dispatcher( - std::shared_ptr> request, - vector> &stoplist, - TransportSpace &space, int seat_capacity, - bool debug = false) { +InsertionResult minimal_passenger_travel_time_dispatcher( + std::shared_ptr> request, + vector> &stoplist, TransportSpace &space, int seat_capacity, + bool debug = false) { double min_cost = INFINITY; bool boolInsertEnd = true; @@ -394,107 +393,71 @@ InsertionResultminimal_passenger_travel_time_dispatcher( // printf("Counter: %i\n", counter); Stop stop_before_pickup = stoplist[counter]; Stop stop_after_pickup = stoplist[counter + 1]; - auto listResultInBetweenTest = is_between(space, request->origin, stop_before_pickup, stop_after_pickup); - if (std::get<0>(listResultInBetweenTest) == true && std::get<2>(listResultInBetweenTest) != 0) { - // printf("stop_before_pickup.occupancy_after_servicing %i\n", stop_before_pickup.occupancy_after_servicing); - // printf("seat_capacity %i\n", seat_capacity); - if (stop_before_pickup.occupancy_after_servicing == seat_capacity) { - continue; - } - double time_to_pickup = space.t(stop_before_pickup.location, request->origin); - double CPAT_pu = cpat_of_inserted_stop(stop_before_pickup, time_to_pickup); - double EAST_pu = request->pickup_timewindow_min; - if (CPAT_pu > request->pickup_timewindow_max) { - continue; - } - // dropoff immediately - double CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request->origin, request->destination); - - if (CPAT_do > request->delivery_timewindow_max) - continue; - best_pickup_idx = counter; - // printf("Best pickup idx: %i\n", best_pickup_idx); - bool boolDropOffEnroute = false; - bool boolContinuePickUpLoop = false; - bool boolBreakPickUpLoop = false; - - auto listResultInBetweenTestFollowingPickUp = is_between_2(space, request->destination, request->origin, stop_after_pickup); - if (std::get<0>(listResultInBetweenTestFollowingPickUp) == true && std::get<2>(listResultInBetweenTestFollowingPickUp) != 0) { - best_dropoff_idx = counter; - min_cost = CPAT_pu; - boolDropOffEnroute = true; - boolInsertEnd = false; - break; - } - - for (int counter_drop_off = best_pickup_idx; counter_drop_off < stoplist.size() - 1; counter_drop_off++) { - // printf("Counter drop off: %i\n", counter_drop_off); - Stop stop_before_dropoff = stoplist[counter_drop_off]; - Stop stop_after_dropoff = stoplist[counter_drop_off + 1]; - if (counter_drop_off == counter) { - continue; - } - auto listResultInBetweenTestDropOff = is_between(space, request->destination, stop_before_dropoff, stop_after_dropoff); - //Changed from 2 to 1 - - if (std::get<0>(listResultInBetweenTestDropOff) == true && std::get<2>(listResultInBetweenTestDropOff) != 0) { - best_dropoff_idx = counter_drop_off; - double time_to_dropoff = space.t(stop_before_dropoff.location, request->destination); - double CPAT_do = cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); - std::vector>stoplist_request_in_vehicle = {stoplist.begin() + best_pickup_idx, stoplist.begin() + best_dropoff_idx + 1}; - - // printf("Stoplist Request In Vehicle: "); - // for (auto i: stoplist_request_in_vehicle) { - // std::cout << i.location << ' '; - // } - // printf("\n"); - - std::vector occupancies_ausschnitt; - std::transform(stoplist_request_in_vehicle.begin(), stoplist_request_in_vehicle.end(), std::back_inserter(occupancies_ausschnitt), [](auto x){return x.occupancy_after_servicing;}); - - // printf("Occupancies: "); - // for (auto i: occupancies_ausschnitt) { - // std::cout << i << ' '; - // } - // printf("\n"); - - if (std::count(occupancies_ausschnitt.begin(), occupancies_ausschnitt.end(), seat_capacity)) { - // printf("True \n"); - boolContinuePickUpLoop = true; - break; - } - - if (CPAT_do > request->delivery_timewindow_max) { - boolContinuePickUpLoop = true; - break; - } - boolInsertEnd = false; - min_cost = CPAT_do; - boolDropOffEnroute = true; - boolBreakPickUpLoop = true; - break; - } - } + auto listResultInBetweenTest = is_between( + space, request->origin, stop_before_pickup, stop_after_pickup); + if (std::get<0>(listResultInBetweenTest) == true && + std::get<2>(listResultInBetweenTest) != 0) { + // printf("stop_before_pickup.occupancy_after_servicing %i\n", + // stop_before_pickup.occupancy_after_servicing); printf("seat_capacity + // %i\n", seat_capacity); + if (stop_before_pickup.occupancy_after_servicing == seat_capacity) { + continue; + } + double time_to_pickup = + space.t(stop_before_pickup.location, request->origin); + double CPAT_pu = + cpat_of_inserted_stop(stop_before_pickup, time_to_pickup); + double EAST_pu = request->pickup_timewindow_min; + if (CPAT_pu > request->pickup_timewindow_max) { + continue; + } + // dropoff immediately + double CPAT_do = max(EAST_pu, CPAT_pu) + + space.t(request->origin, request->destination); - if (boolBreakPickUpLoop == true){ - break; - } + if (CPAT_do > request->delivery_timewindow_max) + continue; + best_pickup_idx = counter; + // printf("Best pickup idx: %i\n", best_pickup_idx); + bool boolDropOffEnroute = false; + bool boolContinuePickUpLoop = false; + bool boolBreakPickUpLoop = false; + + auto listResultInBetweenTestFollowingPickUp = is_between_2( + space, request->destination, request->origin, stop_after_pickup); + if (std::get<0>(listResultInBetweenTestFollowingPickUp) == true && + std::get<2>(listResultInBetweenTestFollowingPickUp) != 0) { + best_dropoff_idx = counter; + min_cost = CPAT_pu; + boolDropOffEnroute = true; + boolInsertEnd = false; + break; + } - if (boolContinuePickUpLoop == true){ - best_pickup_idx = stoplist.size() - 1; - best_dropoff_idx = stoplist.size() - 1; + for (int counter_drop_off = best_pickup_idx; + counter_drop_off < stoplist.size() - 1; counter_drop_off++) { + // printf("Counter drop off: %i\n", counter_drop_off); + Stop stop_before_dropoff = stoplist[counter_drop_off]; + Stop stop_after_dropoff = stoplist[counter_drop_off + 1]; + if (counter_drop_off == counter) { continue; } + auto listResultInBetweenTestDropOff = + is_between(space, request->destination, stop_before_dropoff, + stop_after_dropoff); + // Changed from 2 to 1 + + if (std::get<0>(listResultInBetweenTestDropOff) == true && + std::get<2>(listResultInBetweenTestDropOff) != 0) { + best_dropoff_idx = counter_drop_off; + double time_to_dropoff = + space.t(stop_before_dropoff.location, request->destination); + double CPAT_do = + cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); + std::vector> stoplist_request_in_vehicle = { + stoplist.begin() + best_pickup_idx, + stoplist.begin() + best_dropoff_idx + 1}; - if (boolDropOffEnroute == false){ - // printf("boolDropOffEnroute"); - // printf("\n"); - best_dropoff_idx = stoplist.size() - 1; - Stop stop_before_dropoff = stoplist[best_dropoff_idx]; - double time_to_dropoff = space.t(stop_before_dropoff.location, request->destination); - double CPAT_do = cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); - std::vector>stoplist_request_in_vehicle = {stoplist.begin() + best_pickup_idx, stoplist.begin() + best_dropoff_idx + 1}; - // printf("Stoplist Request In Vehicle: "); // for (auto i: stoplist_request_in_vehicle) { // std::cout << i.location << ' '; @@ -502,30 +465,94 @@ InsertionResultminimal_passenger_travel_time_dispatcher( // printf("\n"); std::vector occupancies_ausschnitt; - std::transform(stoplist_request_in_vehicle.begin(), stoplist_request_in_vehicle.end(), std::back_inserter(occupancies_ausschnitt), [](auto x){return x.occupancy_after_servicing;}); - + std::transform(stoplist_request_in_vehicle.begin(), + stoplist_request_in_vehicle.end(), + std::back_inserter(occupancies_ausschnitt), + [](auto x) { return x.occupancy_after_servicing; }); + // printf("Occupancies: "); // for (auto i: occupancies_ausschnitt) { // std::cout << i << ' '; // } // printf("\n"); - - if (std::count(occupancies_ausschnitt.begin(), occupancies_ausschnitt.end(), seat_capacity)) { + + if (std::count(occupancies_ausschnitt.begin(), + occupancies_ausschnitt.end(), seat_capacity)) { // printf("True \n"); - best_pickup_idx = stoplist.size() - 1; - best_dropoff_idx = stoplist.size() - 1; - continue; + boolContinuePickUpLoop = true; + break; } if (CPAT_do > request->delivery_timewindow_max) { - best_pickup_idx = stoplist.size() - 1; - best_dropoff_idx = stoplist.size() - 1; - continue; + boolContinuePickUpLoop = true; + break; } boolInsertEnd = false; min_cost = CPAT_do; + boolDropOffEnroute = true; + boolBreakPickUpLoop = true; break; } + } + + if (boolBreakPickUpLoop == true) { + break; + } + + if (boolContinuePickUpLoop == true) { + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + + if (boolDropOffEnroute == false) { + // printf("boolDropOffEnroute"); + // printf("\n"); + best_dropoff_idx = stoplist.size() - 1; + Stop stop_before_dropoff = stoplist[best_dropoff_idx]; + double time_to_dropoff = + space.t(stop_before_dropoff.location, request->destination); + double CPAT_do = + cpat_of_inserted_stop(stop_before_dropoff, time_to_dropoff); + std::vector> stoplist_request_in_vehicle = { + stoplist.begin() + best_pickup_idx, + stoplist.begin() + best_dropoff_idx + 1}; + + // printf("Stoplist Request In Vehicle: "); + // for (auto i: stoplist_request_in_vehicle) { + // std::cout << i.location << ' '; + // } + // printf("\n"); + + std::vector occupancies_ausschnitt; + std::transform(stoplist_request_in_vehicle.begin(), + stoplist_request_in_vehicle.end(), + std::back_inserter(occupancies_ausschnitt), + [](auto x) { return x.occupancy_after_servicing; }); + + // printf("Occupancies: "); + // for (auto i: occupancies_ausschnitt) { + // std::cout << i << ' '; + // } + // printf("\n"); + + if (std::count(occupancies_ausschnitt.begin(), + occupancies_ausschnitt.end(), seat_capacity)) { + // printf("True \n"); + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + + if (CPAT_do > request->delivery_timewindow_max) { + best_pickup_idx = stoplist.size() - 1; + best_dropoff_idx = stoplist.size() - 1; + continue; + } + boolInsertEnd = false; + min_cost = CPAT_do; + break; + } } else { continue; } @@ -537,17 +564,19 @@ InsertionResultminimal_passenger_travel_time_dispatcher( // printf("\n"); best_pickup_idx = stoplist.size() - 1; best_dropoff_idx = stoplist.size() - 1; - double time_to_pickup = space.t(stoplist[best_pickup_idx].location, request->origin); - double CPAT_pu = cpat_of_inserted_stop(stoplist[best_pickup_idx], time_to_pickup); + double time_to_pickup = + space.t(stoplist[best_pickup_idx].location, request->origin); + double CPAT_pu = + cpat_of_inserted_stop(stoplist[best_pickup_idx], time_to_pickup); double EAST_pu = request->pickup_timewindow_min; - double CPAT_do = max(EAST_pu, CPAT_pu) + space.t(request->origin, request->destination); + double CPAT_do = + max(EAST_pu, CPAT_pu) + space.t(request->origin, request->destination); if (CPAT_pu > request->pickup_timewindow_max) { min_cost = INFINITY; - } else{ + } else { if (CPAT_do > request->delivery_timewindow_max) { min_cost = INFINITY; - } - else{ + } else { min_cost = CPAT_do; } } @@ -575,7 +604,7 @@ InsertionResultminimal_passenger_travel_time_dispatcher( auto EAST_do = new_stoplist[best_dropoff_idx + 2].time_window_min; auto LAST_do = new_stoplist[best_dropoff_idx + 2].time_window_max; return InsertionResult{new_stoplist, min_cost, EAST_pu, - LAST_pu, EAST_do, LAST_do}; + LAST_pu, EAST_do, LAST_do}; } else { return InsertionResult{{}, min_cost, NAN, NAN, NAN, NAN}; } @@ -597,15 +626,14 @@ class BruteForceTotalTravelTimeMinimizingDispatcher }; template -class MinimalPassengerTravelTimeDispatcher - : public AbstractDispatcher { +class MinimalPassengerTravelTimeDispatcher : public AbstractDispatcher { public: InsertionResult operator()(std::shared_ptr> request, vector> &stoplist, TransportSpace &space, int seat_capacity, bool debug = false) { - return minimal_passenger_travel_time_dispatcher( - request, stoplist, space, seat_capacity, debug); + return minimal_passenger_travel_time_dispatcher(request, stoplist, space, + seat_capacity, debug); } }; diff --git a/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h b/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h index 042f3d7f..03540357 100644 --- a/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h +++ b/src/ridepy/util/dispatchers_cython/cdispatchers_utils.h @@ -221,33 +221,35 @@ bool is_timewindow_violated_dueto_insertion( } template -std::tuple is_between( - TransportSpace &space, const Loc a, Stop &stop_before, Stop &stop_after) { - double dist_to = space.t(stop_before.location, a); - double dist_from = space.t(a, stop_after.location); - double dist_direct = space.t(stop_before.location, stop_after.location); - bool is_inbetween = dist_to + dist_from == dist_direct; - if (is_inbetween) { - return std::make_tuple(true, dist_to, dist_from, dist_direct); - } else { - return std::make_tuple(false, dist_to, dist_from, dist_direct); - } - } +std::tuple +is_between(TransportSpace &space, const Loc a, Stop &stop_before, + Stop &stop_after) { + double dist_to = space.t(stop_before.location, a); + double dist_from = space.t(a, stop_after.location); + double dist_direct = space.t(stop_before.location, stop_after.location); + bool is_inbetween = dist_to + dist_from == dist_direct; + if (is_inbetween) { + return std::make_tuple(true, dist_to, dist_from, dist_direct); + } else { + return std::make_tuple(false, dist_to, dist_from, dist_direct); + } +} template -std::tuple is_between_2( - TransportSpace &space, const Loc a, const Loc u, Stop &stop_after) { - double dist_to = space.t(u, a); - double dist_from = space.t(a, stop_after.location); - double dist_direct = space.t(u, stop_after.location); - bool is_inbetween = dist_to + dist_from == dist_direct; - if (is_inbetween) { - return std::make_tuple(true, dist_to, dist_from, dist_direct); - } else { - return std::make_tuple(false, dist_to, dist_from, dist_direct); - } - } - +std::tuple +is_between_2(TransportSpace &space, const Loc a, const Loc u, + Stop &stop_after) { + double dist_to = space.t(u, a); + double dist_from = space.t(a, stop_after.location); + double dist_direct = space.t(u, stop_after.location); + bool is_inbetween = dist_to + dist_from == dist_direct; + if (is_inbetween) { + return std::make_tuple(true, dist_to, dist_from, dist_direct); + } else { + return std::make_tuple(false, dist_to, dist_from, dist_direct); + } +} + } // namespace ridepy #endif // RIDEPY_CDISPATCHERS_UTILS_H \ No newline at end of file From 5ec817139636fae45b192da415c829c3bc812ac8 Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Tue, 24 Sep 2024 10:56:38 +0200 Subject: [PATCH 5/8] Fixes due to comments (execpt for the dispatcher name) --- src/ridepy/util/dispatchers/ridepooling.py | 88 +++++++++------------- 1 file changed, 36 insertions(+), 52 deletions(-) diff --git a/src/ridepy/util/dispatchers/ridepooling.py b/src/ridepy/util/dispatchers/ridepooling.py index 11afac98..304558b7 100644 --- a/src/ridepy/util/dispatchers/ridepooling.py +++ b/src/ridepy/util/dispatchers/ridepooling.py @@ -417,23 +417,26 @@ def MinimalPassengerTravelTimeDispatcher( seat_capacity: int, ) -> DispatcherSolution: """ - #FIXME Add description + The dispatcher optimizes travel time for each passenger. Once a passenger is assigned, their travel time cannot be increased, meaning detours are not permitted. """ min_cost = np.inf - boolInsertEnd = True + bool_insert_end = True best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 for counter in range(0, len(stoplist) - 1): stop_before_pickup = stoplist[counter] stop_after_pickup = stoplist[counter + 1] - listResultInBetweenTest = is_between( + list_result_in_between_test = is_between( space, request.origin, stop_before_pickup.location, stop_after_pickup.location, ) - if listResultInBetweenTest[0] == True and listResultInBetweenTest[2] != 0: + if ( + list_result_in_between_test[0] == True + and list_result_in_between_test[2] != 0 + ): if stop_before_pickup.occupancy_after_servicing == seat_capacity: continue time_to_pickup = space.t(stop_before_pickup.location, request.origin) @@ -448,26 +451,21 @@ def MinimalPassengerTravelTimeDispatcher( continue best_pickup_idx = counter - boolDropOffEnroute = False - boolContinuePickUpLoop = False - boolBreakPickUpLoop = False - - # Check if drop-off is between pick-up and next stop + bool_drop_off_enroute = False + bool_continue_pick_up_loop = False + bool_break_pick_up_loop = False - listResultInBetweenTestFollowingPickUp = is_between( + list_result_in_between_testFollowingPickUp = is_between( space, request.destination, request.origin, stop_after_pickup.location ) if ( - listResultInBetweenTestFollowingPickUp[0] == True - and listResultInBetweenTestFollowingPickUp[2] != 0 + list_result_in_between_testFollowingPickUp[0] == True + and list_result_in_between_testFollowingPickUp[2] != 0 ): - # Time violation and seat capacity error not possilbe - # As long as now drop-off time restriction is used - # Ganz grosse Skepsis hier ... best_dropoff_idx = counter min_cost = CPAT_pu - boolDropOffEnroute = True - boolInsertEnd = False + bool_drop_off_enroute = True + bool_insert_end = False break for counter_drop_off, ( @@ -476,15 +474,15 @@ def MinimalPassengerTravelTimeDispatcher( ) in enumerate(pairwise(stoplist[best_pickup_idx:])): if counter_drop_off == 0: continue - listResultInBetweenTestDropOff = is_between( + list_result_in_between_testDropOff = is_between( space, request.destination, stop_before_dropoff.location, stop_after_dropoff.location, ) if ( - listResultInBetweenTestDropOff[0] == True - and listResultInBetweenTestDropOff[1] != 0 + list_result_in_between_testDropOff[0] == True + and list_result_in_between_testDropOff[1] != 0 ): best_dropoff_idx = counter + counter_drop_off time_to_dropoff = space.t( @@ -497,34 +495,33 @@ def MinimalPassengerTravelTimeDispatcher( best_pickup_idx : best_dropoff_idx + 1 ] - # occupancies_ausschnitt = list(map(lambda x: x.occupancy_after_servicing, stoplist_request_in_vehicle)) - - occupancies_ausschnitt = [] + list_occupancies_after_servicing = [] for x in stoplist_request_in_vehicle: - occupancies_ausschnitt.append(x.occupancy_after_servicing) + list_occupancies_after_servicing.append( + x.occupancy_after_servicing + ) - if seat_capacity in occupancies_ausschnitt: - boolContinuePickUpLoop = True + if seat_capacity in list_occupancies_after_servicing: + bool_continue_pick_up_loop = True break if CPAT_do > request.delivery_timewindow_max: - # Pick-Up kann direkt fortgesetzt werden, da in diesem Fall auch der Drop-Off am Ende sicher scheitern wird - boolContinuePickUpLoop = True + bool_continue_pick_up_loop = True break - boolInsertEnd = False + bool_insert_end = False min_cost = CPAT_do - boolDropOffEnroute = True - boolBreakPickUpLoop = True + bool_drop_off_enroute = True + bool_break_pick_up_loop = True break - if boolBreakPickUpLoop: + if bool_break_pick_up_loop: break - if boolContinuePickUpLoop: + if bool_continue_pick_up_loop: best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 continue - if not boolDropOffEnroute: + if not bool_drop_off_enroute: best_dropoff_idx = len(stoplist) - 1 stop_before_dropoff = stoplist[-1] time_to_dropoff = space.t( @@ -533,18 +530,15 @@ def MinimalPassengerTravelTimeDispatcher( CPAT_do = cpat_of_inserted_stop( stop_before_dropoff, time_to_dropoff, delta_cpat=0 ) - # Check time violations? - # Hier muss ueberprueft werden, ob druch das einfügen irgendwo die seat capacity verletzt wird stoplist_request_in_vehicle = stoplist[ best_pickup_idx : best_dropoff_idx + 1 ] - # occupancies_ausschnitt = list(map(lambda x: x.occupancy_after_servicing, stoplist_request_in_vehicle)) - occupancies_ausschnitt = [] + list_occupancies_after_servicing = [] for x in stoplist_request_in_vehicle: - occupancies_ausschnitt.append(x.occupancy_after_servicing) + list_occupancies_after_servicing.append(x.occupancy_after_servicing) - if seat_capacity in occupancies_ausschnitt: + if seat_capacity in list_occupancies_after_servicing: best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 continue @@ -552,13 +546,13 @@ def MinimalPassengerTravelTimeDispatcher( best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 continue - boolInsertEnd = False + bool_insert_end = False min_cost = CPAT_do break else: continue - if boolInsertEnd: + if bool_insert_end: best_pickup_idx = len(stoplist) - 1 best_dropoff_idx = len(stoplist) - 1 time_to_pickup = space.t(stoplist[best_pickup_idx].location, request.origin) @@ -592,16 +586,6 @@ def MinimalPassengerTravelTimeDispatcher( listOccupanciesNewStopList = list( map(lambda x: x.occupancy_after_servicing, new_stoplist) ) - for item in listOccupanciesNewStopList: - if item > seat_capacity: - print("Seat capacity error!!") - - for item in new_stoplist[1:]: - request_item = item.request - if item.action.name == "pickup": - if item.estimated_arrival_time > request_item.pickup_timewindow_max: - print("Time violation!!") - continue return min_cost, new_stoplist, (EAST_pu, LAST_pu, EAST_do, LAST_do) else: From ff87400b852dc2d78eaa14cc8a36b7b9bc927040 Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Thu, 21 Nov 2024 16:31:35 +0100 Subject: [PATCH 6/8] Update fleet state for no detour dispatcher only --- src/ridepy/fleet_state.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ridepy/fleet_state.py b/src/ridepy/fleet_state.py index 0dab85e6..4bee9294 100644 --- a/src/ridepy/fleet_state.py +++ b/src/ridepy/fleet_state.py @@ -7,7 +7,7 @@ import operator as op import numpy as np - +import random from abc import ABC, abstractmethod from typing import ( @@ -413,6 +413,7 @@ def _apply_request_solution( Modifies the `.VehicleState` of the vehicle with the least cost inplace. """ + all_solutions = list(all_solutions) ( best_vehicle, min_cost, @@ -423,6 +424,23 @@ def _apply_request_solution( delivery_timewindow_max, ), ) = min(all_solutions, key=op.itemgetter(1)) + all_solutions_equal_min_costs = list( + filter(lambda x: x[1] == min_cost, all_solutions) + ) + + ( + best_vehicle, + min_cost, + ( + pickup_timewindow_min, + pickup_timewindow_max, + delivery_timewindow_min, + delivery_timewindow_max, + ), + ) = random.choice(all_solutions_equal_min_costs) + + # test = list(self.fleet[0].stoplist) + # test1 = list(self.fleet[1].stoplist) logger.debug(f"best vehicle: {best_vehicle}, at min_cost={min_cost}") if min_cost == np.inf: # no solution was found return { From 2979d44aa6fb8abeb2a6bbf1f3cb7b8ec1d5470e Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Wed, 11 Dec 2024 11:22:11 +0100 Subject: [PATCH 7/8] Update dispatcher python --- src/ridepy/util/dispatchers/ridepooling.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ridepy/util/dispatchers/ridepooling.py b/src/ridepy/util/dispatchers/ridepooling.py index 304558b7..cf065187 100644 --- a/src/ridepy/util/dispatchers/ridepooling.py +++ b/src/ridepy/util/dispatchers/ridepooling.py @@ -472,7 +472,9 @@ def MinimalPassengerTravelTimeDispatcher( stop_before_dropoff, stop_after_dropoff, ) in enumerate(pairwise(stoplist[best_pickup_idx:])): - if counter_drop_off == 0: + if counter_drop_off == best_pickup_idx: + continue + if counter_drop_off < best_pickup_idx: continue list_result_in_between_testDropOff = is_between( space, From d7b47fae0857204bd24ee765896bd8f54fb98dfd Mon Sep 17 00:00:00 2001 From: Alex020796 Date: Thu, 12 Dec 2024 11:28:01 +0100 Subject: [PATCH 8/8] Bugfixes after merge with main --- .pre-commit-config.yaml | 2 +- src/ridepy/fleet_state.py | 20 +------------------ .../util/dispatchers_cython/dispatchers.pyx | 2 +- .../util/testing_utils_cython/dispatchers.pyx | 6 +++--- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e03f4b6a..80ebea22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: 24.8.0 hooks: - id: black - language_version: python3.12 + language_version: python3 - repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 hooks: diff --git a/src/ridepy/fleet_state.py b/src/ridepy/fleet_state.py index 4bee9294..0dab85e6 100644 --- a/src/ridepy/fleet_state.py +++ b/src/ridepy/fleet_state.py @@ -7,7 +7,7 @@ import operator as op import numpy as np -import random + from abc import ABC, abstractmethod from typing import ( @@ -413,7 +413,6 @@ def _apply_request_solution( Modifies the `.VehicleState` of the vehicle with the least cost inplace. """ - all_solutions = list(all_solutions) ( best_vehicle, min_cost, @@ -424,23 +423,6 @@ def _apply_request_solution( delivery_timewindow_max, ), ) = min(all_solutions, key=op.itemgetter(1)) - all_solutions_equal_min_costs = list( - filter(lambda x: x[1] == min_cost, all_solutions) - ) - - ( - best_vehicle, - min_cost, - ( - pickup_timewindow_min, - pickup_timewindow_max, - delivery_timewindow_min, - delivery_timewindow_max, - ), - ) = random.choice(all_solutions_equal_min_costs) - - # test = list(self.fleet[0].stoplist) - # test1 = list(self.fleet[1].stoplist) logger.debug(f"best vehicle: {best_vehicle}, at min_cost={min_cost}") if min_cost == np.inf: # no solution was found return { diff --git a/src/ridepy/util/dispatchers_cython/dispatchers.pyx b/src/ridepy/util/dispatchers_cython/dispatchers.pyx index a19502d2..ccef9b5b 100644 --- a/src/ridepy/util/dispatchers_cython/dispatchers.pyx +++ b/src/ridepy/util/dispatchers_cython/dispatchers.pyx @@ -47,7 +47,7 @@ cdef class MinimalPassengerTravelTimeDispatcher(Dispatcher): ) elif loc_type == LocType.INT: self.u_dispatcher.dispatcher_int_ptr = ( - new CMinimalPassengerTravelTimeDispatcher[int]() + new CMinimalPassengerTravelTimeDispatcher[uiloc]() ) else: raise ValueError("This line should never have been reached") diff --git a/src/ridepy/util/testing_utils_cython/dispatchers.pyx b/src/ridepy/util/testing_utils_cython/dispatchers.pyx index f6dd0062..d2b80ee2 100644 --- a/src/ridepy/util/testing_utils_cython/dispatchers.pyx +++ b/src/ridepy/util/testing_utils_cython/dispatchers.pyx @@ -101,7 +101,7 @@ cdef class MinimalPassengerTravelTimeDispatcher: bint debug=False ): cdef InsertionResult[R2loc] insertion_result_r2loc - cdef InsertionResult[int] insertion_result_int + cdef InsertionResult[uiloc] insertion_result_int if self.loc_type == LocType.R2LOC: insertion_result_r2loc = c_minimal_passenger_travel_time_dispatcher[R2loc]( @@ -113,8 +113,8 @@ cdef class MinimalPassengerTravelTimeDispatcher: (insertion_result_r2loc.EAST_pu, insertion_result_r2loc.LAST_pu, insertion_result_r2loc.EAST_do, insertion_result_r2loc.LAST_do) elif self.loc_type == LocType.INT: - insertion_result_int = c_minimal_passenger_travel_time_dispatcher[int]( - dynamic_pointer_cast[CTransportationRequest[int], CRequest[int]](cy_request._ureq._req_int), + insertion_result_int = c_minimal_passenger_travel_time_dispatcher[uiloc]( + dynamic_pointer_cast[CTransportationRequest[uiloc], CRequest[uiloc]](cy_request._ureq._req_int), stoplist.ustoplist._stoplist_int, dereference(space.u_space.space_int_ptr), seat_capacity, debug )