OptimizeToursRequest
stosuje ograniczenia w tych obszarach:
- Przesyłki, które wpływają na sposób realizacji przesyłek
- pojazdy, które wpływają na sposób obliczania tras pojazdów;
- Wpływa na pojazdy i przesyłki na całym świecie.
W tym przewodniku skupimy się na jednym z najważniejszych ograniczeń dostawy: oknach czasowych.
Okna czasowe to rodzaj ograniczenia, które podajesz w OptimizeToursRequest
wiadomości (REST, gRPC), aby określić limity czasowe dla działań związanych z przesyłką. Ten typ ograniczenia wpływa na to, kiedy i jak można zrealizować dostawę, a także na przypisanie pojazdu do dostawy. W przypadku tych ograniczeń optymalizator preferuje pojazdy, które najlepiej spełniają ograniczenia czasowe dostawy.
Ograniczenia dotyczące przesyłki: przedziały czasu
Wiadomość z informacją o możliwości odbioru lub dostawy możesz określić w ten sposób:Shipment.VisitRequest
- Użyj właściwości
timeWindows
w wiadomości (REST, gRPC). - Określ czas rozpoczęcia i zakończenia w
TimeWindow
wiadomości (REST, gRPC).
Przykładowe żądanie z ograniczeniami dotyczącymi przedziału czasu
Przykład pokazuje 3 różne przesyłki, z których każda ma własne okno dostawy. Dla uproszczenia w tym przykładzie przedziały czasu są ustawione tylko dla deliveries
, ale można je też stosować w przypadku odbiorów. Można określić wiele przedziałów czasowych, ale w tym przykładzie używamy tylko jednego przedziału na dostawę VisitRequest
.
Zobacz przykładowe żądanie z okresami
{ "populatePolylines": false, "populateTransitionPolylines": false, "model": { "globalStartTime": "2023-01-13T16:00:00Z", "globalEndTime": "2023-01-14T16:00:00Z", "shipments": [ { "deliveries": [ { "arrivalLocation": { "latitude": 37.789456, "longitude": -122.390192 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T18:00:00Z", "endTime": "2023-01-13T19:00:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 100.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.789116, "longitude": -122.395080 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T18:00:00Z", "endTime": "2023-01-13T18:30:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 20.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.795242, "longitude": -122.399347 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T17:30:00Z", "endTime": "2023-01-13T18:00:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 50.0 } ], "vehicles": [ { "endLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "startLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "costPerHour": 40.0, "costPerKilometer": 10.0 } ] } }
Przykładowa odpowiedź z ograniczeniami dotyczącymi przedziału czasowego
W przykładzie odpowiedzi czas rozpoczęcia i zakończenia jazdy to odpowiednio 17:35:50 i 18:17:24. Te czasy odzwierciedlają minimalizację czasu przez optymalizator
wymaganego do obsługi pojazdu określonego w żądaniu jako costPerHour
przy jednoczesnym
spełnieniu wszystkich ograniczeń dotyczących przedziałów czasowych. Użycie godziny 17:35:50 jako czasu rozpoczęcia
eliminuje konieczność czekania pojazdu w miejscu wizyty do momentu rozpoczęcia
okna czasowego wizyty. W odpowiedzi pojawia się wartość zero waitDuration
.
Zobacz odpowiedź na przykładowe żądanie z okresami
{ "routes": [ { "vehicleStartTime": "2023-01-13T17:35:50Z", "vehicleEndTime": "2023-01-13T18:17:24Z", "visits": [ { "isPickup": true, "startTime": "2023-01-13T17:35:50Z", "detour": "0s" }, { "shipmentIndex": 1, "isPickup": true, "startTime": "2023-01-13T17:38:20Z", "detour": "150s" }, { "shipmentIndex": 2, "isPickup": true, "startTime": "2023-01-13T17:40:50Z", "detour": "300s" }, { "shipmentIndex": 2, "startTime": "2023-01-13T17:50:09Z", "detour": "0s" }, { "shipmentIndex": 1, "startTime": "2023-01-13T18:00:00Z", "detour": "796s" }, { "startTime": "2023-01-13T18:07:35Z", "detour": "1520s" } ], "transitions": [ { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:35:50Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:38:20Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:40:50Z" }, { "travelDuration": "409s", "travelDistanceMeters": 1371, "waitDuration": "0s", "totalDuration": "409s", "startTime": "2023-01-13T17:43:20Z" }, { "travelDuration": "341s", "travelDistanceMeters": 1312, "waitDuration": "0s", "totalDuration": "341s", "startTime": "2023-01-13T17:54:19Z" }, { "travelDuration": "205s", "travelDistanceMeters": 636, "waitDuration": "0s", "totalDuration": "205s", "startTime": "2023-01-13T18:04:10Z" }, { "travelDuration": "339s", "travelDistanceMeters": 1276, "waitDuration": "0s", "totalDuration": "339s", "startTime": "2023-01-13T18:11:45Z" } ], "metrics": { "performedShipmentCount": 3, "travelDuration": "1294s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "2494s", "travelDistanceMeters": 4595 }, "routeCosts": { "model.vehicles.cost_per_hour": 27.711111111111112, "model.vehicles.cost_per_kilometer": 45.95 }, "routeTotalCost": 73.661111111111111 } ], "metrics": { "aggregatedRouteMetrics": { "performedShipmentCount": 3, "travelDuration": "1294s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "2494s", "travelDistanceMeters": 4595 }, "usedVehicleCount": 1, "earliestVehicleStartTime": "2023-01-13T17:35:50Z", "latestVehicleEndTime": "2023-01-13T18:17:24Z", "totalCost": 73.661111111111111, "costs": { "model.vehicles.cost_per_hour": 27.711111111111112, "model.vehicles.cost_per_kilometer": 45.95 } } }
Okna czasowe mają uporządkowane visits
pojazdu, dzięki czemu przesyłki z najwcześniejszymi oknami czasowymi są dostarczane w pierwszej kolejności.
shipments[2]
dostarczono o 17:50shipments[1]
dostarczono o 18:00shipments[0]
dostarczono o 18:07
Przykładowe żądanie określa sztywne ograniczenia dotyczące przedziału czasowego, co oznacza, że dostawy muszą zostać zrealizowane w tych przedziałach. Jeśli zrealizowanie dostawy VisitRequests
w dowolnym z określonych przedziałów czasowych jest niemożliwe lub nieopłacalne, optymalizator pomija tę dostawę. Jeśli przesyłka ma penaltyCost
, optymalizator dodaje ją do kosztów podanych w odpowiedzimetrics
. W przeciwnym razie zwiększa się właściwość skippedMandatoryShipmentCount
wiadomości OptimizeToursResponse
(REST, gRPC).
Jeśli zmienisz przedział czasowy, przesuwając okno shipment[1]
o kilka godzin (np. z 18:00 na 21:00), wyniki będą inne, co ilustrują poniższe przykłady.
Zobacz przykładowe żądanie z okresami, których nie można spełnić
{ "populatePolylines": false, "populateTransitionPolylines": false, "model": { "globalStartTime": "2023-01-13T16:00:00Z", "globalEndTime": "2023-01-14T16:00:00Z", "shipments": [ { "deliveries": [ { "arrivalLocation": { "latitude": 37.789456, "longitude": -122.390192 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T18:00:00Z", "endTime": "2023-01-13T19:00:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 100.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.789116, "longitude": -122.395080 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T21:00:00Z", "endTime": "2023-01-13T21:30:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 20.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.795242, "longitude": -122.399347 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T17:30:00Z", "endTime": "2023-01-13T18:00:00Z" } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 50.0 } ], "vehicles": [ { "endLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "startLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "costPerHour": 40.0, "costPerKilometer": 10.0 } ] } }
Zobacz odpowiedź na drugie przykładowe żądanie z okresami, w których przesyłka jest pomijana
{ "routes": [ { "vehicleStartTime": "2023-01-13T17:37:49Z", "vehicleEndTime": "2023-01-13T18:09:49Z", "visits": [ { "isPickup": true, "startTime": "2023-01-13T17:37:49Z", "detour": "0s" }, { "shipmentIndex": 2, "isPickup": true, "startTime": "2023-01-13T17:40:19Z", "detour": "150s" }, { "shipmentIndex": 2, "startTime": "2023-01-13T17:49:38Z", "detour": "0s" }, { "startTime": "2023-01-13T18:00:00Z", "detour": "946s" } ], "transitions": [ { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:37:49Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:40:19Z" }, { "travelDuration": "409s", "travelDistanceMeters": 1371, "waitDuration": "0s", "totalDuration": "409s", "startTime": "2023-01-13T17:42:49Z" }, { "travelDuration": "372s", "travelDistanceMeters": 1348, "waitDuration": "0s", "totalDuration": "372s", "startTime": "2023-01-13T17:53:48Z" }, { "travelDuration": "339s", "travelDistanceMeters": 1276, "waitDuration": "0s", "totalDuration": "339s", "startTime": "2023-01-13T18:04:10Z" } ], "metrics": { "performedShipmentCount": 2, "travelDuration": "1120s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "800s", "totalDuration": "1920s", "travelDistanceMeters": 3995 }, "routeCosts": { "model.vehicles.cost_per_kilometer": 39.95, "model.vehicles.cost_per_hour": 21.333333333333332 }, "routeTotalCost": 61.283333333333331 } ], "skippedShipments": [ { "index": 1 } ], "metrics": { "aggregatedRouteMetrics": { "performedShipmentCount": 2, "travelDuration": "1120s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "800s", "totalDuration": "1920s", "travelDistanceMeters": 3995 }, "usedVehicleCount": 1, "earliestVehicleStartTime": "2023-01-13T17:37:49Z", "latestVehicleEndTime": "2023-01-13T18:09:49Z", "totalCost": 81.283333333333331, "costs": { "model.shipments.penalty_cost": 20, "model.vehicles.cost_per_hour": 21.333333333333332, "model.vehicles.cost_per_kilometer": 39.95 } } }
W tym przykładzie późniejszy przedział czasowy spowodował pominięcie shipment[1]
, ponieważ dodatkowy czas pracy pojazdu wymagany do dostarczenia przesyłki w określonym przedziale czasowym przekroczył koszt kary za niedotrzymanie terminu dostawy.
Koszt kary za shipment[1]
jest widoczny w metrics.costs
, a jego indeks – w skippedShipments
.
Ograniczenia dotyczące przedziału czasu
Jak wspomnieliśmy w sekcji Parametry modelu kosztów, okna czasowe można stosować jako łagodne ograniczenia. Ograniczenia miękkie różnią się od ograniczeń twardych w następujący sposób:
- Sztywne ograniczenia: nie można ich naruszać, a optymalizator nie proponuje rozwiązania, które narusza ograniczenie, nawet jeśli oznacza to pominięcie dostawy.
- Ograniczenia elastyczne: mogą zostać naruszone, co oznacza, że optymalizator może podać rozwiązanie, które narusza takie ograniczenie. Optymalizator przypisuje jednak koszt do każdego naruszenia. Podajesz ten koszt jako dodatkową właściwość w okresie, zwykle jako koszt za godzinę w przypadku każdej godziny przed lub po okresie, w którym występuje aktywność.
Okna czasowe są łagodzone przez użycie operatorów softStartTime
lub softEndTime
zamiast odpowiednio startTime
lub endTime
oraz przez ustawienie wartości costPerHourBeforeSoftStartTime
lub costPerHourAfterSoftEndTime
.
Używaj łagodnych ograniczeń dotyczących przedziału czasu, gdy odbiór lub dostawa powinny nastąpić w określonym przedziale czasu, ale nie jest to bezwzględnie wymagane. Możesz używać razem twardych i miękkich ograniczeń czasowych, aby wyrażać cele biznesowe. Na przykład:
- Sztywne okno czasowe: wskazuje godziny pracy klienta, np. od 9:00 do 17:00.
- Miękkie okno czasowe: przedział czasowy dostawy lub odbioru zgodny z powiadomieniem wysłanym do klienta, np. od 9:00 do 13:00.
W tym przykładzie w przypadku przesyłki, która została wcześniej pominięta, ponieważ jej przedział czasowy rozpoczął się zbyt późno, ograniczenie czasu rozpoczęcia zostało złagodzone. W przypadku pozostałych przesyłek również wydłużyliśmy czas dostawy.
Zobacz przykładowe żądanie z określonymi przedziałami czasu
{ "populatePolylines": false, "populateTransitionPolylines": false, "model": { "globalStartTime": "2023-01-13T16:00:00Z", "globalEndTime": "2023-01-14T16:00:00Z", "shipments": [ { "deliveries": [ { "arrivalLocation": { "latitude": 37.789456, "longitude": -122.390192 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T18:00:00Z", "softEndTime": "2023-01-13T19:00:00Z", "costPerHourAfterSoftEndTime": 2.0 } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 100.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.789116, "longitude": -122.395080 }, "duration": "250s", "timeWindows": [ { "softStartTime": "2023-01-13T21:00:00Z", "endTime": "2023-01-13T21:30:00Z", "costPerHourBeforeSoftStartTime": 2.0 } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 20.0 }, { "deliveries": [ { "arrivalLocation": { "latitude": 37.795242, "longitude": -122.399347 }, "duration": "250s", "timeWindows": [ { "startTime": "2023-01-13T17:30:00Z", "softEndTime": "2023-01-13T18:00:00Z", "costPerHourAfterSoftEndTime": 2.0 } ] } ], "pickups": [ { "arrivalLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "duration": "150s" } ], "penaltyCost": 50.0 } ], "vehicles": [ { "endLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "startLocation": { "latitude": 37.794465, "longitude": -122.394839 }, "costPerHour": 40.0, "costPerKilometer": 10.0 } ] } }
Zobacz odpowiedź na przykładowe żądanie z określonymi i elastycznymi przedziałami czasowymi
{ "routes": [ { "vehicleStartTime": "2023-01-13T17:48:35Z", "vehicleEndTime": "2023-01-13T18:24:28Z", "visits": [ { "isPickup": true, "startTime": "2023-01-13T17:48:35Z", "detour": "0s" }, { "shipmentIndex": 1, "isPickup": true, "startTime": "2023-01-13T17:51:05Z", "detour": "150s" }, { "shipmentIndex": 2, "isPickup": true, "startTime": "2023-01-13T17:53:35Z", "detour": "300s" }, { "startTime": "2023-01-13T18:00:00Z", "detour": "300s" }, { "shipmentIndex": 1, "startTime": "2023-01-13T18:07:42Z", "detour": "493s" }, { "shipmentIndex": 2, "startTime": "2023-01-13T18:17:27Z", "detour": "873s" } ], "transitions": [ { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:48:35Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:51:05Z" }, { "travelDuration": "0s", "waitDuration": "0s", "totalDuration": "0s", "startTime": "2023-01-13T17:53:35Z" }, { "travelDuration": "235s", "travelDistanceMeters": 795, "waitDuration": "0s", "totalDuration": "235s", "startTime": "2023-01-13T17:56:05Z" }, { "travelDuration": "212s", "travelDistanceMeters": 791, "waitDuration": "0s", "totalDuration": "212s", "startTime": "2023-01-13T18:04:10Z" }, { "travelDuration": "335s", "travelDistanceMeters": 1204, "waitDuration": "0s", "totalDuration": "335s", "startTime": "2023-01-13T18:11:52Z" }, { "travelDuration": "171s", "travelDistanceMeters": 665, "waitDuration": "0s", "totalDuration": "171s", "startTime": "2023-01-13T18:21:37Z" } ], "metrics": { "performedShipmentCount": 3, "travelDuration": "953s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "2153s", "travelDistanceMeters": 3455 }, "routeCosts": { "model.shipments.deliveries.time_windows.cost_per_hour_after_soft_end_time": 0.58166666666666667, "model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time": 5.7433333333333332, "model.vehicles.cost_per_hour": 23.922222222222221, "model.vehicles.cost_per_kilometer": 34.55 }, "routeTotalCost": 64.797222222222217 } ], "metrics": { "aggregatedRouteMetrics": { "performedShipmentCount": 3, "travelDuration": "953s", "waitDuration": "0s", "delayDuration": "0s", "breakDuration": "0s", "visitDuration": "1200s", "totalDuration": "2153s", "travelDistanceMeters": 3455 }, "usedVehicleCount": 1, "earliestVehicleStartTime": "2023-01-13T17:48:35Z", "latestVehicleEndTime": "2023-01-13T18:24:28Z", "totalCost": 64.797222222222217, "costs": { "model.vehicles.cost_per_kilometer": 34.55, "model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time": 5.7433333333333332, "model.shipments.deliveries.time_windows.cost_per_hour_after_soft_end_time": 0.58166666666666667, "model.vehicles.cost_per_hour": 23.922222222222221 } } }
W przypadku przykładu z ograniczeniami dotyczącymi tylko sztywnego przedziału czasu dostawy dostawa została całkowicie pominiętashipment[1]
. Złagodzenie ograniczeń dotyczących przedziału czasu dostawy spowodowało, że dostawa została zrealizowana przed rozpoczęciem przedziału czasu dostawy. Podobnie złagodzenie godzin zakończenia innych dostaw pozwoliło na dostarczenie shipment[2]
po zakończeniu przedziału czasowego.
Jednocześnie zmieniły się zarówno koszty, jak i łączna liczba przesyłek:
totalCost
: zmniejszono z 81 283 do 64 797- łączna liczba zrealizowanych przesyłek: wzrosła z 2 do 3;
Optymalizator znalazł tańsze rozwiązanie, ponieważ w porównaniu z poprzednim przykładem ograniczenia dotyczące przedziału czasu zostały złagodzone.
Właściwość metrics.costs
zawiera też nowy klucz, który wskazuje rzeczywisty koszt poniesiony na podstawie iloczynu ograniczenia i czasu, o jaki przekroczono okno dostawy. Czyli:
costPerHourBeforeSoftStartTime
2.0 i- czas między rzeczywistą dostawą a początkiem przedziału czasowego: 2,83583 godziny;
Efekt:
model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time
:
5.6716666666666669.
Te dane umożliwiają przeprowadzenie analizy kosztów, aby zobaczyć kompromis między ograniczeniami twardymi a miękkimi. Możesz ich użyć do dostosowania ograniczeń, aby lepiej pasowały do Twoich konkretnych reguł biznesowych. W tym przypadku łączny koszt jest mniejszy niż shipment[1].penalty_cost
z 20, 0. Optymalizator stwierdził, że bardziej opłaca się dostarczyć przesyłkę wcześniej niż ją pominąć.