Skip to content

Commit

Permalink
Add detours to the merged response.
Browse files Browse the repository at this point in the history
For shipments picked up/delivered directly, the detours are taken from the
global model and they have the usual meaning.
For shipments delivered from a parking location, the detour is computed as the
sum of the detour in the global model (the extra time spent globally on the way
to the parking) + the detour in the local model (the extra time spent locally
in the parking).
For the virtual visits, the detours are defined as:
- for the arrival, it is the time difference compared to driving straight to
  the parking (the detour of the parking visit in the global model).
- for the departure, it is the time spent at the parking (the duration of the
  local route).
  • Loading branch information
ondrasej committed Nov 6, 2023
1 parent 215a17e commit 5c5492c
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 53 deletions.
10 changes: 8 additions & 2 deletions python/cfr/json/cfr_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ class ShipmentRoute(TypedDict, total=False):
vehicleIndex: int
vehicleLabel: str

vehicleStartTime: str
vehicleEndTime: str
vehicleStartTime: TimeString
vehicleEndTime: TimeString
vehicleDetour: DurationString

visits: list[Visit]
transitions: list[Transition]
Expand Down Expand Up @@ -466,6 +467,11 @@ def get_visit_request_duration(
return parse_duration_string(visit_request.get("duration"))


def get_visit_detour(visit: Visit) -> datetime.timedelta:
"""Returns the detour of a visit on a route."""
return parse_duration_string(visit.get("detour", "0s"))


def get_global_start_time(model: ShipmentModel) -> datetime.datetime:
"""Returns the global start time of `model`."""
global_start_time = model.get("globalStartTime")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"vehicleLabel": "V001",
"vehicleStartTime": "2023-08-11T08:00:00Z",
"vehicleEndTime": "2023-08-11T16:00:00Z",
"vehicleDetour": "28800s",
"visits": [
{
"startTime": "2023-08-11T08:07:01Z",
Expand Down Expand Up @@ -37,67 +38,80 @@
{
"shipmentIndex": 9,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T13:51:14Z"
"startTime": "2023-08-11T13:51:14Z",
"detour": "20574s"
},
{
"shipmentIndex": 3,
"shipmentLabel": "S004",
"startTime": "2023-08-11T14:00:00Z"
"startTime": "2023-08-11T14:00:00Z",
"detour": "20574s"
},
{
"shipmentIndex": 10,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T14:05:37Z"
"startTime": "2023-08-11T14:05:37Z",
"detour": "863s"
},
{
"shipmentIndex": 11,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T14:05:37Z"
"startTime": "2023-08-11T14:05:37Z",
"detour": "21437s"
},
{
"shipmentIndex": 0,
"shipmentLabel": "S001",
"startTime": "2023-08-11T14:08:11Z"
"startTime": "2023-08-11T14:08:11Z",
"detour": "21437s"
},
{
"shipmentIndex": 1,
"shipmentLabel": "S002",
"startTime": "2023-08-11T14:16:22Z"
"startTime": "2023-08-11T14:16:22Z",
"detour": "21556s"
},
{
"shipmentIndex": 12,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T14:23:29Z"
"startTime": "2023-08-11T14:23:29Z",
"detour": "1072s"
},
{
"shipmentIndex": 13,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T14:23:29Z"
"startTime": "2023-08-11T14:23:29Z",
"detour": "22509s"
},
{
"shipmentIndex": 2,
"shipmentLabel": "S003",
"startTime": "2023-08-11T14:32:15Z"
"startTime": "2023-08-11T14:32:15Z",
"detour": "22509s"
},
{
"shipmentIndex": 14,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T14:37:52Z"
"startTime": "2023-08-11T14:37:52Z",
"detour": "863s"
},
{
"shipmentIndex": 15,
"shipmentLabel": "P002 arrival",
"startTime": "2023-08-11T14:40:52Z"
"startTime": "2023-08-11T14:40:52Z",
"detour": "23372s"
},
{
"shipmentIndex": 6,
"shipmentLabel": "S007",
"startTime": "2023-08-11T14:43:22Z"
"startTime": "2023-08-11T14:43:22Z",
"detour": "23372s"
},
{
"shipmentIndex": 16,
"shipmentLabel": "P002 departure",
"startTime": "2023-08-11T14:48:21Z"
"startTime": "2023-08-11T14:48:21Z",
"detour": "449s"
}
],
"transitions": [
Expand Down Expand Up @@ -339,41 +353,49 @@
"vehicleLabel": "V002",
"vehicleStartTime": "2023-08-11T08:00:00Z",
"vehicleEndTime": "2023-08-11T20:00:00Z",
"vehicleDetour": "43200s",
"visits": [
{
"shipmentIndex": 17,
"shipmentLabel": "P002 arrival",
"startTime": "2023-08-11T08:10:53Z"
"startTime": "2023-08-11T08:10:53Z",
"detour": "0s"
},
{
"shipmentIndex": 7,
"shipmentLabel": "S008",
"startTime": "2023-08-11T08:13:23Z"
"startTime": "2023-08-11T08:13:23Z",
"detour": "0s"
},
{
"shipmentIndex": 18,
"shipmentLabel": "P002 departure",
"startTime": "2023-08-11T08:18:22Z"
"startTime": "2023-08-11T08:18:22Z",
"detour": "449s"
},
{
"shipmentIndex": 19,
"shipmentLabel": "P002 arrival",
"startTime": "2023-08-11T08:19:22Z"
"startTime": "2023-08-11T08:19:22Z",
"detour": "482s"
},
{
"shipmentIndex": 5,
"shipmentLabel": "S006",
"startTime": "2023-08-11T08:21:52Z"
"startTime": "2023-08-11T08:21:52Z",
"detour": "482s"
},
{
"shipmentIndex": 4,
"shipmentLabel": "S005",
"startTime": "2023-08-11T08:24:23Z"
"startTime": "2023-08-11T08:24:23Z",
"detour": "633s"
},
{
"shipmentIndex": 20,
"shipmentLabel": "P002 departure",
"startTime": "2023-08-11T08:29:22Z"
"startTime": "2023-08-11T08:29:22Z",
"detour": "600s"
}
],
"transitions": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,77 +202,92 @@
{
"shipmentIndex": 9,
"shipmentLabel": "P002 arrival",
"startTime": "2023-08-11T15:05:44Z"
"startTime": "2023-08-11T15:05:44Z",
"detour": "0s"
},
{
"shipmentIndex": 7,
"shipmentLabel": "S008",
"startTime": "2023-08-11T15:07:59Z"
"startTime": "2023-08-11T15:07:59Z",
"detour": "0s"
},
{
"shipmentIndex": 6,
"shipmentLabel": "S007",
"startTime": "2023-08-11T15:10:29Z"
"startTime": "2023-08-11T15:10:29Z",
"detour": "0s"
},
{
"shipmentIndex": 4,
"shipmentLabel": "S005",
"startTime": "2023-08-11T15:12:59Z"
"startTime": "2023-08-11T15:12:59Z",
"detour": "0s"
},
{
"shipmentIndex": 10,
"shipmentLabel": "P002 departure",
"startTime": "2023-08-11T15:17:44Z"
"startTime": "2023-08-11T15:17:44Z",
"detour": "720s"
},
{
"shipmentIndex": 11,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T15:17:44Z"
"startTime": "2023-08-11T15:17:44Z",
"detour": "0s"
},
{
"shipmentIndex": 3,
"shipmentLabel": "S004",
"startTime": "2023-08-11T15:26:39Z"
"startTime": "2023-08-11T15:26:39Z",
"detour": "0s"
},
{
"shipmentIndex": 12,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T15:29:47Z"
"startTime": "2023-08-11T15:29:47Z",
"detour": "723s"
},
{
"shipmentIndex": 13,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T15:29:47Z"
"startTime": "2023-08-11T15:29:47Z",
"detour": "0s"
},
{
"shipmentIndex": 0,
"shipmentLabel": "S001",
"startTime": "2023-08-11T15:32:23Z"
"startTime": "2023-08-11T15:32:23Z",
"detour": "0s"
},
{
"shipmentIndex": 1,
"shipmentLabel": "S002",
"startTime": "2023-08-11T15:40:40Z"
"startTime": "2023-08-11T15:40:40Z",
"detour": "0s"
},
{
"shipmentIndex": 14,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T15:45:18Z"
"startTime": "2023-08-11T15:45:18Z",
"detour": "931s"
},
{
"shipmentIndex": 15,
"shipmentLabel": "P001 arrival",
"startTime": "2023-08-11T15:45:18Z"
"startTime": "2023-08-11T15:45:18Z",
"detour": "0s"
},
{
"shipmentIndex": 2,
"shipmentLabel": "S003",
"startTime": "2023-08-11T15:54:13Z"
"startTime": "2023-08-11T15:54:13Z",
"detour": "0s"
},
{
"shipmentIndex": 16,
"shipmentLabel": "P001 departure",
"startTime": "2023-08-11T15:57:21Z"
"startTime": "2023-08-11T15:57:21Z",
"detour": "723s"
}
],
"metrics": {
Expand Down
57 changes: 41 additions & 16 deletions python/cfr/two_step_routing/two_step_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,24 +876,27 @@ def add_merged_transition(
merged_visits: list[cfr_json.Visit] = []
merged_transitions: list[cfr_json.Transition] = []
merged_travel_steps: list[cfr_json.TravelStep] = []
merged_routes.append(
{
"routeTotalCost": global_route["routeTotalCost"],
"transitions": merged_transitions,
"travelSteps": merged_travel_steps,
"vehicleEndTime": global_route["vehicleEndTime"],
"vehicleIndex": global_route.get("vehicleIndex", 0),
"vehicleLabel": global_route["vehicleLabel"],
"vehicleStartTime": global_route["vehicleStartTime"],
"visits": merged_visits,
# TODO(ondrasej): metrics, detailed costs, ...
}
)
merged_route: cfr_json.ShipmentRoute = {
"routeTotalCost": global_route["routeTotalCost"],
"transitions": merged_transitions,
"travelSteps": merged_travel_steps,
"vehicleEndTime": global_route["vehicleEndTime"],
"vehicleIndex": global_route.get("vehicleIndex", 0),
"vehicleLabel": global_route["vehicleLabel"],
"vehicleStartTime": global_route["vehicleStartTime"],
"visits": merged_visits,
# TODO(ondrasej): metrics, detailed costs, ...
}

# Copy breaks from the global route, if present.
global_breaks = global_route.get("breaks")
if global_breaks is not None:
merged_routes[-1]["breaks"] = global_breaks
if (global_breaks := global_route.get("breaks")) is not None:
merged_route["breaks"] = global_breaks

# Copy vehicle detour from the global route, if present.
if (global_detour := global_route.get("vehicleDetour")) is not None:
merged_route["vehicleDetour"] = global_detour

merged_routes.append(merged_route)

def add_parking_location_shipment(
parking: ParkingLocation, arrival: bool
Expand Down Expand Up @@ -922,6 +925,7 @@ def add_parking_location_shipment(
vehicle=global_vehicle,
)
global_visit_label = global_visit["shipmentLabel"]
global_visit_detour = cfr_json.get_visit_detour(global_visit)
visit_type, index = _parse_global_shipment_label(global_visit_label)
match visit_type:
case "s":
Expand Down Expand Up @@ -953,6 +957,10 @@ def add_parking_location_shipment(
"shipmentIndex": arrival_shipment_index,
"shipmentLabel": arrival_shipment["label"],
"startTime": global_visit["startTime"],
# NOTE(ondrasej): The detour of the parking arrival visit is the
# difference from a plan where the vehicle drives directly to
# this parking location.
"detour": cfr_json.as_duration_string(global_visit_detour),
})

# Transfer all visits and transitions from the local route. Update
Expand All @@ -976,12 +984,22 @@ def add_parking_location_shipment(
shipment_index = _get_shipment_index_from_local_route_visit(
local_visit
)
local_visit_detour = cfr_json.get_visit_detour(local_visit)
merged_visit: cfr_json.Visit = {
"shipmentIndex": shipment_index,
"shipmentLabel": self._shipments[shipment_index]["label"],
"startTime": cfr_json.update_time_string(
local_visit["startTime"], local_to_global_delta
),
# NOTE(ondrasej): The computation of the detour works with the
# assumption that all visits on the local route are for
# delivery-only shipments. The sum of the local and global
# detours is equivalent to the detour from a route where the
# vehicle drivers straight to the current parking location and
# where the driver then goes directly to this visit.
"detour": cfr_json.as_duration_string(
global_visit_detour + local_visit_detour
),
}
merged_visits.append(merged_visit)

Expand All @@ -1002,12 +1020,19 @@ def add_parking_location_shipment(
departure_shipment_index, departure_shipment = (
add_parking_location_shipment(parking, arrival=False)
)
local_route_duration = cfr_json.parse_duration_string(
local_route["metrics"]["totalDuration"]
)
merged_visits.append({
"shipmentIndex": departure_shipment_index,
"shipmentLabel": departure_shipment["label"],
"startTime": cfr_json.update_time_string(
local_route["vehicleEndTime"], local_to_global_delta
),
# NOTE(ondrasej): The detour of the parking departure visit is
# the time spent in the parking (the delta between the arrival
# to the parking and the departure from the parking).
"detour": cfr_json.as_duration_string(local_route_duration),
})
case _:
raise ValueError(f"Unexpected visit type: '{visit_type}'")
Expand Down

0 comments on commit 5c5492c

Please sign in to comment.