ניתוב Fleet: הקצאת משלוחים לכלי רכב

במדריך הזה נסביר איך מספר כלי הרכב שמוצגים בפתרון של אופטימיזציית מסלולים עשוי להשתנות בהתאם לפרמטרים של הבקשה.

ה-Route Optimization API לא רק מבצע אופטימיזציה של סדר השלמת המשלוחים, אלא גם מקצה את המשלוחים האלה לכלי רכב כדי לבצע אופטימיזציה של העלויות במסגרת האילוצים שאתם מנהלים.

בדוגמה הראשונה, מספר כלי הרכב תואם למספר המשלוחים, וכל כלי הרכב חולקים את אותם מאפייני עלות ומיקום. לכל רכב יש עלות לשעת הפעלה ועלות לקילומטר נסיעה, וכך תוכלו לצמצם את זמן הנסיעה ואת המרחק. יכול להיות שתתבקשו להקצות כמה כלי רכב להעברות, אבל בתשובה לדוגמה מוצגת הפתרון בעל העלות הנמוכה ביותר בהתאם לפרמטרים של מודל העלויות שצוינו.

דוגמה לבקשה עם כמה כלי רכב

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

הצגת תגובה לבקשה עם מספר רכבים

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

הפתרון מקצה את כל המשלוחים לרכב אחד בלבד, ודלג על משלוח אחד למרות שזמינות הרכבים מספיקה. הסיבה לכך היא שהעלות של הפעלת רכבים נוספים גבוהה מדי ולא מצדיקה את הפעולה, וגם לא משתלם לאף רכב להשלים את המשלוח שהושמט, כי עלות העונש נמוכה. למרות קיבולת הרכב הזמינה, רכב אחד יכול לבצע את כל המשלוחים שהוקצו לו באופן הכי חסכוני. לכלי הרכב בבקשה לא מוגדר המאפיין usedIfRouteIsEmpty (פרטים נוספים זמינים במסמכי העזרה של ההודעה Vehicle (REST, ‏ gRPC)), כך שאין עלות אם לא משתמשים בהם.

שינוי הפרמטרים של העלות כדי לתת עדיפות לפתרונות קצרים יותר באופן גלובלי במקום למסלולים קצרים יותר בנפרד לכל רכב גורם ליותר רכבים להשתתף בפתרון. בבקשת הדוגמה הבאה, Vehicle.costPerHour מוחלף ב-ShipmentModel.globalDurationCostPerHour ברמת ה-global, כדי לתת עדיפות לפתרונות שהזמן הכולל שלהם קצר יותר במהלך זמן הפעולה של כל רכב נתון. גם עלות העונש של shipment[1] תגדל כדי לצמצם את הסבירות לדילוג עליה.

דוגמה לבקשה באמצעות 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
      }
    ]
  }
}
    

התוצאה מראה שהשימוש בפרמטר העלות הגלובלית לשעה מוביל לשימוש בכל שלושת כלי הרכב במקום רק באחד מהם.

הצגת התשובה לבקשה באמצעות 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
    }
  }
}
    

בתשובה הזו, כל שלושת הרכבים נמצאים בשימוש (לפי metrics.usedVehicleCount), וכל רכב הוקצה לביצוע משלוח אחד. מכיוון שמיקומי ההתחלה, מיקומי הסיום ו-costPerKilometer זהים, אפשר להחליף בפועל בין כל שלושת כלי הרכב, כך שלא משנה איזה משלוח משויך לאיזה רכב.

הערך globalDurationCostPerHour גורם למטמיזטור למצוא פתרון קצר יותר באופן כללי: ההפרש בין earliestVehicleStartTime ל-latestVehicleEndTime הוא רק 18 דקות ו-54 שניות, לעומת 28 דקות ו-22 שניות בתשובה הקודמת. עם זאת, הערך של metrics.costs.model.vehicles.cost_per_kilometer עלה, והוא משקף את המרחק הכולל שנסע על ידי שלושת כלי הרכב המשומשים. זוהי דוגמה אחת לאופן שבו מודל העלות מאפשר לבצע פשרות:

  • עלייה בעלות הזמן הכוללת: הגדלת ניצול הרכב כדי למזער את זמן השלמת המשימה הכולל, על חשבון מרחק נסיעה ארוך יותר וזמן רב יותר בדרכים.
  • עלות מוגברת של זמן הרכב: הפחתת ניצול הרכב והזמן שחלף בנסיעה, על חשבון פתרון כולל ארוך יותר.

הערך globalDurationCostPerHour של 150.0 בדוגמה הזו מוגדר פי שלושה מהערך costPerHour של 50.0 של כלי הרכב הבודדים מהדוגמה הקודמת. ערך העלות הגלובלי הזה מבוסס על ההנחה שכל שלושת הרכבים יפעלו בו-זמנית, אבל בהגדרות מעשיות, הנחות כאלה עשויות שלא לשקף את המציאות, ובפועל להשפיע לרעה על איכות התוצאות.

כפי שמתואר בקטע פרמטרים של מודל עלות, כל פרמטרים העלות מפורטים באותן יחידות ללא מאפיין ממד, אבל יכולים להיות להם משמעויות שונות מאוד. בדרך כלל, כדאי שהערכים של הפרמטרים של מודל העלות יהיו מבוססים על נתונים מהמציאות ככל האפשר, כי עלויות מלאכותיות כמו אלה שבדוגמאות האלה עלולות לגרום ל-API לבצע אופטימיזציה להשגת מטרות שלא תואמות לכוונתכם.