עומסים ומגבלות

במדריך הזה מתוארים loadDemands ו-loadLimits, ואיך הם קשורים זה לזה.

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

לרכבים ולמשלוחים יש מאפיינים פיזיים כשמתכננים מסלול.

  • כלי רכב: המאפיין loadLimits מציין את העומס המקסימלי שהרכב יכול להתמודד איתו. צפייה במסמכי התיעוד של ההודעה של Vehicle (REST, gRPC).
  • משלוחים: המאפיין loadDemands מציין את כמות העומס שמשלוח נתון צורך. צפייה במסמכי התיעוד של ההודעה של Shipment (REST, gRPC).

יחד, שני האילוצים האלה מאפשרים ל-Optimize להקצות משלוחים לרכבים בצורה המתאימה ביותר לקיבולת הצי ולדרישות המשלוח.

בהמשך המסמך הזה נדון בפירוט ב-loadLimits וב-loadDemands.

עומסים ומגבלות: סוגים

כל דרישה לעומס ואילוץ של הגבלה מבטאת את ביטוי סוג מסוים.

תוכל לספק קבוצה משלך של סוגי טעינה, כמו הדוגמאות הבאות:

  • משקל
  • עוצמת קול
  • מדידות ליניאריות
  • שמות של פריטים או ציוד שמועברים

במדריך הזה השתמשנו בweightKg כדוגמה.

גם Shipment.loadDemands וגם Vehicle.loadLimits משתמשים בסוג map של מאגרי פרוטוקולים, עם string מפתחות שמייצגים את סוגי הטעינה.

ערכים של Shipment.loadDemands משתמשים בהודעה Load (REST, gRPC). להודעה של Load יש מאפיין אחד של amount שמייצג את הקיבולת הנדרשת להשלמת המשלוח מהסוג שצוין.

ערכים של Vehicle.loadLimits משתמשים בהודעה LoadLimit (REST, gRPC). להודעה LoadLimit יש כמה מאפיינים, כאשר maxLoad מייצג את קיבולת העומס המקסימלית של הרכב בסוג שצוין.

ה-loadDemands של המשלוח משתמש ב-loadLimits של הרכב שהוקצה לו רק אם לשניהם יש מפתחות תואמים של סוג טעינה. לדוגמה, משלוח עם loadDemands של:

"loadDemands": {
  "weightKg": {
    "amount": 50
  }
}

נדרשות 50 יחידות טעינה מסוג weightKg כדי להשלים את המשלוח. רכב עם loadLimits של:

"loadLimits": {
  "weightKg": {
    "maxLoad": 100
  }
}

עשויה להשלים את המשלוח, כי הערך של maxLoad של הרכב מסוג weightKg גדול מ-loadDemands או שווה לו מסוג weightKg. עם זאת, כלי רכב עם loadLimits של:

"loadLimits": {
  "equipmentRackStorage": {
    "maxLoad": 10
  }
}

באופן מרומז, יש קיבולת ללא הגבלה של weightKg כי אין מגבלת עומס על weightKg, כך שהמשקל של הרכב לא מוגבל.

העברת עומסים בין משלוחים וכלי רכב

כשאוספים ומסירים כלי רכב, השדה loadDemand של המשלוח עובר בין המשלוח לרכב. אפשר לראות את טעינות הרכב בהודעה של OptimizeToursResponse (REST, gRPC)routes.transitions של רכב נתון. הרצף הוא:

  1. קיבולת העומס הנדרשת מוגדרת למשלוח כ-loadDemand.
  2. איסוף המשלוח מתבצע על ידי הרכב שהוקצה לו, והמדד vehicleLoads של הרכב גדל במספר ה-loadDemand של המשלוח. ההעברה הזו מיוצגת על ידי visits.loadDemands חיובי בהודעת התגובה.
  3. הרכב מספק את המשלוח, והמדד'vehicleLoads' של הרכב יורד בכמות ה-loadDemand של המשלוח שנמסר. ההעברה הזו מיוצגת על ידי הערך visits.loadDemands השלילי של הודעת התשובה.

השדה vehicleLoads של הרכב לא יכול לחרוג מה-loadLimits שהוגדר לרכב בכל שלב במסלול.

דוגמה מלאה לדרישות עומס ומגבלות

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

{
  "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"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 100.0,
        "loadDemands": {
          "weightKg": {
            "amount": 50
          }
        }
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.789116,
              "longitude": -122.395080
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 15.0,
        "loadDemands": {
          "weightKg": {
            "amount": 10
          }
        }
      },
      {
        "deliveries": [
          {
            "arrivalLocation": {
              "latitude": 37.795242,
              "longitude": -122.399347
            },
            "duration": "250s"
          }
        ],
        "pickups": [
          {
            "arrivalLocation": {
              "latitude": 37.794465,
              "longitude": -122.394839
            },
            "duration": "150s"
          }
        ],
        "penaltyCost": 50.0,
        "loadDemands": {
          "weightKg": {
            "amount": 80
          }
        }
      }
    ],
    "vehicles": [
      {
        "endLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "startLocation": {
          "latitude": 37.794465,
          "longitude": -122.394839
        },
        "costPerHour": 40.0,
        "costPerKilometer": 10.0,
        "loadLimits": {
          "weightKg": {
            "maxLoad": 100
          }
        }
      }
    ]
  }
}
    

הבקשה לדוגמה מכילה מספר פרמטרים הקשורים לטעינה:

  • ל-shipments[0] יש ביקוש עומס של 50 weightKg.
  • ל-shipments[1] יש ביקוש עומס של 10 weightKg.
  • ל-shipments[2] יש ביקוש עומס של 80 weightKg.
  • מגבלת הטעינה של vehicles[0] היא 100 weightKg.

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

{
  "routes": [
    {
      "vehicleStartTime": "2023-01-13T16:00:00Z",
      "vehicleEndTime": "2023-01-13T16:43:27Z",
      "visits": [
        {
          "isPickup": true,
          "startTime": "2023-01-13T16:00:00Z",
          "detour": "0s",
          "loadDemands": {
            "weightKg": {
              "amount": "50"
            }
          }
        },
        {
          "shipmentIndex": 1,
          "isPickup": true,
          "startTime": "2023-01-13T16:02:30Z",
          "detour": "150s",
          "loadDemands": {
            "weightKg": {
              "amount": "10"
            }
          }
        },
        {
          "startTime": "2023-01-13T16:08:55Z",
          "detour": "150s",
          "loadDemands": {
            "weightKg": {
              "amount": "-50"
            }
          }
        },
        {
          "shipmentIndex": 1,
          "startTime": "2023-01-13T16:16:37Z",
          "detour": "343s",
          "loadDemands": {
            "weightKg": {
              "amount": "-10"
            }
          }
        },
        {
          "shipmentIndex": 2,
          "isPickup": true,
          "startTime": "2023-01-13T16:27:07Z",
          "detour": "1627s",
          "loadDemands": {
            "weightKg": {
              "amount": "80"
            }
          }
        },
        {
          "shipmentIndex": 2,
          "startTime": "2023-01-13T16:36:26Z",
          "detour": "0s",
          "loadDemands": {
            "weightKg": {
              "amount": "-80"
            }
          }
        }
      ],
      "transitions": [
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-13T16:00:00Z",
          "vehicleLoads": {
            "weightKg": {}
          }
        },
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2023-01-13T16:02:30Z",
          "vehicleLoads": {
            "weightKg": {
              "amount": "50"
            }
          }
        },
        {
          "travelDuration": "235s",
          "travelDistanceMeters": 795,
          "waitDuration": "0s",
          "totalDuration": "235s",
          "startTime": "2023-01-13T16:05:00Z",
          "vehicleLoads": {
            "weightKg": {
              "amount": "60"
            }
          }
        },
        {
          "travelDuration": "212s",
          "travelDistanceMeters": 791,
          "waitDuration": "0s",
          "totalDuration": "212s",
          "startTime": "2023-01-13T16:13:05Z",
          "vehicleLoads": {
            "weightKg": {
              "amount": "10"
            }
          }
        },
        {
          "travelDuration": "380s",
          "travelDistanceMeters": 1190,
          "waitDuration": "0s",
          "totalDuration": "380s",
          "startTime": "2023-01-13T16:20:47Z",
          "vehicleLoads": {
            "weightKg": {}
          }
        },
        {
          "travelDuration": "409s",
          "travelDistanceMeters": 1371,
          "waitDuration": "0s",
          "totalDuration": "409s",
          "startTime": "2023-01-13T16:29:37Z",
          "vehicleLoads": {
            "weightKg": {
              "amount": "80"
            }
          }
        },
        {
          "travelDuration": "171s",
          "travelDistanceMeters": 665,
          "waitDuration": "0s",
          "totalDuration": "171s",
          "startTime": "2023-01-13T16:40:36Z",
          "vehicleLoads": {
            "weightKg": {}
          }
        }
      ],
      "metrics": {
        "performedShipmentCount": 3,
        "travelDuration": "1407s",
        "waitDuration": "0s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "1200s",
        "totalDuration": "2607s",
        "travelDistanceMeters": 4812,
        "maxLoads": {
          "weightKg": {
            "amount": "80"
          }
        }
      },
      "routeCosts": {
        "model.vehicles.cost_per_kilometer": 48.12,
        "model.vehicles.cost_per_hour": 28.966666666666665
      },
      "routeTotalCost": 77.086666666666659
    }
  ],
  "metrics": {
    "aggregatedRouteMetrics": {
      "performedShipmentCount": 3,
      "travelDuration": "1407s",
      "waitDuration": "0s",
      "delayDuration": "0s",
      "breakDuration": "0s",
      "visitDuration": "1200s",
      "totalDuration": "2607s",
      "travelDistanceMeters": 4812,
      "maxLoads": {
        "weightKg": {
          "amount": "80"
        }
      }
    },
    "usedVehicleCount": 1,
    "earliestVehicleStartTime": "2023-01-13T16:00:00Z",
    "latestVehicleEndTime": "2023-01-13T16:43:27Z",
    "totalCost": 77.086666666666659,
    "costs": {
      "model.vehicles.cost_per_hour": 28.966666666666665,
      "model.vehicles.cost_per_kilometer": 48.12
    }
  }
}
    

אילוצי הטעינה שנוספו משפיעות על הסדר של visits:

  1. בוצע איסוף של shipment[0]
  2. בוצע איסוף של shipment[1]
  3. shipment[0] נמסר
  4. shipment[1] נמסר
  5. בוצע איסוף של shipment[2]
  6. shipment[2] נמסר

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

כל רשומת visits כוללת את השינוי בעומס הרכב כתוצאה מהשלמת Visit. ערכי טעינה חיוביים מייצגים את טעינת המשלוח, ואילו ערכים שליליים מייצגים את הסרת המשלוח.

כל רשומת transitions כוללת את סך טעינת הרכב במהלך Transition. לדוגמה, ל-transitions[2] יש עומס weightKg של 60, שמייצג את העומס המשולב של shipment[0] ו-shipment[1].

אובייקטים של מדדים routes[0].metrics ו-metrics.aggregatedRouteMetrics כוללים את המאפיין maxLoads. הערך לסוג weightKg הוא 80, והוא מייצג את החלק במסלול של הרכב שהוביל את shipments[2] ליעד המשלוח.

מגבלות מגבלת עומס רך

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

למגבלות על עומס רך יש מספר שימושים, כמו:

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

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