From c0e86ccd2dba51602fe454e2963ff12855348813 Mon Sep 17 00:00:00 2001 From: reinterpretcat Date: Sat, 26 Aug 2023 11:22:52 +0200 Subject: [PATCH] Update docs and tour compactness feature --- CHANGELOG.md | 2 +- .../src/examples/pragmatic/basics/recharge.md | 4 +- .../basics/recharge.basic.problem.json | 484 ++++++++++++ .../basics/recharge.basic.solution.json | 664 ++++++++++++++++ .../basics/recharge.solution.geojson | 728 ++++++++++++++++++ .../construction/features/tour_compactness.rs | 13 +- 6 files changed, 1888 insertions(+), 7 deletions(-) create mode 100644 examples/data/pragmatic/basics/recharge.basic.problem.json create mode 100644 examples/data/pragmatic/basics/recharge.basic.solution.json create mode 100644 examples/data/pragmatic/basics/recharge.solution.geojson diff --git a/CHANGELOG.md b/CHANGELOG.md index 870eefa9e..39d383014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. ### Added -* recharge stations feature to support basic electric VRP use cases +* `experimental` recharge stations feature to support basic electric VRP use case ### Changed diff --git a/docs/src/examples/pragmatic/basics/recharge.md b/docs/src/examples/pragmatic/basics/recharge.md index b00651cd8..012654e3b 100644 --- a/docs/src/examples/pragmatic/basics/recharge.md +++ b/docs/src/examples/pragmatic/basics/recharge.md @@ -4,8 +4,6 @@ This example demonstrates an **experimental** feature to model a simple scenario a distance limit (10km), so that they are forced to visit charging stations. Each station is defined by specific location, charging duration and time windows. -**TODO**: update examples -
Problem

@@ -30,3 +28,5 @@ charging duration and time windows.

+ +**NOTE**: the feature is on early stage \ No newline at end of file diff --git a/examples/data/pragmatic/basics/recharge.basic.problem.json b/examples/data/pragmatic/basics/recharge.basic.problem.json new file mode 100644 index 000000000..c34b4e807 --- /dev/null +++ b/examples/data/pragmatic/basics/recharge.basic.problem.json @@ -0,0 +1,484 @@ +{ + "plan": { + "jobs": [ + { + "id": "job1", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.55770070807453, + "lng": 13.47831557890163 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job2", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.56043917150327, + "lng": 13.300632258878089 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job3", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.51645925211421, + "lng": 13.310531630245283 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job4", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.48382634966174, + "lng": 13.431958828477603 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job5", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.465611353026226, + "lng": 13.448598436078305 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job6", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.47033706718906, + "lng": 13.319996854028378 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job7", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.55268247887361, + "lng": 13.381458245337722 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job8", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.52515727139781, + "lng": 13.488275599400666 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job9", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.5113790139571, + "lng": 13.508014352789077 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job10", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.50443783915422, + "lng": 13.490517138667146 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job11", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.53728559309784, + "lng": 13.384345955947186 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job12", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.560969920106366, + "lng": 13.347428870365968 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job13", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.46326762540066, + "lng": 13.426293829138386 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job14", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.533357673695875, + "lng": 13.487291894366974 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job15", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.5603208042266, + "lng": 13.459275950620873 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job16", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.52330707366729, + "lng": 13.439791508009296 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job17", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.48354407179593, + "lng": 13.317361234494124 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job18", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.53636687412595, + "lng": 13.396692271118948 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job19", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.46220732686443, + "lng": 13.381890620781645 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + }, + { + "id": "job20", + "deliveries": [ + { + "places": [ + { + "location": { + "lat": 52.52862953792047, + "lng": 13.434017943525484 + }, + "duration": 300.0 + } + ], + "demand": [ + 1 + ] + } + ] + } + ] + }, + "fleet": { + "vehicles": [ + { + "typeId": "vehicle", + "vehicleIds": [ + "vehicle_1", + "vehicle_2" + ], + "profile": { + "matrix": "car" + }, + "costs": { + "fixed": 25.0, + "distance": 0.0002, + "time": 0.005 + }, + "shifts": [ + { + "start": { + "earliest": "2020-05-01T09:00:00.00Z", + "location": { + "lat": 52.5189, + "lng": 13.4011 + } + }, + "end": { + "latest": "2020-05-01T18:00:00.00Z", + "location": { + "lat": 52.5189, + "lng": 13.4011 + } + }, + "breaks": [ + { + "time": [ + "2020-05-01T12:30:00.00Z", + "2020-05-01T13:00:00.00Z" + ], + "places": [ + { + "duration": 3600.0 + } + ] + } + ], + "recharges": { + "maxDistance": 10000, + "stations": [ + { + "location": { + "lat": 52.5459, + "lng": 13.5058 + }, + "duration": 900 + }, + { + "location": { + "lat": 52.5502, + "lng": 13.3975 + }, + "duration": 900 + }, + { + "location": { + "lat": 52.5204, + "lng": 13.3243 + }, + "duration": 900 + }, + { + "location": { + "lat": 52.4936, + "lng": 13.4076 + }, + "duration": 900 + }, + { + "location": { + "lat": 52.5064, + "lng": 13.5011 + }, + "duration": 900 + } + ] + } + } + ], + "capacity": [ + 10 + ] + } + ], + "profiles": [ + { + "name": "car" + } + ] + } +} diff --git a/examples/data/pragmatic/basics/recharge.basic.solution.json b/examples/data/pragmatic/basics/recharge.basic.solution.json new file mode 100644 index 000000000..db4d24417 --- /dev/null +++ b/examples/data/pragmatic/basics/recharge.basic.solution.json @@ -0,0 +1,664 @@ +{ + "statistic": { + "cost": 134.25099999999998, + "distance": 56055, + "duration": 14608, + "times": { + "driving": 5608, + "serving": 9000, + "waiting": 0, + "break": 0, + "commuting": 0, + "parking": 0 + } + }, + "tours": [ + { + "vehicleId": "vehicle_1", + "typeId": "vehicle", + "shiftIndex": 0, + "stops": [ + { + "location": { + "lat": 52.5189, + "lng": 13.4011 + }, + "time": { + "arrival": "2020-05-01T09:00:00Z", + "departure": "2020-05-01T09:00:00Z" + }, + "distance": 0, + "load": [ + 5 + ], + "activities": [ + { + "jobId": "departure", + "type": "departure" + } + ] + }, + { + "location": { + "lat": 52.51645925211421, + "lng": 13.310531630245285 + }, + "time": { + "arrival": "2020-05-01T09:10:14Z", + "departure": "2020-05-01T09:15:14Z" + }, + "distance": 6141, + "load": [ + 4 + ], + "activities": [ + { + "jobId": "job3", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5204, + "lng": 13.3243 + }, + "time": { + "arrival": "2020-05-01T09:16:57Z", + "departure": "2020-05-01T09:31:57Z" + }, + "distance": 7172, + "load": [ + 4 + ], + "activities": [ + { + "jobId": "recharge", + "type": "recharge" + } + ] + }, + { + "location": { + "lat": 52.560969920106366, + "lng": 13.347428870365968 + }, + "time": { + "arrival": "2020-05-01T09:39:55Z", + "departure": "2020-05-01T09:44:55Z" + }, + "distance": 11952, + "load": [ + 3 + ], + "activities": [ + { + "jobId": "job12", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.55268247887361, + "lng": 13.381458245337722 + }, + "time": { + "arrival": "2020-05-01T09:49:03Z", + "departure": "2020-05-01T09:54:03Z" + }, + "distance": 14433, + "load": [ + 2 + ], + "activities": [ + { + "jobId": "job7", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5502, + "lng": 13.3975 + }, + "time": { + "arrival": "2020-05-01T09:55:55Z", + "departure": "2020-05-01T10:10:55Z" + }, + "distance": 15553, + "load": [ + 2 + ], + "activities": [ + { + "jobId": "recharge", + "type": "recharge" + } + ] + }, + { + "location": { + "lat": 52.53728559309784, + "lng": 13.384345955947186 + }, + "time": { + "arrival": "2020-05-01T10:13:44Z", + "departure": "2020-05-01T10:18:44Z" + }, + "distance": 17244, + "load": [ + 1 + ], + "activities": [ + { + "jobId": "job11", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.53636687412595, + "lng": 13.396692271118948 + }, + "time": { + "arrival": "2020-05-01T10:20:08Z", + "departure": "2020-05-01T10:25:08Z" + }, + "distance": 18086, + "load": [ + 0 + ], + "activities": [ + { + "jobId": "job18", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5189, + "lng": 13.4011 + }, + "time": { + "arrival": "2020-05-01T10:28:25Z", + "departure": "2020-05-01T10:28:25Z" + }, + "distance": 20053, + "load": [ + 0 + ], + "activities": [ + { + "jobId": "arrival", + "type": "arrival" + } + ] + } + ], + "statistic": { + "cost": 55.5356, + "distance": 20053, + "duration": 5305, + "times": { + "driving": 2005, + "serving": 3300, + "waiting": 0, + "break": 0, + "commuting": 0, + "parking": 0 + } + } + }, + { + "vehicleId": "vehicle_2", + "typeId": "vehicle", + "shiftIndex": 0, + "stops": [ + { + "location": { + "lat": 52.5189, + "lng": 13.4011 + }, + "time": { + "arrival": "2020-05-01T09:00:00Z", + "departure": "2020-05-01T09:00:00Z" + }, + "distance": 0, + "load": [ + 10 + ], + "activities": [ + { + "jobId": "departure", + "type": "departure" + } + ] + }, + { + "location": { + "lat": 52.52862953792047, + "lng": 13.434017943525484 + }, + "time": { + "arrival": "2020-05-01T09:04:08Z", + "departure": "2020-05-01T09:09:08Z" + }, + "distance": 2479, + "load": [ + 9 + ], + "activities": [ + { + "jobId": "job20", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5603208042266, + "lng": 13.459275950620873 + }, + "time": { + "arrival": "2020-05-01T09:15:40Z", + "departure": "2020-05-01T09:20:40Z" + }, + "distance": 6399, + "load": [ + 8 + ], + "activities": [ + { + "jobId": "job15", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.55770070807453, + "lng": 13.47831557890163 + }, + "time": { + "arrival": "2020-05-01T09:22:52Z", + "departure": "2020-05-01T09:27:52Z" + }, + "distance": 7720, + "load": [ + 7 + ], + "activities": [ + { + "jobId": "job1", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5459, + "lng": 13.5058 + }, + "time": { + "arrival": "2020-05-01T09:31:40Z", + "departure": "2020-05-01T09:46:40Z" + }, + "distance": 9997, + "load": [ + 7 + ], + "activities": [ + { + "jobId": "recharge", + "type": "recharge" + } + ] + }, + { + "location": { + "lat": 52.533357673695875, + "lng": 13.487291894366974 + }, + "time": { + "arrival": "2020-05-01T09:49:48Z", + "departure": "2020-05-01T09:54:48Z" + }, + "distance": 11873, + "load": [ + 6 + ], + "activities": [ + { + "jobId": "job14", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.52515727139781, + "lng": 13.488275599400666 + }, + "time": { + "arrival": "2020-05-01T09:56:20Z", + "departure": "2020-05-01T10:01:20Z" + }, + "distance": 12788, + "load": [ + 5 + ], + "activities": [ + { + "jobId": "job8", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5113790139571, + "lng": 13.508014352789075 + }, + "time": { + "arrival": "2020-05-01T10:04:43Z", + "departure": "2020-05-01T10:09:43Z" + }, + "distance": 14823, + "load": [ + 4 + ], + "activities": [ + { + "jobId": "job9", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.50443783915422, + "lng": 13.490517138667146 + }, + "time": { + "arrival": "2020-05-01T10:12:05Z", + "departure": "2020-05-01T10:17:05Z" + }, + "distance": 16238, + "load": [ + 3 + ], + "activities": [ + { + "jobId": "job10", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5064, + "lng": 13.5011 + }, + "time": { + "arrival": "2020-05-01T10:18:20Z", + "departure": "2020-05-01T10:33:20Z" + }, + "distance": 16988, + "load": [ + 3 + ], + "activities": [ + { + "jobId": "recharge", + "type": "recharge" + } + ] + }, + { + "location": { + "lat": 52.465611353026226, + "lng": 13.448598436078305 + }, + "time": { + "arrival": "2020-05-01T10:42:57Z", + "departure": "2020-05-01T10:47:57Z" + }, + "distance": 22757, + "load": [ + 2 + ], + "activities": [ + { + "jobId": "job5", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.4936, + "lng": 13.4076 + }, + "time": { + "arrival": "2020-05-01T10:54:55Z", + "departure": "2020-05-01T11:09:55Z" + }, + "distance": 26932, + "load": [ + 2 + ], + "activities": [ + { + "jobId": "recharge", + "type": "recharge" + } + ] + }, + { + "location": { + "lat": 52.48382634966174, + "lng": 13.431958828477605 + }, + "time": { + "arrival": "2020-05-01T11:13:13Z", + "departure": "2020-05-01T11:18:13Z" + }, + "distance": 28909, + "load": [ + 1 + ], + "activities": [ + { + "jobId": "job4", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.52330707366729, + "lng": 13.439791508009296 + }, + "time": { + "arrival": "2020-05-01T11:25:36Z", + "departure": "2020-05-01T11:30:36Z" + }, + "distance": 33336, + "load": [ + 0 + ], + "activities": [ + { + "jobId": "job16", + "type": "delivery" + } + ] + }, + { + "location": { + "lat": 52.5189, + "lng": 13.4011 + }, + "time": { + "arrival": "2020-05-01T11:35:03Z", + "departure": "2020-05-01T11:35:03Z" + }, + "distance": 36002, + "load": [ + 0 + ], + "activities": [ + { + "jobId": "arrival", + "type": "arrival" + } + ] + } + ], + "statistic": { + "cost": 78.71539999999999, + "distance": 36002, + "duration": 9303, + "times": { + "driving": 3603, + "serving": 5700, + "waiting": 0, + "break": 0, + "commuting": 0, + "parking": 0 + } + } + } + ], + "unassigned": [ + { + "jobId": "job17", + "reasons": [ + { + "code": "RECHARGE_CONSTRAINT_CODE", + "description": "cannot be assigned due to recharge constraint", + "details": [ + { + "vehicleId": "vehicle_1", + "shiftIndex": 0 + } + ] + }, + { + "code": "CAPACITY_CONSTRAINT", + "description": "does not fit into any vehicle due to capacity", + "details": [ + { + "vehicleId": "vehicle_2", + "shiftIndex": 0 + } + ] + } + ] + }, + { + "jobId": "job19", + "reasons": [ + { + "code": "RECHARGE_CONSTRAINT_CODE", + "description": "cannot be assigned due to recharge constraint", + "details": [ + { + "vehicleId": "vehicle_1", + "shiftIndex": 0 + } + ] + }, + { + "code": "CAPACITY_CONSTRAINT", + "description": "does not fit into any vehicle due to capacity", + "details": [ + { + "vehicleId": "vehicle_2", + "shiftIndex": 0 + } + ] + } + ] + }, + { + "jobId": "job2", + "reasons": [ + { + "code": "RECHARGE_CONSTRAINT_CODE", + "description": "cannot be assigned due to recharge constraint", + "details": [ + { + "vehicleId": "vehicle_1", + "shiftIndex": 0 + } + ] + }, + { + "code": "CAPACITY_CONSTRAINT", + "description": "does not fit into any vehicle due to capacity", + "details": [ + { + "vehicleId": "vehicle_2", + "shiftIndex": 0 + } + ] + } + ] + }, + { + "jobId": "job13", + "reasons": [ + { + "code": "RECHARGE_CONSTRAINT_CODE", + "description": "cannot be assigned due to recharge constraint", + "details": [ + { + "vehicleId": "vehicle_1", + "shiftIndex": 0 + } + ] + }, + { + "code": "CAPACITY_CONSTRAINT", + "description": "does not fit into any vehicle due to capacity", + "details": [ + { + "vehicleId": "vehicle_2", + "shiftIndex": 0 + } + ] + } + ] + }, + { + "jobId": "job6", + "reasons": [ + { + "code": "RECHARGE_CONSTRAINT_CODE", + "description": "cannot be assigned due to recharge constraint", + "details": [ + { + "vehicleId": "vehicle_1", + "shiftIndex": 0 + } + ] + }, + { + "code": "CAPACITY_CONSTRAINT", + "description": "does not fit into any vehicle due to capacity", + "details": [ + { + "vehicleId": "vehicle_2", + "shiftIndex": 0 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/data/pragmatic/basics/recharge.solution.geojson b/examples/data/pragmatic/basics/recharge.solution.geojson new file mode 100644 index 000000000..431055683 --- /dev/null +++ b/examples/data/pragmatic/basics/recharge.solution.geojson @@ -0,0 +1,728 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:00:00Z", + "departure": "2020-05-01T09:00:00Z", + "distance": "0", + "jobs_ids": "departure", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "warehouse", + "stop_idx": "0", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.4011, + 52.5189 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:10:14Z", + "departure": "2020-05-01T09:15:14Z", + "distance": "6141", + "jobs_ids": "job3", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "1", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.310531630245285, + 52.51645925211421 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:16:57Z", + "departure": "2020-05-01T09:31:57Z", + "distance": "7172", + "jobs_ids": "recharge", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "charging-station", + "stop_idx": "2", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.3243, + 52.5204 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:39:55Z", + "departure": "2020-05-01T09:44:55Z", + "distance": "11952", + "jobs_ids": "job12", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "3", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.347428870365968, + 52.560969920106366 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:49:03Z", + "departure": "2020-05-01T09:54:03Z", + "distance": "14433", + "jobs_ids": "job7", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "4", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.381458245337722, + 52.55268247887361 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:55:55Z", + "departure": "2020-05-01T10:10:55Z", + "distance": "15553", + "jobs_ids": "recharge", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "charging-station", + "stop_idx": "5", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.3975, + 52.5502 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:13:44Z", + "departure": "2020-05-01T10:18:44Z", + "distance": "17244", + "jobs_ids": "job11", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "6", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.384345955947186, + 52.53728559309784 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:20:08Z", + "departure": "2020-05-01T10:25:08Z", + "distance": "18086", + "jobs_ids": "job18", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "7", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.396692271118948, + 52.53636687412595 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:28:25Z", + "departure": "2020-05-01T10:28:25Z", + "distance": "20053", + "jobs_ids": "arrival", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "warehouse", + "stop_idx": "8", + "tour_idx": "0" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.4011, + 52.5189 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:00:00Z", + "departure": "2020-05-01T09:00:00Z", + "distance": "0", + "jobs_ids": "departure", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "warehouse", + "stop_idx": "0", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.4011, + 52.5189 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:04:08Z", + "departure": "2020-05-01T09:09:08Z", + "distance": "2479", + "jobs_ids": "job20", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "1", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.434017943525484, + 52.52862953792047 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:15:40Z", + "departure": "2020-05-01T09:20:40Z", + "distance": "6399", + "jobs_ids": "job15", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "2", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.459275950620873, + 52.5603208042266 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:22:52Z", + "departure": "2020-05-01T09:27:52Z", + "distance": "7720", + "jobs_ids": "job1", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "3", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.47831557890163, + 52.55770070807453 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:31:40Z", + "departure": "2020-05-01T09:46:40Z", + "distance": "9997", + "jobs_ids": "recharge", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "charging-station", + "stop_idx": "4", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.5058, + 52.5459 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:49:48Z", + "departure": "2020-05-01T09:54:48Z", + "distance": "11873", + "jobs_ids": "job14", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "5", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.487291894366974, + 52.533357673695875 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T09:56:20Z", + "departure": "2020-05-01T10:01:20Z", + "distance": "12788", + "jobs_ids": "job8", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "6", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.488275599400666, + 52.52515727139781 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:04:43Z", + "departure": "2020-05-01T10:09:43Z", + "distance": "14823", + "jobs_ids": "job9", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "7", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.508014352789075, + 52.5113790139571 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:12:05Z", + "departure": "2020-05-01T10:17:05Z", + "distance": "16238", + "jobs_ids": "job10", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "8", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.490517138667146, + 52.50443783915422 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:18:20Z", + "departure": "2020-05-01T10:33:20Z", + "distance": "16988", + "jobs_ids": "recharge", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "charging-station", + "stop_idx": "9", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.5011, + 52.5064 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:42:57Z", + "departure": "2020-05-01T10:47:57Z", + "distance": "22757", + "jobs_ids": "job5", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "10", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.448598436078305, + 52.465611353026226 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T10:54:55Z", + "departure": "2020-05-01T11:09:55Z", + "distance": "26932", + "jobs_ids": "recharge", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "charging-station", + "stop_idx": "11", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.4076, + 52.4936 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T11:13:13Z", + "departure": "2020-05-01T11:18:13Z", + "distance": "28909", + "jobs_ids": "job4", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "12", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.431958828477605, + 52.48382634966174 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T11:25:36Z", + "departure": "2020-05-01T11:30:36Z", + "distance": "33336", + "jobs_ids": "job16", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "marker", + "stop_idx": "13", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.439791508009296, + 52.52330707366729 + ] + } + }, + { + "type": "Feature", + "properties": { + "arrival": "2020-05-01T11:35:03Z", + "departure": "2020-05-01T11:35:03Z", + "distance": "36002", + "jobs_ids": "arrival", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "warehouse", + "stop_idx": "14", + "tour_idx": "1" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.4011, + 52.5189 + ] + } + }, + { + "type": "Feature", + "properties": { + "activities": "9", + "arrival": "2020-05-01T10:28:25Z", + "departure": "2020-05-01T09:00:00Z", + "distance": "20053", + "shift_idx": "0", + "stroke": "#e6194b", + "stroke-width": "4", + "tour_idx": "0", + "vehicle_id": "vehicle_1" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 13.4011, + 52.5189 + ], + [ + 13.310531630245285, + 52.51645925211421 + ], + [ + 13.3243, + 52.5204 + ], + [ + 13.347428870365968, + 52.560969920106366 + ], + [ + 13.381458245337722, + 52.55268247887361 + ], + [ + 13.3975, + 52.5502 + ], + [ + 13.384345955947186, + 52.53728559309784 + ], + [ + 13.396692271118948, + 52.53636687412595 + ], + [ + 13.4011, + 52.5189 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "activities": "15", + "arrival": "2020-05-01T11:35:03Z", + "departure": "2020-05-01T09:00:00Z", + "distance": "36002", + "shift_idx": "0", + "stroke": "#3cb44b", + "stroke-width": "4", + "tour_idx": "1", + "vehicle_id": "vehicle_2" + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 13.4011, + 52.5189 + ], + [ + 13.434017943525484, + 52.52862953792047 + ], + [ + 13.459275950620873, + 52.5603208042266 + ], + [ + 13.47831557890163, + 52.55770070807453 + ], + [ + 13.5058, + 52.5459 + ], + [ + 13.487291894366974, + 52.533357673695875 + ], + [ + 13.488275599400666, + 52.52515727139781 + ], + [ + 13.508014352789075, + 52.5113790139571 + ], + [ + 13.490517138667146, + 52.50443783915422 + ], + [ + 13.5011, + 52.5064 + ], + [ + 13.448598436078305, + 52.465611353026226 + ], + [ + 13.4076, + 52.4936 + ], + [ + 13.431958828477605, + 52.48382634966174 + ], + [ + 13.439791508009296, + 52.52330707366729 + ], + [ + 13.4011, + 52.5189 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "job_id": "job17", + "marker-color": "#e6194b", + "marker-size": "medium", + "marker-symbol": "roadblock", + "reasons": "RECHARGE_CONSTRAINT_CODE:cannot be assigned due to recharge constraint,CAPACITY_CONSTRAINT:does not fit into any vehicle due to capacity" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.317361234494124, + 52.48354407179593 + ] + } + }, + { + "type": "Feature", + "properties": { + "job_id": "job19", + "marker-color": "#3cb44b", + "marker-size": "medium", + "marker-symbol": "roadblock", + "reasons": "CAPACITY_CONSTRAINT:does not fit into any vehicle due to capacity,RECHARGE_CONSTRAINT_CODE:cannot be assigned due to recharge constraint" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.381890620781643, + 52.46220732686443 + ] + } + }, + { + "type": "Feature", + "properties": { + "job_id": "job2", + "marker-color": "#4363d8", + "marker-size": "medium", + "marker-symbol": "roadblock", + "reasons": "CAPACITY_CONSTRAINT:does not fit into any vehicle due to capacity,RECHARGE_CONSTRAINT_CODE:cannot be assigned due to recharge constraint" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.300632258878087, + 52.56043917150327 + ] + } + }, + { + "type": "Feature", + "properties": { + "job_id": "job13", + "marker-color": "#f58231", + "marker-size": "medium", + "marker-symbol": "roadblock", + "reasons": "CAPACITY_CONSTRAINT:does not fit into any vehicle due to capacity,RECHARGE_CONSTRAINT_CODE:cannot be assigned due to recharge constraint" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.426293829138386, + 52.46326762540066 + ] + } + }, + { + "type": "Feature", + "properties": { + "job_id": "job6", + "marker-color": "#911eb4", + "marker-size": "medium", + "marker-symbol": "roadblock", + "reasons": "CAPACITY_CONSTRAINT:does not fit into any vehicle due to capacity,RECHARGE_CONSTRAINT_CODE:cannot be assigned due to recharge constraint" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 13.319996854028378, + 52.47033706718906 + ] + } + } + ] +} \ No newline at end of file diff --git a/vrp-core/src/construction/features/tour_compactness.rs b/vrp-core/src/construction/features/tour_compactness.rs index 45e8192af..66a4231ed 100644 --- a/vrp-core/src/construction/features/tour_compactness.rs +++ b/vrp-core/src/construction/features/tour_compactness.rs @@ -113,11 +113,16 @@ impl FeatureState for TourCompactnessState { fn count_shared_neighbours(item: (&SolutionContext, &RouteContext, &Job), jobs: &Jobs, job_radius: usize) -> usize { let (solution_ctx, route_ctx, job) = item; - let profile = &route_ctx.route().actor.vehicle.profile; - let departure = route_ctx.route().tour.start().map_or(Timestamp::default(), |s| s.schedule.departure); + let route = route_ctx.route(); + let departure = route.tour.start().map_or(Timestamp::default(), |s| s.schedule.departure); - jobs.neighbors(profile, job, departure) + jobs.neighbors(&route.actor.vehicle.profile, job, departure) .take(job_radius) - .filter(|(j, _)| solution_ctx.routes.iter().filter(|rc| *rc != route_ctx).any(|rc| rc.route().tour.has_job(j))) + .filter(|(j, _)| { + let not_current = !route.tour.has_job(j); + let is_assigned = !solution_ctx.required.contains(j); + + not_current && is_assigned + }) .count() }