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ń dotyczących dostawy: oknach czasowych.
Okna czasowe to rodzaj ograniczenia, które podajesz w wiadomości OptimizeToursRequest (REST, gRPC), aby określić limity czasowe dla działań związanych z dostawą. Ten typ ograniczenia wpływa na to, kiedy i jak można zrealizować dostawę, a także na przypisanie pojazdu do dostawy. Z uwzględnieniem tych ograniczeń optymalizator preferuje pojazdy, które najlepiej spełniają ograniczenia czasowe dostawy.
Ograniczenia dotyczące przesyłki: przedziały czasu
Wiadomość powinna zawierać informacje o tym, kiedy może nastąpić odbiór lub dostawa:Shipment.VisitRequest
- Użyj właściwości
timeWindowsw wiadomości (REST, gRPC). - Określ czas rozpoczęcia i zakończenia w wiadomości
TimeWindow(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 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ładowej odpowiedzi czas rozpoczęcia i zakończenia jazdy pojazdu 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łu czasu. Użycie godziny 17:35:50 jako czasu rozpoczęcia eliminuje konieczność czekania pojazdu w miejscu wizyty do czasu 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 uporządkowały visits pojazdu w taki sposób, aby przesyłki z najwcześniejszymi oknami czasowymi były dostarczane w pierwszej kolejności.
shipments[2]dostarczono o 17:50shipments[1]zostanie dostarczony o 18:00shipments[0]dostarczono o 18:07
Przykładowe żądanie określa sztywne ograniczenia czasowe, które wymagają ukończenia dostaw w tych przedziałach czasowych. 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 dostawa ma penaltyCost, optymalizator dodaje ją do kosztów podanych w odpowiedzi metrics. 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 punktu shipment[1], ponieważ dodatkowy czas pracy pojazdu wymagany do dostarczenia przesyłki w określonym przedziale czasowym przekroczył koszt kary za opóźnienie.
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 ten 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 przesyłki.
- Ograniczenia elastyczne: mogą zostać naruszone, co oznacza, że optymalizator może podać rozwiązanie, które narusza ograniczenie elastyczne. 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 odbiory lub dostawy powinny nastąpić w określonym przedziale czasu, ale odbiór lub dostawa w tym przedziale czasu nie jest bezwzględnie wymagana. Możesz używać ograniczeń twardego i miękkiego okna czasowego, 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 przesyłka, która została wcześniej pominięta, ponieważ jej okno czasowe rozpoczęło się zbyt późno, ma złagodzone ograniczenie czasu rozpoczęcia. 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 sztywnych przedziałów czasowych dostawa została całkowicie pominiętashipment[1], a złagodzenie ograniczeń dotyczących przedziału czasowego dostawy spowodowało, że dostawa nastąpiła przed rozpoczęciem przedziału czasowego. Podobnie złagodzenie terminów dostawy innych przesyłek umożliwiło 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 dostaw: 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:
costPerHourBeforeSoftStartTimew wersji 2.0 i- czas między rzeczywistą dostawą a początkiem przedziału czasowego: 2,83583 godziny;
Wynik:
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ąć.