Optymalizacja tras floty: przypisywanie przesyłek do pojazdów

Z tego przewodnika dowiesz się, jak liczba pojazdów podana w rozwiązaniu Route Optimization może się różnić w zależności od parametrów żądania.

Interfejs Route Optimization API nie tylko optymalizuje kolejność realizacji przesyłek, ale także przypisuje je do pojazdów, aby optymalizować koszty w ramach zarządzanych przez Ciebie ograniczeń.

W pierwszym przykładzie liczba pojazdów jest zgodna z liczbą przesyłek, a wszystkie pojazdy mają te same właściwości kosztów i lokalizacji. Każdy pojazd ma koszt za godzinę pracy i koszt za przejechany kilometr, co pomaga zminimalizować czas i odległość podróży. Można by się spodziewać, że do przesyłek zostanie przypisanych wiele pojazdów, ale przykładowa odpowiedź pokazuje rozwiązanie o najniższym koszcie przy podanych parametrach modelu kosztów.

Przykład żądania z wieloma pojazdami

{
  "model": {
    "globalStartTime": "2023-01-13T16:00:00-08:00",
    "globalEndTime": "2023-01-14T16:00:00-08:00",
    "shipments": [
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.789456,
              "longitude": -122.390192
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 100.0
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.789116,
              "longitude": -122.395080
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 5.0
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.795242,
              "longitude": -122.399347
            },
            "duration": "250s"
          }
        ],
        "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": 50.0,
        "costPerKilometer": 10.0
      },
      {
        "endLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "startLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "costPerHour": 50.0,
        "costPerKilometer": 10.0
      },
      {
        "endLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "startLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "costPerHour": 50.0,
        "costPerKilometer": 10.0
      }
    ]
  }
}
    

Wyświetlanie odpowiedzi na prośbę dotyczącą wielu pojazdów

{
  "routes": [
    {
      "vehicleStartTime": "2023-01-14T00:00:00Z",
      "vehicleEndTime": "2023-01-14T00:28:22Z",
      "visits": [
        {
          "isPickup": true,
          "startTime": "2023-01-14T00:00:00Z",
          "detour": "0s"
        },
        {
          "shipmentIndex": 2,
          "isPickup": true,
          "startTime": "2023-01-14T00:02:30Z",
          "detour": "150s"
        },
        {
          "startTime": "2023-01-14T00:08:55Z",
          "detour": "150s"
        },
        {
          "shipmentIndex": 2,
          "startTime": "2023-01-14T00:21:21Z",
          "detour": "572s"
        }
      ],
      "transitions": [
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-14T00:00:00Z"
        },
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-14T00:02:30Z"
        },
        {
          "travelDuration": "235s",
          "travelDistanceMeters": 795,
          "waitDuration": "0s",
          "totalDuration": "235s",
          "startTime": "2023-01-14T00:05:00Z"
        },
        {
          "travelDuration": "496s",
          "travelDistanceMeters": 1893,
          "waitDuration": "0s",
          "totalDuration": "496s",
          "startTime": "2023-01-14T00:13:05Z"
        },
        {
          "travelDuration": "171s",
          "travelDistanceMeters": 665,
          "waitDuration": "0s",
          "totalDuration": "171s",
          "startTime": "2023-01-14T00:25:31Z"
        }
      ],
      "metrics": {
        "performedShipmentCount": 2,
        "travelDuration": "902s",
        "waitDuration": "0s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "800s",
        "totalDuration": "1702s",
        "travelDistanceMeters": 3353
      },
      "routeCosts": {
        "model.vehicles.cost_per_kilometer": 33.53,
        "model.vehicles.cost_per_hour": 23.638888888888889
      },
      "routeTotalCost": 57.168888888888887
    },
    {
      "vehicleIndex": 1
    },
    {
      "vehicleIndex": 2
    }
  ],
  "skippedShipments": [
    {
      "index": 1
    }
  ],
  "metrics": {
    "aggregatedRouteMetrics": {
      "performedShipmentCount": 2,
      "travelDuration": "902s",
      "waitDuration": "0s",
      "delayDuration": "0s",
      "breakDuration": "0s",
      "visitDuration": "800s",
      "totalDuration": "1702s",
      "travelDistanceMeters": 3353
    },
    "usedVehicleCount": 1,
    "earliestVehicleStartTime": "2023-01-14T00:00:00Z",
    "latestVehicleEndTime": "2023-01-14T00:28:22Z",
    "totalCost": 62.168888888888887,
    "costs": {
      "model.vehicles.cost_per_hour": 23.638888888888889,
      "model.shipments.penalty_cost": 5,
      "model.vehicles.cost_per_kilometer": 33.53
    }
  }
}
    

Rozwiązanie przypisuje wszystkie przesyłki tylko do jednego pojazdu, pomijając jedną przesyłkę, mimo że dostępnych jest wiele pojazdów. Dzieje się tak, ponieważ koszt obsługi dodatkowych pojazdów jest zbyt wysoki, aby go uzasadnić, a w przypadku żadnego pojazdu nie jest opłacalne zrealizowanie pominiętej przesyłki ze względu na niski koszt kary. Pomimo dostępnej ładowności pojazdu jeden pojazd może zrealizować wszystkie przypisane dostawy w najbardziej opłacalny sposób. Pojazdy w żądaniu nie mają ustawionej właściwości usedIfRouteIsEmpty (szczegółowe informacje znajdziesz w dokumentacji Vehicle wiadomości (REST, gRPC)), więc jeśli nie są używane, nie generują kosztów.

Zmiana parametrów kosztów w celu nadania priorytetu krótszym rozwiązaniom globalnym zamiast krótszym trasom poszczególnych pojazdów powoduje, że w rozwiązaniu uczestniczy więcej pojazdów. W następnym przykładzie żądania zastąpimy Vehicle.costPerHour globalnym ShipmentModel.globalDurationCostPerHour, przyznając priorytet rozwiązaniom, które są krótsze w sumie niż czas pracy dowolnego pojazdu. Zwiększyliśmy też koszt kary za shipment[1], aby zmniejszyć prawdopodobieństwo pominięcia tego elementu.

Zobacz przykładowe zgłoszenie z użyciem globalDurationCostPerHour

{
  "model": {
    "globalStartTime": "2023-01-13T16:00:00-08:00",
    "globalEndTime": "2023-01-14T16:00:00-08:00",
    "globalDurationCostPerHour": 150.0,
    "shipments": [
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.789456,
              "longitude": -122.390192
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 100.0
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.789116,
              "longitude": -122.395080
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 75.0
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.795242,
              "longitude": -122.399347
            },
            "duration": "250s"
          }
        ],
        "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
        },
        "costPerKilometer": 10.0
      },
      {
        "endLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "startLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "costPerKilometer": 10.0
      },
      {
        "endLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "startLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "costPerKilometer": 10.0
      }
    ]
  }
}
    

Wynik pokazuje, że użycie parametru kosztu globalnego za godzinę powoduje użycie wszystkich 3 pojazdów zamiast tylko jednego.

Zobacz odpowiedź na prośbę, korzystając z globalDurationCostPerHour.

{
  "routes": [
    {
      "vehicleStartTime": "2023-01-14T00:00:00Z",
      "vehicleEndTime": "2023-01-14T00:16:20Z",
      "visits": [
        {
          "shipmentIndex": 2,
          "isPickup": true,
          "startTime": "2023-01-14T00:00:00Z",
          "detour": "0s"
        },
        {
          "shipmentIndex": 2,
          "startTime": "2023-01-14T00:09:19Z",
          "detour": "0s"
        }
      ],
      "transitions": [
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-14T00:00:00Z"
        },
        {
          "travelDuration": "409s",
          "travelDistanceMeters": 1371,
          "waitDuration": "0s",
          "totalDuration": "409s",
          "startTime": "2023-01-14T00:02:30Z"
        },
        {
          "travelDuration": "171s",
          "travelDistanceMeters": 665,
          "waitDuration": "0s",
          "totalDuration": "171s",
          "startTime": "2023-01-14T00:13:29Z"
        }
      ],
      "metrics": {
        "performedShipmentCount": 1,
        "travelDuration": "580s",
        "waitDuration": "0s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "400s",
        "totalDuration": "980s",
        "travelDistanceMeters": 2036
      },
      "routeCosts": {
        "model.vehicles.cost_per_kilometer": 20.36
      },
      "routeTotalCost": 20.36
    },
    {
      "vehicleIndex": 1,
      "vehicleStartTime": "2023-01-14T00:00:00Z",
      "vehicleEndTime": "2023-01-14T00:18:54Z",
      "visits": [
        {
          "shipmentIndex": 1,
          "isPickup": true,
          "startTime": "2023-01-14T00:00:00Z",
          "detour": "0s"
        },
        {
          "shipmentIndex": 1,
          "startTime": "2023-01-14T00:08:24Z",
          "detour": "0s"
        }
      ],
      "transitions": [
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-14T00:00:00Z"
        },
        {
          "travelDuration": "354s",
          "travelDistanceMeters": 1192,
          "waitDuration": "0s",
          "totalDuration": "354s",
          "startTime": "2023-01-14T00:02:30Z"
        },
        {
          "travelDuration": "380s",
          "travelDistanceMeters": 1190,
          "waitDuration": "0s",
          "totalDuration": "380s",
          "startTime": "2023-01-14T00:12:34Z"
        }
      ],
      "metrics": {
        "performedShipmentCount": 1,
        "travelDuration": "734s",
        "waitDuration": "0s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "400s",
        "totalDuration": "1134s",
        "travelDistanceMeters": 2382
      },
      "routeCosts": {
        "model.vehicles.cost_per_kilometer": 23.82
      },
      "routeTotalCost": 23.82
    },
    {
      "vehicleIndex": 2,
      "vehicleStartTime": "2023-01-14T00:00:00Z",
      "vehicleEndTime": "2023-01-14T00:16:14Z",
      "visits": [
        {
          "isPickup": true,
          "startTime": "2023-01-14T00:00:00Z",
          "detour": "0s"
        },
        {
          "startTime": "2023-01-14T00:06:25Z",
          "detour": "0s"
        }
      ],
      "transitions": [
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-14T00:00:00Z"
        },
        {
          "travelDuration": "235s",
          "travelDistanceMeters": 795,
          "waitDuration": "0s",
          "totalDuration": "235s",
          "startTime": "2023-01-14T00:02:30Z"
        },
        {
          "travelDuration": "339s",
          "travelDistanceMeters": 1276,
          "waitDuration": "0s",
          "totalDuration": "339s",
          "startTime": "2023-01-14T00:10:35Z"
        }
      ],
      "metrics": {
        "performedShipmentCount": 1,
        "travelDuration": "574s",
        "waitDuration": "0s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "400s",
        "totalDuration": "974s",
        "travelDistanceMeters": 2071
      },
      "routeCosts": {
        "model.vehicles.cost_per_kilometer": 20.71
      },
      "routeTotalCost": 20.71
    }
  ],
  "metrics": {
    "aggregatedRouteMetrics": {
      "performedShipmentCount": 3,
      "travelDuration": "1888s",
      "waitDuration": "0s",
      "delayDuration": "0s",
      "breakDuration": "0s",
      "visitDuration": "1200s",
      "totalDuration": "3088s",
      "travelDistanceMeters": 6489
    },
    "usedVehicleCount": 3,
    "earliestVehicleStartTime": "2023-01-14T00:00:00Z",
    "latestVehicleEndTime": "2023-01-14T00:18:54Z",
    "totalCost": 112.14,
    "costs": {
      "model.vehicles.cost_per_kilometer": 64.89,
      "model.global_duration_cost_per_hour": 47.25
    }
  }
}
    

W tej odpowiedzi wszystkie 3 pojazdy są w użyciu (zgodnie z metrics.usedVehicleCount), a każdy z nich ma przypisaną 1 dostawę do zrealizowania. Przy identycznych lokalizacjach początkowych, końcowych i costPerKilometer wszystkie 3 pojazdy są w zasadzie wymienne, więc nie ma znaczenia, który transport zostanie przypisany do którego pojazdu.

Symbol globalDurationCostPerHour powoduje, że optymalizator znajduje rozwiązanie, które jest ogólnie krótsze: różnica między earliestVehicleStartTimelatestVehicleEndTime wynosi tylko 18 minut i 54 sekundy, a nie 28 minut i 22 sekundy, jak w poprzedniej odpowiedzi. Warto jednak zauważyć, że metrics.costs.model.vehicles.cost_per_kilometer wzrosła, co odzwierciedla większy łączny dystans pokonany przez 3 używane pojazdy. Pokazuje to jeden ze sposobów, w jaki model kosztów pozwala na kompromisy:

  • Zwiększony globalny koszt czasu: zwiększ wykorzystanie pojazdów, aby zminimalizować ogólny czas realizacji, kosztem większej odległości pokonywanej przez pojazdy i dłuższego czasu spędzanego w transporcie.
  • Zwiększony koszt czasu pojazdu: zmniejsz wykorzystanie pojazdu i czas spędzony w transporcie kosztem dłuższego czasu realizacji.

Zwróć uwagę, że w tym przykładzie wartość globalDurationCostPerHour wynosząca 150,0 jest trzykrotnie większa od wartości costPerHour wynoszącej 50,0 w poprzednim przykładzie. Ta globalna wartość kosztu zakłada, że wszystkie 3 pojazdy będą działać jednocześnie, ale w praktyce takie założenia mogą nie odzwierciedlać rzeczywistości i mogą mieć negatywny wpływ na jakość wyników.

Jak opisano w sekcji Parametry modelu kosztów, wszystkie parametry kosztów są wyrażone w tych samych jednostkach bezwymiarowych, ale mogą mieć bardzo różne znaczenia. Zazwyczaj wartości parametrów modelu kosztów powinny być jak najbardziej zbliżone do rzeczywistości, ponieważ sztuczne koszty, takie jak w tym przykładzie, mogą spowodować, że interfejs API będzie optymalizować wyniki pod kątem celów, które nie są zgodne z Twoimi intencjami.