Vincoli dei tempi di ritiro e consegna

OptimizeToursRequest applica vincoli a quanto segue:

  • Spedizioni, che influiscono sulle modalità di spedizione
  • Veicoli, che influisce sulla modalità di calcolo dei percorsi dei veicoli
  • A livello globale, interessando sia i veicoli che le spedizioni.

Questa guida si concentra su un vincolo essenziale della spedizione: le finestre temporali.

Le finestre temporali sono un tipo di vincolo che fornisci nella OptimizeToursRequest messaggio (REST, gRPC) per specificare i limiti temporali alle attività di spedizione. Questo tipo di vincolo influenza sia quando e come può essere effettuata una spedizione sia l'assegnazione del veicolo per la spedizione. Con questi vincoli, l'ottimizzatore dà la precedenza i veicoli più adatti per soddisfare i limiti di tempo previsti per la spedizione.

Vincoli di spedizione: finestre temporali

Puoi specificare quando può avvenire il ritiro o la consegna in Shipment.VisitRequest messaggio 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 illustra tre diverse spedizioni, ciascuna con il proprio finestra di consegna. Per semplicità, questo esempio imposta finestre temporali su deliveries ma possono essere applicate anche ai ritiri. È possibile anche se in questo esempio ne viene utilizzata solo una per pubblicazione VisitRequest.

Visualizza un esempio di richiesta con le 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 limiti di tempo

Nella risposta di esempio, l'ora di inizio e di fine del veicolo sono 17:35:50 e rispettivamente 18:17:24. Questi tempi riflettono l'ottimizzatore che riduce al minimo il tempo necessario per utilizzare il veicolo specificato nella richiesta come costPerHour, mentre che soddisfano tutti i vincoli di intervallo di tempo. Uso di 17:35:50 come ora di inizio. elimina la necessità per il veicolo di attendere in un luogo di visita fino a quando l'intervallo di tempo della visita. Questo compare nella risposta come zero waitDuration e i relativi valori.

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 prime finestre temporali vengono pubblicate per prime.

  1. shipments[2] viene consegnato alle 17:50
  2. shipments[1] viene consegnato alle 18:00
  3. shipments[0] viene consegnato alle 18:07

La richiesta di esempio specifica i vincoli relativi alle finestre temporali fisiche, richiedendo le consegne da completare all'interno di quelle finestre. Se completi una spedizione VisitRequests all'interno di una qualsiasi delle sue finestre temporali non è fattibile o conveniente, l'ottimizzatore salta la spedizione. Se la spedizione include un penaltyCost, l'ottimizzatore la aggiunge ai costi riportati nella risposta metrics. In caso contrario, la proprietà skippedMandatoryShipmentCount del Aumenta il numero di messaggi OptimizeToursResponse (REST, gRPC).

Se cambi le finestre temporali spostando la finestra di shipment[1] di diverse ore più tardi (alle 21:00 dalle 18:00), i risultati saranno diversi come illustrato nella i seguenti esempi.

Visualizza un esempio di richiesta con finestre temporali che non possono sii soddisfatto

{
  "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
      }
    ]
  }
}
    

Guarda una risposta alla seconda richiesta di esempio con finestre temporali in cui una spedizione viene saltata

{
  "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'ignoramento di shipment[1], poiché il tempo di funzionamento aggiuntivo del veicolo è necessario per completare la consegna 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 compare in skippedShipments.

Vincoli temporali flessibili

Come accennato brevemente in Parametri del modello di costo, le finestre temporali possono essere applicate come vincoli soft. I vincoli soft si differenziano dai vincoli rigidi nel seguente modo:

  • Vincoli rigidi: non possono essere violati e l'ottimizzatore non offre una che viola il vincolo, anche se ciò significa saltare spedizione.
  • Vincoli flessibili: potrebbero essere violati, il che significa che l'ottimizzatore potrebbe una soluzione che violi un vincolo soft. Tuttavia, l'ottimizzatore applica inoltre un costo a qualsiasi violazione. Fornisci questo costo come ulteriori proprietà nella finestra temporale, di solito come costo orario per ogni ora prima o dopo l'intervallo di tempo in cui si svolge l'attività.

Le finestre temporali vengono ridotte utilizzando softStartTime o softEndTime anziché rispettivamente startTime o endTime e impostando costPerHourBeforeSoftStartTime o costPerHourAfterSoftEndTime.

Utilizza vincoli temporali flessibili quando devono avvenire ritiri o consegne all'interno di un intervallo di tempo specificato, ma il ritiro o la consegna all'interno di tale finestra assolutamente necessario. Puoi utilizzare vincoli di tempo fissi e fissi insieme per esprimere gli scopi commerciali. Ad esempio:

  • Finestra temporale fissa: indica l'orario di apertura di un cliente, ad esempio da Dalle 09:00 alle 17:00.
  • Finestra temporale flessibile: indica i tempi 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 è stata saltata in precedenza perché l'orario finestra iniziata troppo tardi ha il vincolo dell'ora di inizio rallentata. L'altro per le spedizioni sono state previste finestre temporali anche le ore di fine si sono ridotte.

Visualizza un esempio di richiesta con tempi rigidi e flessibili finestre

{
  "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 i comandi finestre temporali 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
    }
  }
}
    

L'esempio con solo vincoli relativi alle finestre temporali difficili è stato ignorato shipment[1], l'attenuazione del periodo di tempo per la consegna fa sì che venga pubblicato prima dell'ora di inizio della finestra temporale. Analogamente, alleggerendo le ore di fine altre spedizioni hanno consentito la consegna di shipment[2] dopo la finestra temporale termina il video.

Allo stesso tempo, i costi e le spedizioni totali sono cambiati:

  • 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é la finestra temporale i vincoli sono stati ridotti rispetto all'esempio precedente.

Infine, la proprietà metrics.costs include anche una nuova chiave per indicare costo effettivo sostenuto in base al prodotto del vincolo e alla lunghezza al momento del mancato rispetto della finestra di consegna. 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 consentono di analizzare i costi per trovare un compromesso tra e i vincoli morbidi, che puoi usare per ottimizzare i vincoli più adatti alle tue specifiche regole aziendali. In questo caso, il costo totale è inferiore a shipment[1].penalty_cost di 20.0. L'ottimizzatore ha identificato che è più conveniente consegnare la spedizione in anticipo rispetto saltare la spedizione.