Job Task Order
The Job Task Order feature allows you to directly set the order of serving the jobs inside a tour. When using Job Task Order, you can assign the desired order
value to the tasks in the problem, and in this way, influence the order of serving them in the solution.
The Job Task Order can be set in two ways - when you set the specific unique order
value to each of the job tasks (see JobTask in the Tour Planning API reference), which may result in building the tour in that specific order, based on the constraints. Or set the specific order
value to the group of the job tasks that you expect to be served before the others. In this way, the jobs with the greater order
values will be served after all the jobs with the less order
values, and at the same time, the job groups with similar order
values will be optimized separately. This way of grouping the job tasks can be used for instance when you need to serve all the deliveries first and then the pick-ups, so the vehicle is empty before loading again.
Note that the value that you set for the order
will be considered even if the total cost of the solution found might be higher due to the additional constraints, so you can be sure that the tasks will be served in the specified order.
The Job Task Order value is set in the plan
part of the problem by adding the order
value to the job task as follows:
{
"id": "job_2",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430127
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
To see in practice how a problem with the Job Task Order option will be solved in comparison with the same problem without the Job Task Order, let's consider the examples below.
Problem With Job Task Order Specified
Here we have a vehicle that is supposed to serve 10 jobs. As we would like some of the jobs to be served before the others, we set the order
value as follows: 5 jobs from "job_1"
to "job_5"
- "order":1
, and the rest of the jobs from "job_6"
to "job_10"
- "order":2
. Note that in the same way, you can set the special order to each of the jobs if you would like them to be served in that specific order.
{
"fleet": {
"types": [
{
"id": "Vehicle1",
"profile": "car_4c40316dc25b",
"costs": {
"fixed": 8.0,
"distance": 0.001,
"time": 0.007
},
"shifts": [
{
"start": {
"time": "2022-06-22T08:03:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2022-06-22T16:03:00Z",
"location": {
"lat": 52.53560051953246,
"lng": 13.351359003414167
}
}
}
],
"capacity": [
40
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car_4c40316dc25b"
}
]
},
"plan": {
"jobs": [
{
"id": "job_1",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_2",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430127
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_3",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.44927857909315,
"lng": 13.365028443606981
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_4",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_5",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472363
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_6",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215823
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_7",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.63186486317331,
"lng": 13.29405790954721
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_8",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.61900693527653,
"lng": 13.32574157985448
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_9",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.50926602381726,
"lng": 13.481018586655173
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_10",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155943
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
}
]
}
}
Solution
From this solution, we can see that all the jobs with the "order"
value 1, are served before all the jobs with the "order"
value 2. Besides that, we can notice that the order of serving all the jobs with the "order"
values 1 and 2 were optimised separately and the order of serving the jobs with "order":1
is job_1
, job_2
, job_4
, job_3
, job_5
, and with "order":2
is job_9
, job_10
, job_6
, job_8
, job_7
.
{
"statistic": {
"cost": 234.29200000000003,
"distance": 107894,
"duration": 16914,
"times": {
"driving": 11914,
"serving": 5000,
"waiting": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "Vehicle1_1",
"typeId": "Vehicle1",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2022-06-22T08:03:00Z",
"departure": "2022-06-22T08:03:00Z"
},
"load": [
15
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"time": {
"arrival": "2022-06-22T08:23:36Z",
"departure": "2022-06-22T08:31:56Z"
},
"load": [
18
],
"activities": [
{
"jobId": "job_1",
"type": "pickup"
}
],
"distance": 8837
},
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430129
},
"time": {
"arrival": "2022-06-22T08:53:17Z",
"departure": "2022-06-22T09:01:37Z"
},
"load": [
21
],
"activities": [
{
"jobId": "job_2",
"type": "pickup"
}
],
"distance": 17327
},
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"time": {
"arrival": "2022-06-22T09:18:07Z",
"departure": "2022-06-22T09:26:27Z"
},
"load": [
24
],
"activities": [
{
"jobId": "job_4",
"type": "pickup"
}
],
"distance": 28464
},
{
"location": {
"lat": 52.44927857909315,
"lng": 13.36502844360698
},
"time": {
"arrival": "2022-06-22T09:40:12Z",
"departure": "2022-06-22T09:48:32Z"
},
"load": [
27
],
"activities": [
{
"jobId": "job_3",
"type": "pickup"
}
],
"distance": 37975
},
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472364
},
"time": {
"arrival": "2022-06-22T10:09:24Z",
"departure": "2022-06-22T10:17:44Z"
},
"load": [
30
],
"activities": [
{
"jobId": "job_5",
"type": "pickup"
}
],
"distance": 50897
},
{
"location": {
"lat": 52.50926602381726,
"lng": 13.481018586655171
},
"time": {
"arrival": "2022-06-22T10:31:47Z",
"departure": "2022-06-22T10:40:07Z"
},
"load": [
27
],
"activities": [
{
"jobId": "job_9",
"type": "delivery"
}
],
"distance": 57922
},
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155945
},
"time": {
"arrival": "2022-06-22T10:55:14Z",
"departure": "2022-06-22T11:03:34Z"
},
"load": [
24
],
"activities": [
{
"jobId": "job_10",
"type": "delivery"
}
],
"distance": 65720
},
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215825
},
"time": {
"arrival": "2022-06-22T11:13:07Z",
"departure": "2022-06-22T11:21:27Z"
},
"load": [
21
],
"activities": [
{
"jobId": "job_6",
"type": "delivery"
}
],
"distance": 70841
},
{
"location": {
"lat": 52.61900693527653,
"lng": 13.32574157985448
},
"time": {
"arrival": "2022-06-22T11:53:21Z",
"departure": "2022-06-22T12:01:41Z"
},
"load": [
18
],
"activities": [
{
"jobId": "job_8",
"type": "delivery"
}
],
"distance": 86890
},
{
"location": {
"lat": 52.63186486317331,
"lng": 13.29405790954721
},
"time": {
"arrival": "2022-06-22T12:09:59Z",
"departure": "2022-06-22T12:18:19Z"
},
"load": [
15
],
"activities": [
{
"jobId": "job_7",
"type": "delivery"
}
],
"distance": 90930
},
{
"location": {
"lat": 52.53560051953246,
"lng": 13.351359003414167
},
"time": {
"arrival": "2022-06-22T12:44:54Z",
"departure": "2022-06-22T12:44:54Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 107479
}
],
"statistic": {
"cost": 234.29200000000003,
"distance": 107894,
"duration": 16914,
"times": {
"driving": 11914,
"serving": 5000,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
}
]
}
Problem Without Job Task Order
Below is the same problem as above but without the Job Task Order option added to the jobs.
{
"fleet": {
"types": [
{
"id": "Vehicle1",
"profile": "car_4c40316dc25b",
"costs": {
"fixed": 8.0,
"distance": 0.001,
"time": 0.007
},
"shifts": [
{
"start": {
"time": "2022-06-22T08:03:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2022-06-22T16:03:00Z",
"location": {
"lat": 52.53560051953246,
"lng": 13.351359003414167
}
}
}
],
"capacity": [
40
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car_4c40316dc25b"
}
]
},
"plan": {
"jobs": [
{
"id": "job_1",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_2",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430127
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_3",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.44927857909315,
"lng": 13.365028443606981
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_4",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_5",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472363
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_6",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215823
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_7",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.63186486317331,
"lng": 13.29405790954721
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_8",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.61900693527653,
"lng": 13.32574157985448
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_9",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.50926602381726,
"lng": 13.481018586655173
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
},
{
"id": "job_10",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155943
},
"duration": 500
}
],
"demand": [
3
]
}
]
}
}
]
}
}
Solution
As we can see from the solution when we do not use the Job Task Order option, the final tour is different because the tour is optimized without taking into account Job Task Order constraints. In this way, the final order of serving the jobs in the same tour looks as follows: job_8
, job_7
, job_1
, job_6
, job_10
, job_9
, job_5
, job_3
, job_4
, job_2
. As a result, the total distance, duration and therefore, the total cost of the tour is smaller.
{
"statistic": {
"cost": 205.85899999999998,
"distance": 91928,
"duration": 15133,
"times": {
"driving": 10133,
"serving": 5000,
"waiting": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "Vehicle1_1",
"typeId": "Vehicle1",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2022-06-22T08:03:00Z",
"departure": "2022-06-22T08:03:00Z"
},
"load": [
15
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.61900693527653,
"lng": 13.32574157985448
},
"time": {
"arrival": "2022-06-22T08:29:28Z",
"departure": "2022-06-22T08:37:48Z"
},
"load": [
12
],
"activities": [
{
"jobId": "job_8",
"type": "delivery"
}
],
"distance": 10059
},
{
"location": {
"lat": 52.63186486317331,
"lng": 13.29405790954721
},
"time": {
"arrival": "2022-06-22T08:46:06Z",
"departure": "2022-06-22T08:54:26Z"
},
"load": [
9
],
"activities": [
{
"jobId": "job_7",
"type": "delivery"
}
],
"distance": 19570
},
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"time": {
"arrival": "2022-06-22T09:22:38Z",
"departure": "2022-06-22T09:30:58Z"
},
"load": [
12
],
"activities": [
{
"jobId": "job_1",
"type": "pickup"
}
],
"distance": 32492
},
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215825
},
"time": {
"arrival": "2022-06-22T09:44:52Z",
"departure": "2022-06-22T09:53:12Z"
},
"load": [
9
],
"activities": [
{
"jobId": "job_6",
"type": "delivery"
}
],
"distance": 39517
},
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155945
},
"time": {
"arrival": "2022-06-22T10:03:29Z",
"departure": "2022-06-22T10:11:49Z"
},
"load": [
6
],
"activities": [
{
"jobId": "job_10",
"type": "delivery"
}
],
"distance": 47315
},
{
"location": {
"lat": 52.50926602381726,
"lng": 13.481018586655171
},
"time": {
"arrival": "2022-06-22T10:26:27Z",
"departure": "2022-06-22T10:34:47Z"
},
"load": [
3
],
"activities": [
{
"jobId": "job_9",
"type": "delivery"
}
],
"distance": 52436
},
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472364
},
"time": {
"arrival": "2022-06-22T10:47:18Z",
"departure": "2022-06-22T10:55:38Z"
},
"load": [
6
],
"activities": [
{
"jobId": "job_5",
"type": "pickup"
}
],
"distance": 59058
},
{
"location": {
"lat": 52.44927857909315,
"lng": 13.36502844360698
},
"time": {
"arrival": "2022-06-22T11:15:37Z",
"departure": "2022-06-22T11:23:57Z"
},
"load": [
9
],
"activities": [
{
"jobId": "job_3",
"type": "pickup"
}
],
"distance": 69971
},
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"time": {
"arrival": "2022-06-22T11:40:07Z",
"departure": "2022-06-22T11:48:27Z"
},
"load": [
12
],
"activities": [
{
"jobId": "job_4",
"type": "pickup"
}
],
"distance": 74011
},
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430129
},
"time": {
"arrival": "2022-06-22T12:04:48Z",
"departure": "2022-06-22T12:13:08Z"
},
"load": [
15
],
"activities": [
{
"jobId": "job_2",
"type": "pickup"
}
],
"distance": 89611
},
{
"location": {
"lat": 52.53560051953246,
"lng": 13.351359003414167
},
"time": {
"arrival": "2022-06-22T12:15:13Z",
"departure": "2022-06-22T12:15:13Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 90358
}
],
"statistic": {
"cost": 205.85899999999998,
"distance": 91928,
"duration": 15133,
"times": {
"driving": 10133,
"serving": 5000,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
}
]
}
Problem With Multiple Vehicles
Let's consider an example where we have more than one vehicle to execute jobs with different job tasks order. In the problem below we have 2 vehicles to execute 10 jobs, 6 of those jobs with order
1, and 4 with order
2. The vehicle will operate at different shift times and from one depot.
Problem
{
"fleet": {
"types": [
{
"id": "6d1dc02e19d6",
"profile": "car_1",
"costs": {
"fixed": 8.0,
"distance": 0.002,
"time": 0.009
},
"shifts": [
{
"start": {
"time": "2021-08-27T06:03:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2021-08-27T13:03:00Z",
"location": {
"lat": 52.529953,
"lng": 13.314877
}
}
}
],
"capacity": [
10
],
"amount": 1
},
{
"id": "b47bb0d26e4c",
"profile": "car_2",
"costs": {
"fixed": 13.0,
"distance": 0.002,
"time": 0.006
},
"shifts": [
{
"start": {
"time": "2021-08-27T14:03:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2021-08-27T19:03:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
}
}
],
"capacity": [
40
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car_1"
},
{
"type": "car",
"name": "car_2"
}
]
},
"plan": {
"jobs": [
{
"id": "job_1",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_2",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430127
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_3",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.44927857909315,
"lng": 13.365028443606981
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_4",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_5",
"tasks": {
"pickups": [
{
"places": [
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472363
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_6",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215823
},
"duration": 500
}
],
"demand": [
3
],
"order":1
}
]
}
},
{
"id": "job_7",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.73186486317331,
"lng": 13.59405790954721
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_8",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.91900693527653,
"lng": 13.92574157985448
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_9",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.90926602381726,
"lng": 13.781018586655173
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
},
{
"id": "job_10",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155943
},
"duration": 500
}
],
"demand": [
3
],
"order":2
}
]
}
}
]
}
}
Solution
As we can see from the solution below, one of the vehicles will serve job_2
, job_1
, and job_6
with order
1 first, and after that job_9
, job_8
, job_7
, job_10
with order
2. The rest of the jobs - job_5
, job_3
, and job_4
- with order
2 will be optimized to be served by another vehicle.
{
"statistic": {
"cost": 697.76,
"distance": 241984,
"duration": 26211,
"times": {
"driving": 21211,
"serving": 5000,
"waiting": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "6d1dc02e19d6_1",
"typeId": "6d1dc02e19d6",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2021-08-27T06:03:00Z",
"departure": "2021-08-27T06:03:00Z"
},
"load": [
9
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.90926602381726,
"lng": 13.781018586655174
},
"time": {
"arrival": "2021-08-27T06:56:53Z",
"departure": "2021-08-27T07:05:13Z"
},
"load": [
6
],
"activities": [
{
"jobId": "job_9",
"type": "delivery"
}
],
"distance": 11909
},
{
"location": {
"lat": 52.91900693527653,
"lng": 13.92574157985448
},
"time": {
"arrival": "2021-08-27T07:26:14Z",
"departure": "2021-08-27T07:34:34Z"
},
"load": [
3
],
"activities": [
{
"jobId": "job_8",
"type": "delivery"
}
],
"distance": 24698
},
{
"location": {
"lat": 52.73186486317331,
"lng": 13.59405790954721
},
"time": {
"arrival": "2021-08-27T08:19:56Z",
"departure": "2021-08-27T08:28:16Z"
},
"load": [
0
],
"activities": [
{
"jobId": "job_7",
"type": "delivery"
}
],
"distance": 35735
},
{
"location": {
"lat": 52.529953,
"lng": 13.314877
},
"time": {
"arrival": "2021-08-27T09:20:22Z",
"departure": "2021-08-27T09:20:22Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 43438
}
],
"statistic": {
"cost": 441.36400000000003,
"distance": 163393,
"duration": 11842,
"times": {
"driving": 10342,
"serving": 1500,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
},
{
"vehicleId": "b47bb0d26e4c_1",
"typeId": "b47bb0d26e4c",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2021-08-27T14:03:00Z",
"departure": "2021-08-27T14:03:00Z"
},
"load": [
6
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.469144533572845,
"lng": 13.497412369472364
},
"time": {
"arrival": "2021-08-27T14:35:54Z",
"departure": "2021-08-27T14:44:14Z"
},
"load": [
9
],
"activities": [
{
"jobId": "job_5",
"type": "pickup"
}
],
"distance": 3350
},
{
"location": {
"lat": 52.44927857909315,
"lng": 13.36502844360698
},
"time": {
"arrival": "2021-08-27T15:07:52Z",
"departure": "2021-08-27T15:16:12Z"
},
"load": [
12
],
"activities": [
{
"jobId": "job_3",
"type": "pickup"
}
],
"distance": 12036
},
{
"location": {
"lat": 52.48998701990294,
"lng": 13.286076137075746
},
"time": {
"arrival": "2021-08-27T15:36:17Z",
"departure": "2021-08-27T15:44:37Z"
},
"load": [
15
],
"activities": [
{
"jobId": "job_4",
"type": "pickup"
}
],
"distance": 18658
},
{
"location": {
"lat": 52.53315878386378,
"lng": 13.352999270430129
},
"time": {
"arrival": "2021-08-27T16:04:46Z",
"departure": "2021-08-27T16:13:06Z"
},
"load": [
18
],
"activities": [
{
"jobId": "job_2",
"type": "pickup"
}
],
"distance": 73933
},
{
"location": {
"lat": 52.58136440229602,
"lng": 13.423236627987324
},
"time": {
"arrival": "2021-08-27T16:37:27Z",
"departure": "2021-08-27T16:45:47Z"
},
"load": [
21
],
"activities": [
{
"jobId": "job_1",
"type": "pickup"
}
],
"distance": 91183
},
{
"location": {
"lat": 52.57604629377083,
"lng": 13.488619313215825
},
"time": {
"arrival": "2021-08-27T17:03:41Z",
"departure": "2021-08-27T17:12:01Z"
},
"load": [
18
],
"activities": [
{
"jobId": "job_6",
"type": "delivery"
}
],
"distance": 128474
},
{
"location": {
"lat": 52.55137844164478,
"lng": 13.509316000155945
},
"time": {
"arrival": "2021-08-27T17:25:49Z",
"departure": "2021-08-27T17:34:09Z"
},
"load": [
15
],
"activities": [
{
"jobId": "job_10",
"type": "delivery"
}
],
"distance": 157410
},
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2021-08-27T18:02:29Z",
"departure": "2021-08-27T18:02:29Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 168272
}
],
"statistic": {
"cost": 256.39599999999996,
"distance": 78591,
"duration": 14369,
"times": {
"driving": 10869,
"serving": 3500,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
}
]
}
Note that in this case, job task order will significantly affect the optimized solution, as if we would have the same problem but without any specific order set, then the tour will be built in a completely different way - job_1
, job_9
, job_8
, job_7
, job_6
, job_10
, job_5
, and job_3
for the vehicle 1, and job_2
, and job_4
for vehicle 2.
Job Task Order And Time Windows
As the job task order is a soft constraint, then we should assume that in some cases it may be omitted because of using some other job constraints that are considered as hard in Tour Planning, such as skills, territories, demand, or time windows for example. Let's build a problem example using job task order for the jobs with time windows to see how it works.
In the problem below we have 10 jobs to be served in different time windows. Some of the jobs are intended to be served in the first half of the day, some in the second, and some can be served during the day. Those constraints look quite simple and the tour in this case would be built quite straightforward considering that the vehicle shift is from 08:47 to 19:47 and therefore includes all the jobs' time windows. But additionally, we have an order constraint here which proclaims order
1 to some of the jobs, and order 2 for others, so here we can see how those would correlate.
The constraints summary will look as follows:
-
job_1_02
and job_2_02
- order
2 and time window from 09:00 to 13:10 -
job_3_02
- order
2, time window from 9:00 to 18:10 -
job_4_02
and job_5_02
- order
2, time window from 14.00 to 18:10 -
job_6_01
- order
1, time window from 9:18:10 -
job_7_01
and job_8_01
- order
1, time window from 14:00 to 18:10 -
job_9_01
and job_10_01
- order
1, time window from 09:00 to 13:10
Problem
{
"fleet": {
"types": [
{
"id": "b2147500aa87",
"profile": "car_9b09eea674ae",
"costs": {
"fixed": 7.0,
"distance": 0.001,
"time": 0.005
},
"shifts": [
{
"start": {
"time": "2022-06-22T08:47:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2022-06-22T19:47:00Z",
"location": {
"lat": 52.65532138588243,
"lng": 13.422448025024245
}
}
}
],
"capacity": [
3800
],
"amount": 1
}
],
"profiles": [
{
"type": "car",
"name": "car_9b09eea674ae"
}
]
},
"plan": {
"jobs": [
{
"id": "job_1_O2",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T13:10:00Z"
]
],
"location": {
"lat": 52.4781979393528,
"lng": 13.454404067996537
},
"duration": 600
}
],
"demand": [
1
],
"order":2
}
]
}
},
{
"id": "job_2_O2",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T13:10:00Z"
]
],
"location": {
"lat": 52.5058605159953,
"lng": 13.448619930375902
},
"duration": 500
}
],
"demand": [
1
],
"order":2
}
]
}
},
{
"id": "job_3_O2",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.556422006689935,
"lng": 13.342132449499255
},
"duration": 500
}
],
"demand": [
1
],
"order":2
}
]
}
},
{
"id": "job_4_O2",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T14:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.6073501578623,
"lng": 13.466163540647994
},
"duration": 500
}
],
"demand": [
1
],
"order":2
}
]
}
},
{
"id": "job_5_O2",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T14:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.48146637768908,
"lng": 13.299952916343699
},
"duration": 500
}
],
"demand": [
1
],
"order":2
}
]
}
},
{
"id": "job_6_O1",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.45479850835988,
"lng": 13.511041608889517
},
"duration": 500
}
],
"demand": [
1
],
"order":1
}
]
}
},
{
"id": "job_7_O1",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T14:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.4572791183728,
"lng": 13.409100745681675
},
"duration": 500
}
],
"demand": [
1
],
"order":1
}
]
}
},
{
"id": "job_8_O1",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T14:00:00Z",
"2022-06-22T18:10:00Z"
]
],
"location": {
"lat": 52.56938393159874,
"lng": 13.45524111564537
},
"duration": 500
}
],
"demand": [
1
],
"order":1
}
]
}
},
{
"id": "job_9_O1",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T13:10:00Z"
]
],
"location": { "lat": 52.555895660008986,
"lng": 13.308740445937364
},
"duration": 500
}
],
"demand": [
1
],
"order":1
}
]
}
},
{
"id": "job_10_O1",
"tasks": {
"deliveries": [
{
"places": [
{
"times": [
[
"2022-06-22T09:00:00Z",
"2022-06-22T13:10:00Z"
]
],
"location": {
"lat": 52.47292620856057,
"lng": 13.420509939038608
},
"duration": 500
}
],
"demand": [
1
],
"order":1
}
]
}
}
]
}
}
Solution
Analyzing the solution, we can see that the vehicle has served job_9_O1
, job_10_O1
, and job_9_O1
first, as the jobs with the respective time windows and order
1. Then job_1_O2
, job_2_O2
, job_5_O2
, job_3_O2
, and job_4_O2
with order
2 were served under their time windows. And after all, job_7_O1
and job_8_O1
were not included in the tour as their time windows (which is a hard constraint) did not allow them to be served along with the other order
1 jobs. To include those jobs in the tour either their time windows should be extended, or the job task order changed.
{
"statistic": {
"cost": 191.54700000000003,
"distance": 105287,
"duration": 15852,
"times": {
"driving": 10800,
"serving": 4100,
"waiting": 952,
"break": 0
}
},
"tours": [
{
"vehicleId": "b2147500aa87_1",
"typeId": "b2147500aa87",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2022-06-22T08:47:00Z",
"departure": "2022-06-22T11:10:12Z"
},
"load": [
8
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.555895660008986,
"lng": 13.308740445937364
},
"time": {
"arrival": "2022-06-22T11:28:22Z",
"departure": "2022-06-22T11:36:42Z"
},
"load": [
7
],
"activities": [
{
"jobId": "job_9_O1",
"type": "delivery"
}
],
"distance": 8124
},
{
"location": {
"lat": 52.47292620856057,
"lng": 13.420509939038608
},
"time": {
"arrival": "2022-06-22T12:02:49Z",
"departure": "2022-06-22T12:11:09Z"
},
"load": [
6
],
"activities": [
{
"jobId": "job_10_O1",
"type": "delivery"
}
],
"distance": 29625
},
{
"location": {
"lat": 52.45479850835988,
"lng": 13.511041608889515
},
"time": {
"arrival": "2022-06-22T12:29:11Z",
"departure": "2022-06-22T12:37:31Z"
},
"load": [
5
],
"activities": [
{
"jobId": "job_6_O1",
"type": "delivery"
}
],
"distance": 37913
},
{
"location": {
"lat": 52.4781979393528,
"lng": 13.454404067996537
},
"time": {
"arrival": "2022-06-22T12:47:32Z",
"departure": "2022-06-22T12:57:32Z"
},
"load": [
4
],
"activities": [
{
"jobId": "job_1_O2",
"type": "delivery"
}
],
"distance": 46535
},
{
"location": {
"lat": 52.5058605159953,
"lng": 13.448619930375902
},
"time": {
"arrival": "2022-06-22T13:10:00Z",
"departure": "2022-06-22T13:18:20Z"
},
"load": [
3
],
"activities": [
{
"jobId": "job_2_O2",
"type": "delivery"
}
],
"distance": 51559
},
{
"location": {
"lat": 52.48146637768908,
"lng": 13.2999529163437
},
"time": {
"arrival": "2022-06-22T13:44:08Z",
"departure": "2022-06-22T14:08:20Z"
},
"load": [
2
],
"activities": [
{
"jobId": "job_5_O2",
"type": "delivery"
}
],
"distance": 65624
},
{
"location": {
"lat": 52.556422006689935,
"lng": 13.342132449499257
},
"time": {
"arrival": "2022-06-22T14:26:49Z",
"departure": "2022-06-22T14:35:09Z"
},
"load": [
1
],
"activities": [
{
"jobId": "job_3_O2",
"type": "delivery"
}
],
"distance": 78671
},
{
"location": {
"lat": 52.6073501578623,
"lng": 13.466163540647994
},
"time": {
"arrival": "2022-06-22T15:04:40Z",
"departure": "2022-06-22T15:13:00Z"
},
"load": [
0
],
"activities": [
{
"jobId": "job_4_O2",
"type": "delivery"
}
],
"distance": 92173
},
{
"location": {
"lat": 52.65532138588243,
"lng": 13.422448025024243
},
"time": {
"arrival": "2022-06-22T15:34:24Z",
"departure": "2022-06-22T15:34:24Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 103362
}
],
"statistic": {
"cost": 191.54700000000003,
"distance": 105287,
"duration": 15852,
"times": {
"driving": 10800,
"serving": 4100,
"waiting": 952,
"break": 0
}
},
"shiftIndex": 0
}
],
"unassigned": [
{
"jobId": "job_7_O1",
"reasons": [
{
"code": "TIME_WINDOW_CONSTRAINT",
"description": "cannot be visited within time window",
"details": [
{
"vehicleId": "b2147500aa87_1",
"shiftIndex": 0
}
]
}
]
},
{
"jobId": "job_8_O1",
"reasons": [
{
"code": "TIME_WINDOW_CONSTRAINT",
"description": "cannot be visited within time window",
"details": [
{
"vehicleId": "b2147500aa87_1",
"shiftIndex": 0
}
]
}
]
}
]
}
Job Task Order With Relations
In some situations, you may need to model a problem where you have jobs with a specific order, where some job needs to be served at the beginning or the end of the tour. In this case, the reserved activity IDs departure
and arrival
of the sequence relation can be used combined with the job task order constraints.
Below we have a simple problem with 2 vehicles with capacity 2, and 4 jobs with demand 1 each. One job is order
2, and the other 3 jobs have order
1.
-
job_1
- order
2 -
job_2
- order
1 -
job_3
- order
1 -
job_4
- order
1
According to the jobs order and demand and capacity correlation, the job with order
2 would be served the last in one of the tours. But for some reason, job_2
with order
1 is the job that we would like to serve the last in the tour.
Regardless of which order
value is specified for the jobs, if a specific job is required to be served the last in the tour, this constraint should be set with the sequence relation by adding the arrival
reserved activity ID at the end. As relations
is hard constraint compared to order
, then the job specified for arrival will be served as the last one in the tour.
The problem for such constraints will look as follows:
Problem
{
"fleet": {
"types": [
{
"id": "Vehicle_1",
"profile": "car_c145d34adba6",
"costs": {
"fixed": 13.0,
"distance": 0.001,
"time": 0.008
},
"shifts": [
{
"start": {
"time": "2022-06-23T08:10:00Z",
"location": {
"lat": 52.530971,
"lng": 13.384915
}
},
"end": {
"time": "2022-06-23T22:10:00Z",
"location": {
"lat": 52.44411159204712,
"lng": 13.328809259875186
}
},
"breaks": [
{
"duration": 1800,
"times": [
[
"2022-06-23T12:10:00Z",
"2022-06-23T13:10:00Z"
]
]
}
]
}
],
"capacity": [
2
],
"amount": 2
}
],
"profiles": [
{
"type": "car",
"name": "car_c145d34adba6"
}
]
},
"plan": {
"jobs": [
{
"id": "job_1",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.43350418357267,
"lng": 13.45451594339357
},
"duration": 500
}
],
"demand": [
1
],
"order": 2
}
]
}
},
{
"id": "job_2",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.604439965100795,
"lng": 13.31543988319147
},
"duration": 500
}
],
"demand": [
1
],
"order": 1
}
]
}
},
{
"id": "job_3",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.57066638953007,
"lng": 13.428175528293371
},
"duration": 500
}
],
"demand": [
1
],
"order": 1
}
]
}
},
{
"id": "job_4",
"tasks": {
"deliveries": [
{
"places": [
{
"location": {
"lat": 52.611818948643425,
"lng": 13.430786777678113
},
"duration": 500
}
],
"demand": [
1
],
"order": 1
}
]
}
}
],
"relations": [
{
"type": "sequence",
"jobs": [
"arrival",
"job_2"
],
"vehicleId": "Vehicle_1_1"
}
]
}
}
Solution
As we can see from the solution, each of the vehicles will serve 2 jobs in a specific order. As we expected from the sequence relations, Vehicle_1
will serve job_2
as the last in the tour.
{
"statistic": {
"cost": 198.434,
"distance": 85898,
"duration": 10817,
"times": {
"driving": 8817,
"serving": 2000,
"waiting": 0,
"break": 0
}
},
"tours": [
{
"vehicleId": "Vehicle_1_1",
"typeId": "Vehicle_1",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2022-06-23T08:10:00Z",
"departure": "2022-06-23T08:10:00Z"
},
"load": [
2
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.611818948643425,
"lng": 13.430786777678112
},
"time": {
"arrival": "2022-06-23T08:32:38Z",
"departure": "2022-06-23T08:40:58Z"
},
"load": [
1
],
"activities": [
{
"jobId": "job_4",
"type": "delivery"
}
],
"distance": 13429
},
{
"location": {
"lat": 52.604439965100795,
"lng": 13.31543988319147
},
"time": {
"arrival": "2022-06-23T09:01:54Z",
"departure": "2022-06-23T09:10:14Z"
},
"load": [
0
],
"activities": [
{
"jobId": "job_2",
"type": "delivery"
}
],
"distance": 23914
},
{
"location": {
"lat": 52.44411159204712,
"lng": 13.328809259875186
},
"time": {
"arrival": "2022-06-23T09:39:36Z",
"departure": "2022-06-23T09:39:36Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 48784
}
],
"statistic": {
"cost": 104.931,
"distance": 48923,
"duration": 5376,
"times": {
"driving": 4376,
"serving": 1000,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
},
{
"vehicleId": "Vehicle_1_2",
"typeId": "Vehicle_1",
"stops": [
{
"location": {
"lat": 52.530971,
"lng": 13.384915
},
"time": {
"arrival": "2022-06-23T08:10:00Z",
"departure": "2022-06-23T08:10:00Z"
},
"load": [
2
],
"activities": [
{
"jobId": "departure",
"type": "departure"
}
],
"distance": 0
},
{
"location": {
"lat": 52.57066638953007,
"lng": 13.428175528293371
},
"time": {
"arrival": "2022-06-23T08:24:35Z",
"departure": "2022-06-23T08:32:55Z"
},
"load": [
1
],
"activities": [
{
"jobId": "job_3",
"type": "delivery"
}
],
"distance": 7075
},
{
"location": {
"lat": 52.43350418357267,
"lng": 13.45451594339357
},
"time": {
"arrival": "2022-06-23T09:10:47Z",
"departure": "2022-06-23T09:19:07Z"
},
"load": [
0
],
"activities": [
{
"jobId": "job_1",
"type": "delivery"
}
],
"distance": 26603
},
{
"location": {
"lat": 52.44411159204712,
"lng": 13.328809259875186
},
"time": {
"arrival": "2022-06-23T09:40:41Z",
"departure": "2022-06-23T09:40:41Z"
},
"load": [
0
],
"activities": [
{
"jobId": "arrival",
"type": "arrival"
}
],
"distance": 37124
}
],
"statistic": {
"cost": 93.503,
"distance": 36975,
"duration": 5441,
"times": {
"driving": 4441,
"serving": 1000,
"waiting": 0,
"break": 0
}
},
"shiftIndex": 0
}
]
}