OptimizeToursRequest
applica vincoli a quanto segue:
- Spedizioni, che influiscono sulla modalità di esecuzione delle spedizioni
- Veicoli, che influiscono sul modo in cui vengono calcolati gli itinerari dei veicoli
- A livello globale, interessando sia i veicoli che le spedizioni.
Questa guida si concentra su un vincolo di spedizione essenziale: le finestre temporali.
Le finestre temporali sono un tipo di vincolo che fornisci nel
messaggio OptimizeToursRequest
(REST, gRPC) per specificare
limiti basati sul tempo per le attività di spedizione. Questo tipo di vincolo influisce
sia su quando e come può essere eseguita una spedizione sia sull'assegnazione del veicolo
per la spedizione. Con questi vincoli, lo strumento di ottimizzazione dà la preferenza ai veicoli che possono soddisfare al meglio i vincoli di tempo della spedizione.
Vincoli di spedizione: finestre temporali
Specifica quando può avvenire un ritiro o una consegna nel messaggio Shipment.VisitRequest
nel seguente modo:
- Utilizza la proprietà
timeWindows
nel messaggio (REST, gRPC). - Specifica l'ora di inizio e di fine nel messaggio
TimeWindow
(REST, gRPC).
Esempio di richiesta con vincoli di finestra temporale
L'esempio qui illustrato mostra tre spedizioni diverse, ognuna con la propria
finestra di consegna. Per semplicità, questo esempio imposta le finestre temporali solo per deliveries
, ma le finestre temporali possono essere applicate anche ai ritiri. È possibile specificare più finestre temporali, anche se questo esempio ne utilizza solo una per consegna VisitRequest
.
Vedi un esempio di richiesta con finestre temporali
{ "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 } ] } }
Esempio di risposta con vincoli di intervallo di tempo
Nella risposta di esempio, l'ora di inizio e di fine del viaggio del veicolo sono rispettivamente le 17:35:50 e le 18:17:24. Questi orari riflettono l'ottimizzatore che riduce al minimo il tempo
necessario per utilizzare il veicolo specificato nella richiesta come costPerHour
, soddisfacendo
tutti i vincoli della finestra temporale. L'utilizzo di 17:35:50 come ora di inizio
elimina la necessità che il veicolo attenda in una posizione di visita fino all'inizio
della finestra temporale della visita. Nella risposta vengono visualizzati valori pari a zero waitDuration
.
Visualizza una risposta alla richiesta di esempio con intervalli di tempo
{ "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 } } }
Le finestre temporali hanno ordinato il visits
del veicolo in modo che le spedizioni con le finestre temporali più vicine vengano consegnate per prime.
shipments[2]
viene consegnato alle 17:50shipments[1]
viene pubblicato alle 18:00shipments[0]
viene consegnato alle 18:07
La richiesta di esempio specifica vincoli di finestra temporale rigidi, che richiedono
che le consegne vengano completate entro queste finestre. Se il completamento di un VisitRequests
di una spedizione in una delle sue finestre temporali non è fattibile o conveniente, lo strumento di ottimizzazione salta la spedizione. Se la spedizione ha un
penaltyCost
, l'ottimizzatore lo aggiunge ai costi riportati nella risposta
metrics
. In caso contrario, la proprietà skippedMandatoryShipmentCount
del
messaggio OptimizeToursResponse
(REST, gRPC) aumenta.
Se modifichi le finestre temporali spostando la finestra di shipment[1]
di diverse ore (dalle 18:00 alle 21:00), i risultati saranno diversi, come illustrato negli esempi seguenti.
Vedi un esempio di richiesta con finestre temporali che non possono essere soddisfatte
{ "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 } ] } }
Visualizza una risposta alla seconda richiesta di esempio con finestre temporali, in cui una spedizione viene ignorata
{ "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 } } }
In questo esempio, la finestra temporale successiva ha causato l'omissione di shipment[1]
,
perché il tempo di funzionamento aggiuntivo del veicolo necessario per completare la consegna della spedizione entro la finestra temporale specificata ha superato il costo della penalità della spedizione.
Il costo della sanzione per shipment[1]
viene visualizzato in metrics.costs
e il relativo indice
in skippedShipments
.
Vincoli di intervallo di tempo soft
Come accennato brevemente in Parametri del modello di costo, le finestre temporali possono essere applicate come vincoli flessibili. I vincoli flessibili differiscono da quelli rigidi come segue:
- Vincoli rigidi: non possono essere violati e l'ottimizzatore non offre una soluzione che violi il vincolo, anche se ciò significa saltare una spedizione.
- Vincoli flessibili: possono essere violati, il che significa che lo strumento di ottimizzazione potrebbe fornire una soluzione che viola un vincolo flessibile. Tuttavia, lo strumento di ottimizzazione applica anche un costo a qualsiasi violazione. Fornisci questo costo come proprietà aggiuntiva nella finestra temporale, in genere come costo orario per ogni ora prima o dopo la finestra temporale in cui si verifica l'attività.
Le finestre temporali vengono ammorbidite utilizzando softStartTime
o softEndTime
anziché startTime
o endTime
rispettivamente e impostando costPerHourBeforeSoftStartTime
o costPerHourAfterSoftEndTime
.
Utilizza vincoli di finestra temporale flessibili quando i ritiri o le consegne devono avvenire entro una finestra temporale specificata, ma il ritiro o la consegna entro quella finestra non è assolutamente obbligatorio. Puoi utilizzare insieme i vincoli di intervallo di tempo rigidi e flessibili per esprimere gli obiettivi commerciali. Ad esempio:
- Finestra temporale rigida: indica l'orario di apertura di un'attività, ad esempio dalle 9:00 alle 17:00.
- Finestra temporale flessibile: indica l'intervallo di tempo per la consegna o il ritiro che corrisponde alla notifica inviata al cliente, ad esempio dalle 9:00 alle 13:00.
In questo esempio, la spedizione precedentemente saltata perché la finestra temporale è iniziata troppo tardi ha un vincolo di orario di inizio meno rigido. Anche gli altri carichi hanno subito un allungamento dei tempi di consegna.
Visualizza un esempio di richiesta con finestre temporali rigide e flessibili
{ "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 } ] } }
Visualizza una risposta alla richiesta di esempio con finestre temporali rigide e flessibili
{ "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 } } }
Mentre l'esempio con solo vincoli di intervallo di tempo rigidi è stato completamente ignorato
shipment[1]
, l'attenuazione del suo intervallo di tempo di pubblicazione fa sì che venga pubblicato
prima dell'ora di inizio dell'intervallo di tempo. Allo stesso modo, l'allentamento dei tempi di fine delle
altre spedizioni ha consentito la consegna di shipment[2]
dopo la fine della finestra temporale.
Allo stesso tempo, sia i costi che le spedizioni totali sono cambiati:
totalCost
: diminuito da 81.283 a 64.797- spedizioni totali completate: aumentate da 2 a 3
L'ottimizzatore ha trovato una soluzione meno costosa perché i vincoli della finestra temporale sono stati allentati rispetto all'esempio precedente.
Infine, la proprietà metrics.costs
include anche una nuova chiave per indicare il costo effettivo sostenuto in base al prodotto del vincolo e alla durata del periodo di tempo in cui non è stata rispettata la finestra di consegna. Ossia:
costPerHourBeforeSoftStartTime
di 2.0 e- il tempo che intercorre tra la consegna effettiva e l'inizio della finestra temporale: 2,83583 ore
Risultato:
model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time
:
5.6716666666666669.
Queste metriche ti consentono di eseguire l'analisi dei costi per visualizzare il compromesso tra vincoli rigidi e flessibili, che puoi utilizzare per ottimizzare i vincoli in modo che si adattino meglio alle tue regole aziendali specifiche. In questo caso, il costo totale è
inferiore a shipment[1].penalty_cost
di 20.0. L'ottimizzatore ha rilevato
che è più conveniente consegnare la spedizione in anticipo piuttosto che
saltarla.