2

I am trying to implement stacking orders

While the most optimal solution would be to consider picking up orders from nearby restaurants that have similar food prep time AND nearby delivery locations.

I'd like to start off with something slightly easier - that is stacking orders only from the SAME restaurants that have similar food prep time to multiple deliver points. (Deliveroo example: https://riders.deliveroo.com.sg/en/tech-round-up-stacking-orders)

The scale is about 200k orders hourly and 5000 riders, 5000 restaurants. Run time is important here ~ ideally less than 1 minute (on demand service)

What I have in mind is this:

(1) Collect orders per few minutes interval and sort all orders by their prep time O(nlogn)

(2) group orders by restaurant O(n)

(3) Starting from the order that has the smallest remaining prep time, look for any orders in the same restaurant within the time window (let's say 3-5 mins), if exists group them as a stack. O(1).

  • locations of delivery points are not considered here to reduce computations - most delivery points are within 3km in a given zone.
  • not so interested in global optima for computational time. Picking the order that has the smallest remaining prep time is to avoid considering all combinations for rider - orders permutation matching.

(4) Run simulated annealing for semi-optimal TSP for vehicle routing. (ex. Pickup order A, B, C from the same restaurants -> deliver C to A to B)

I understand for multiple PICKUP and Dropoff the problem would translate into VRPTW - a hard problem to solve in real-time.

A somewhat easier problem - single Pickup and multiple Drop off would there be any better way than what I have in mind right now?

Many thanks in advance.

zcahfg2
  • 861
  • 1
  • 12
  • 27
  • You should be able to solve those TSP instances optimally as a MIP without burning too much in the way of compute. – David Eisenstat Sep 28 '21 at 13:07
  • "The scale is about 200k orders hourly and 5000 riders." How many different restaurants? – ravenspoint Sep 28 '21 at 14:45
  • lets say about 5k restaurants per zone. – zcahfg2 Sep 28 '21 at 21:41
  • @DavidEisenstat isn't MIP NP-hard? If you are saying that stacking numbers has practical limit (< 8 orders to carry at most) and so computing routes for 8 delivery points shouldn't take too long - possibly. But i think that would depend on the size of the graph (of the node network). Which in this particular example would be fairly large even if the graph network only consists of a single zone. (5kmx5km grid ?) – zcahfg2 Sep 28 '21 at 21:52
  • oops typo: node network -> road network. – zcahfg2 Sep 28 '21 at 21:58
  • 1
    @zcahfg2 computing pairwise distances is fast (sublinear in the graph size for each node with the right preprocessing) in a graph that looks like a road network, and the base of the exponential for the TSP MIP is quite gentle in practice. – David Eisenstat Sep 28 '21 at 22:28
  • "5k restaurants per zone" What is a 'zone'? It it the same as whatever generates the 200k orders per hour? – ravenspoint Sep 29 '21 at 14:10
  • Zone is an arbitrary delivery area/polygon (~ 5km 5km size, not necessarily square) where only customer located within the zone could order food from the restaurants in the same zone. And yes, single zone could have 200k during the peak time. – zcahfg2 Sep 30 '21 at 06:48
  • "single zone could have 200k during the peak time" I am still confused by the zone concept. In your comment, you say each zone can generate 200k orders and one restaurant. In another comment, you said there could be 5000 restaurants. So that would mean the systems needs to handle 1 billion orders per hour. Seems unlikely! Please clarify. – ravenspoint Sep 30 '21 at 14:08
  • Apologies I meant one zone could contain up to 5000 restaurants and the sum of all orders within one zone are up to 200k orders per hour. Hope this cleared any confusion you had. We have about 30 zones per major city atm. – zcahfg2 Sep 30 '21 at 15:04
  • That makes sense. I believe I have captured this correctly. Please check https://github.com/JamesBremner/pickup/blob/95a24239d315369598961860aa511fe38135f048/src/pickup.cpp#L31-L61 – ravenspoint Oct 01 '21 at 14:04
  • "Run time is important here ~ ideally less than 1 minute" Is this for one zone, or for all 30 zones? – ravenspoint Oct 01 '21 at 14:05
  • It is for the entire zone but since its processed in parallel we can say its for one zone. – zcahfg2 Oct 03 '21 at 15:03

1 Answers1

2

I have implemented your algorithm

(1) Collect orders per few minutes interval and sort all orders by their prep time

(2) group orders by restaurant

(3) Starting from the order that has the smallest remaining prep time, look for any orders in the same restaurant within the time window (let's say 3-5 mins), if exists group them as a stack.

Running my implementation with a configuration as follows

theConfig.OrdersPerHour = 20000;        // incoming order per hour
theConfig.GroupTimeMins = 5;            // order collection time
theConfig.ResterauntCount = 1000;       // number of resteraunts
theConfig.PickupWindowMins = 5;         // pickup window time
theConfig.MaxPrepTimeMins = 15;         // maximum order preparation time

815 order stacks are generated in about 1/3 second

C:\Users\James\code\pickup\bin>pickup.exe
Pickup
815 order stacks created
raven::set::cRunWatch code timing profile
Calls           Mean (secs)     Total           Scope
       1        0.329374        0.329374        stack

You can inspect the code at https://github.com/JamesBremner/pickup

Note that this time does NOT include the time to generate the drivers' routes. This should not take too long since each driver will be visiting less than half a dozen sites. It depends a lot on the size of the graph being searched. If you can partition the graph around each restaurant, then it will go very quickly. If each search can be completed in a third of a second and the searching is done in series then you will need 5 mins to perform 800 routes.

As an initial experiment I have assumed:

  1. Partition search graph to 5km by 5km with restaurant at center
  2. Manhattan distances
  3. Deliveries to furthest corners, or near to furthest corners

Using the following input to the Pathfinder travelling salesman implementation.

format sales
manhatten 0 0 0
c 0.0 0.0 rest
c 2.5 2.5 topright
c -2.5 2.5 topleft
c 2.5 -2.5 bottomright
c 2 2 neartopright

gives

enter image description here

This takes 0.4 milliseconds.

pickup timer test
manhatten 0 0 0
c 0.0 0.0 rest
c 2.5 2.5 topright
c -2.5 2.5 topleft
c 2.5 -2.5 bottomright
c 2 2 neartopright

route rest -> topright -> neartopright -> topleft -> bottomright -> rest ->
raven::set::cRunWatch code timing profile
Calls           Mean (secs)     Total           Scope
       1        0.0003218       0.0003218       TravellingSalesManCalculation
       1        2.23e-05        2.23e-05        CalculateManhattenDistances

For 800 order stacks that is 1/3 second when processed in series. Adding the order stacking time shown above, gives total calculation time of less than a second. You will have to add the time taken to receive the order data from your server and then send the routes to the drivers which will depend on your server and network, but I would guess you will need just a few more seconds. ( You still haven't posted your runtime requirement!!! )

Note: I am assuming that all that the drivers need is a list of delivery locations in optimal order, when they can use their own GPS device to find the detailed route to the next delivery. If this is not the case, and the drivers need detailed routing ( left right, second left ... ) then this will take longer. Please let me know how you want this to work.

I have increased the number of restaurants to 5000

C:\Users\James\code\pickup\bin>pickup.exe
Pickup
Orders per hour                20000
Order collection time mins     5
Restaurants                    5000
Pickup window mins             5
Maximum order preparation mins 15
1416 order stacks created
raven::set::cRunWatch code timing profile
Calls           Mean (secs)     Total           Scope
       1        2.80843         2.80843         stack

Since the order rate has not increased, the number of order per restaurant is reduced and so is the opportuniy to stack orders - result is an significant increase in calculation time.

ravenspoint
  • 19,093
  • 6
  • 57
  • 103
  • Thanks, I will have a close look at the code once I go work. For the graph - ideally it would be a bidirected graph of a road network (can trim down highways as the delivery vehicle types are mostly motorcycle, bike, and walking to reduce computation) To construct a road graph we can use raw shapefiles such as this (http://download.geofabrik.de/europe/great-britain/england.html). Since the London map is quite large - for this particular example we can trim the network by 5kmx5km (single-zone size). – zcahfg2 Sep 28 '21 at 22:07
  • If TSP computation on real road network is too slow to use for real-time on-demand service. I'd have to use manhattan distance instead (ditching road network altogether) and create simple network consisting of restaurant, delivery points as the nodes only. But with SA to run heuristics on "cropped" road network it should really be possible. At least Uber pooling is able to pull it off. – zcahfg2 Sep 28 '21 at 22:09
  • Please specify your time constraint. How soon after all the orders have been registered do you need all the routes to be calculated? You also need to specify how many threads you can run and the other parameters in `theConfig.*` – ravenspoint Sep 29 '21 at 12:33
  • "I will have a close look at the code" Any comments or suggestions? – ravenspoint Sep 29 '21 at 14:06
  • Proposal: as an initial estimate, partition the search grid around each restaurant to a 5km by 5km Manhattan grid with 3 blocks per km and every restaurant and delivery point at the nearest intersection. – ravenspoint Sep 29 '21 at 14:21
  • Thank you, time constraint for calculation would ideally be 1 minute for a set of orders. If one minute isn't suffice - I would postpone calculating any orders that still have long remaining prep time. (let's say ~10 min) – zcahfg2 Sep 30 '21 at 06:50
  • As of now we are using python as our backend so single thread per zone for now, unfortunately. but we could switch to golang later should numpy + cpython combination isn't sufficient. Still looking at the code now. – zcahfg2 Sep 30 '21 at 06:56
  • Okay, I think PickupOrders, FindRestFirstNextPickup could be slightly more efficient with hashmap? The 1 minute window i mentioned above corresponds to GroupTimeMins in your code. Since new order collection are formed the previous calculation must be finished before the new order collection calculation begins. – zcahfg2 Sep 30 '21 at 09:23
  • This is only a part of the whole calculation to be done under GroupTimeMins time. After the stack group formation, we have orders-rider matching to do: including stacks and non stacked order - which is another expensive operation. – zcahfg2 Sep 30 '21 at 09:23
  • If we have orderMap[restaurant_id] = [sorted_order_by_my_time] we would remove many of the if-else statment in the code – zcahfg2 Sep 30 '21 at 09:27
  • 1
    " time constraint for calculation would ideally be 1 minute for a set of orders." My experiments have shown that this is easily achieved. This is your most important constraint - why is it not in your question? You should edit your question to include it, rather than leaving it buried in a comment. – ravenspoint Sep 30 '21 at 13:18
  • 1
    "we are using python " Bad idea if calculation time is important to you. Python is slow, slow, slow, ... – ravenspoint Sep 30 '21 at 13:19
  • "we have orders-rider matching to do" You did not mention this in your question. Please provide some details of this problem. In particular, why cannot you not assign the first rider to the first stack and so on? – ravenspoint Sep 30 '21 at 13:22
  • "If we have orderMap[restaurant_id] = [sorted_order_by_my_time] we would remove many of the if-else statement in the code" Maybe so, but it would run more slowly. – ravenspoint Sep 30 '21 at 13:23
  • I have added an issue for discussion of the order-rider matching feature. https://github.com/JamesBremner/pickup/issues/1 Please log into github and add a precise description of this problem. – ravenspoint Sep 30 '21 at 13:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237671/discussion-between-zcahfg2-and-ravenspoint). – zcahfg2 Sep 30 '21 at 14:46