OptimizeToursRequest
applica vincoli a quanto segue:
- Spedizioni, che influiscono sulle modalità di spedizione
- Veicoli, che influiscono sul modo in cui vengono calcolati i percorsi dei veicoli
- A livello globale, con ripercussioni sia sui veicoli che sulle spedizioni.
Questa guida si concentra su un vincolo essenziale per la spedizione: le finestre temporali.
Le finestre temporali sono un tipo di vincolo che fornisci nel messaggio OptimizeToursRequest
(REST, gRPC) per specificare i limiti basati sul tempo per le attività di spedizione. Questo tipo di vincolo influenza quando e come una spedizione può essere eseguita, nonché l'assegnazione del veicolo per la spedizione. Con questi vincoli, l'ottimizzatore dà la precedenza ai veicoli che meglio soddisfano i limiti di tempo della spedizione.
Vincoli di spedizione: finestre temporali
Puoi specificare quando può avvenire il ritiro o la consegna nel messaggio Shipment.VisitRequest
come segue:
- 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 relativi alla finestra temporale
L'esempio riportato di seguito mostra tre spedizioni diverse, ciascuna con il proprio periodo di consegna. Per semplicità, questo esempio imposta le fasce orarie solo su deliveries
, ma le fasce orarie possono essere applicate anche ai ritiri. È possibile specificare più finestre temporali, anche se in questo esempio ne viene utilizzata una sola per invio VisitRequest
.
Visualizza 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 relativi alla finestra temporale
Nella risposta di esempio, l'ora di inizio e di fine del veicolo sono rispettivamente 17:35:50 e
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 al contempo tutti i vincoli relativi alla finestra temporale. L'uso di 17:35:50 come ora di inizio
evita la necessità per il veicolo di attendere in un luogo di visita fino
all'inizio della finestra temporale della visita. Questo valore viene visualizzato nella risposta come zero valori waitDuration
.
Visualizza una risposta alla richiesta di esempio con finestre temporali
{ "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ù strette vengano consegnate per prime.
shipments[2]
viene consegnato alle 17:50shipments[1]
viene consegnato alle ore 18:00shipments[0]
viene consegnato alle ore 18:07
La richiesta di esempio specifica vincoli di finestra temporale rigidi, che richiedono di completare le importazioni entro queste finestre. Se il completamento del VisitRequests
di una spedizione entro una delle relative finestre temporali non è fattibile o conveniente, l'ottimizzatore salta la spedizione. Se la spedizione ha un valore penaltyCost
, l'ottimizzatore lo aggiunge ai costi riportati nella rispostametrics
. In caso contrario, la proprietà skippedMandatoryShipmentCount
del messaggioOptimizeToursResponse
(REST, gRPC) aumenta.
Se modifichi le finestre temporali spostando la finestra di shipment[1]
diverse ore
dopo (alle 21:00 dalle 18:00), i risultati saranno diversi, come illustrato negli
esempi seguenti.
Visualizza 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 viene saltata una spedizione
{ "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'esclusione di shipment[1]
,
perché il tempo di funzionamento aggiuntivo del veicolo necessario per completare la consegna
della spedizione entro il periodo di tempo specificato ha superato il costo di penalità della spedizione.
Il costo della penalità per shipment[1]
viene visualizzato in metrics.costs
e il relativo indice
viene visualizzato in skippedShipments
.
Vincoli flessibili per le finestre temporali
Come accennato brevemente in Parametri del modello di costo, le finestre temporali possono essere applicate come vincoli soft. I vincoli flessibili sono diversi 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 l'ottimizzatore 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 per ora per ogni ora prima o dopo la finestra temporale in cui si verifica l'attività.
Le finestre temporali vengono attenuate utilizzando softStartTime
o softEndTime
anziché startTime
o endTime
e impostando costPerHourBeforeSoftStartTime
o costPerHourAfterSoftEndTime
.
Utilizza vincoli di finestra temporale flessibili quando i ritiri o le consegne devono avvenire in una finestra temporale specificata, ma il ritiro o la consegna all'interno di questa finestra non è obbligatoria. Puoi utilizzare insieme vincoli di finestra temporale rigidi e flessibili per esprimere gli obiettivi commerciali. Ad esempio:
- Finestra temporale fissa: indica l'orario di apertura di un cliente, ad esempio dalle 9:00 alle 17:00.
- Intervallo di tempo non flessibile: indica i tempi di consegna o ritiro corrispondenti alla notifica inviata al cliente, ad esempio dalle 9:00 alle 13:00.
In questo esempio, il vincolo dell'ora di inizio è più lento e in precedenza è stato saltato perché la finestra temporale è iniziata troppo tardi. Anche per le altre spedizioni sono stati modificati i periodi di tempo in cui è possibile effettuare l'ordine.
Visualizza un esempio di richiesta con finestre di tempo 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 } ] } }
Guarda una risposta alla richiesta di esempio con finestre temporali fisse 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 } } }
Nel caso in cui l'esempio con solo vincoli relativi alle fasce orarie specifiche ha ignorato completamente shipment[1]
, con l'attenuazione della finestra temporale di pubblicazione la pubblicazione viene eseguita prima dell'ora di inizio della finestra temporale. Analogamente, l'allungamento degli orari di fine delle altre spedizioni ha consentito di consegnare shipment[2]
dopo il termine della finestra temporale.
Allo stesso tempo, sono cambiati sia i costi sia le spedizioni totali:
totalCost
: diminuito da 81.283 a 64.797- spedizioni completate totali: 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 il periodo di tempo per cui il periodo di consegna non è stato rispettato. Ossia:
costPerHourBeforeSoftStartTime
di 2,0 e- il tempo che intercorre tra la pubblicazione effettiva e l'inizio dell'intervallo di tempo: 2,83583 ore
Risultato:
model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time
:
5,6716666666666669.
Queste metriche ti consentono di eseguire un'analisi dei costi per vedere il compromesso tra vincoli rigidi e vincoli 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 rispetto a saltarla.