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

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

ה-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 הגלובלי, והפתרונות הקצרים יותר בזמן הפעולה מקבלים עדיפות לכל רכב נתון. גם עלות העונש על 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 לבצע אופטימיזציה ליעדים שלא תואמים לכוונות שלכם.