OptimizeToursRequest
מחילה מגבלות על:
- משלוחים, שמשפיעים על אופן ביצוע המשלוחים
- כלי רכב, שמשפיעים על אופן החישוב של מסלולי כלי רכב
- ברמה גלובלית, משפיעה גם על כלי רכב וגם על משלוחים.
המדריך הזה מתמקד באילוץ חיוני בנושא משלוחים: חלונות זמן.
חלונות זמן הם סוג של אילוץ שצריך לציין בהודעה OptimizeToursRequest
(REST, gRPC) כדי לציין מגבלות מבוססות-זמן על פעילויות של משלוחים. סוג האילוץ הזה משפיע גם על המועד ועל האופן שבו אפשר לבצע את המשלוח, וגם על הקצאת הרכב למשלוח. בהתאם לאילוצים האלה, הכלי לאופטימיזציה נותן עדיפות לכלי הרכב שיכולים לעמוד בצורה הטובה ביותר באילוצי הזמן של המשלוח.
אילוצים על משלוחים: חלונות זמן
מציינים מתי אפשר לבצע איסוף או משלוח בהודעה Shipment.VisitRequest
באופן הבא:
- שימוש במאפיין
timeWindows
בהודעה (REST, gRPC) - מציינים את שעת ההתחלה ושעת הסיום בהודעה
TimeWindow
(REST, gRPC).
דוגמה לבקשה עם מגבלות של חלון זמן
בדוגמה הזו מוצגות שלוש משלוחים שונים, לכל אחד מהם חלון זמן משלו. כדי לשמור על הסבר פשוט, בדוגמה הזו מגדירים חלונות זמן ב-deliveries
בלבד, אבל אפשר להחיל חלונות זמן גם על איסוף. אפשר לציין כמה חלונות זמן, אבל בדוגמה הזו נעשה שימוש בחלון אחד לכל העברה VisitRequest
.
דוגמה לבקשה עם חלונות זמן
{ "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 } ] } }
דוגמה לתגובה עם מגבלות של חלון זמן
בתשובה לדוגמה, שעת ההתחלה ושעת הסיום של הרכב הן 17:35:50 ו-18:17:24, בהתאמה. הזמנים האלה משקפים את הזמן המינימלי שנדרש לצורך הפעלת הרכב שצוין בבקשה כ-costPerHour
, תוך עמידה בכל האילוצים של חלון הזמן, שהאופטימיזטור מחשב. שימוש בשעה 17:35:50 כזמן התחלה מבטל את הצורך ברכב להמתין במיקום הביקור עד שחלון הזמן של הביקור יתחיל. הערך הזה מופיע בתגובה כערכים של waitDuration
ששווים לאפס.
הצגת תגובה לבקשת הדוגמה עם חלונות זמן
{ "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 } } }
חלונות הזמן הזמינו את visits
של הרכב כדי שהמשלוחים עם חלונות הזמן המוקדמים ביותר יסופקו ראשונים.
shipments[2]
נמסר בשעה 17:50shipments[1]
נמסר בשעה 18:00shipments[0]
נמסר בשעה 18:07
בבקשת הדוגמה מצוינות מגבלות קשות של חלונות זמן, שמחייבות שההעברות יושלמו בחלונות האלה. אם אי אפשר להשלים את VisitRequests
של משלוח מסוים בחלון הזמן שלו או שהדבר לא משתלם, הכלי לאופטימיזציה מדלג על המשלוח. אם המשלוח כולל את הערך penaltyCost
, כלי האופטימיזציה מוסיף אותו לעלויות שמדווחות בתגובה metrics
. אחרת, המאפיין skippedMandatoryShipmentCount
של ההודעה OptimizeToursResponse
(REST, gRPC) יגדל.
אם משנים את חלונות הזמן על ידי העברת החלון של shipment[1]
לכמה שעות מאוחר יותר (ל-21:00 מ-18:00), התוצאות יהיו שונות, כפי שמתואר בדוגמאות הבאות.
הצגת בקשה לדוגמה עם חלונות זמן שאי אפשר לעמוד בהם
{ "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 } ] } }
הצגת תשובה לבקשה השנייה לדוגמה עם חלונות זמן שבהם מדלגים על משלוח
{ "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 } } }
בדוגמה הזו, חלון הזמן המאוחר יותר גרם לדילוג על shipment[1]
, כי זמן ההפעלה הנוסף של הרכב שנדרש כדי להשלים את מסירת המשלוח במסגרת חלון הזמן שצוין חרג מדמי המשלוח.
עלות הקנס על shipment[1]
מופיעה בmetrics.costs
, והמדד שלו מופיע ב-skippedShipments
.
אילוצים רכים של חלון זמן
כפי שצוין בקצרה בקטע פרמטרים של מודל עלות, אפשר להחיל חלונות זמן כמגבלות רכות. מגבלות רכות שונות ממגבלות קשות באופן הבא:
- אילוצים קשיחים: אי אפשר להפר אותם, והאופטימיזטור לא מציע פתרון שמפר את האילוץ, גם אם המשמעות היא דילוג על משלוח.
- מגבלות רכות: יכולות להיפר, כלומר הכלי לאופטימיזציה עשוי לספק פתרון שמפר מגבלה רכה. עם זאת, כלי האופטימיזציה מחיל עלות גם על כל הפרה. את העלות הזו מספקים כמאפיין נוסף בחלון הזמן, בדרך כלל כעלות לשעה בכל שעה לפני או אחרי חלון הזמן שבו הפעילות מתרחשת.
כדי לרכך את חלונות הזמן, משתמשים ב-softStartTime
או ב-softEndTime
במקום ב-startTime
או ב-endTime
, בהתאמה, ומגדירים את הערך costPerHourBeforeSoftStartTime
או costPerHourAfterSoftEndTime
.
מומלץ להשתמש באילוצים רכים של חלון זמן כשהאיסוף או המסירה צריכים להתרחש בחלון זמן מסוים, אבל אין צורך מוחלט שהאיסוף או המסירה יתרחשו בחלון הזמן הזה. אפשר להשתמש יחד באילוצים קשיחים ורכים של חלונות זמן כדי להביע את היעדים העסקיים. לדוגמה:
- חלון זמן קשה: מציין את שעות הפעילות של הלקוח, למשל מ-9:00 עד 17:00.
- חלון זמן משוער: מציין את מסגרת הזמן למשלוח או לאיסוף, בהתאם להודעה שנשלחה ללקוח, למשל 9:00 עד 13:00.
בדוגמה הזו, המערכת מקלה על אילוץ מועד ההתחלה של המשלוח שקודם דילגה עליו כי חלון הזמן שלו התחיל מאוחר מדי. גם זמני הסיום של חלונות הזמנים של שאר המשלוחים הושהו.
הצגת בקשה לדוגמה עם חלונות של שעות מוקשות ורך
{ "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 } ] } }
הצגת תשובה לבקשה לדוגמה עם חלונות זמן לפגישות ביניים
{ "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 } } }
בדוגמה עם אילוצים קשיחים בלבד על חלון הזמן, ההודעה shipment[1]
פשוט לא נשלחה. אם תורידו את האילוצים, ההודעה תישלח לפני תחילת חלון הזמן. באופן דומה, הארכת זמני הסיום של שאר המשלוחים אפשרה את מסירת ההזמנה של shipment[2]
אחרי סיום חלון הזמנים שלה.
במקביל, גם העלויות וגם מספר המשלוחים הכוללים השתנו:
totalCost
: ירדה מ-81.283 ל-64.797- סה"כ משלוחים שהושלמו: עלייה מ-2 ל-3
האופטימיזטור מצא פתרון זול יותר כי האילוצים של חלון הזמן הושהו בהשוואה לדוגמה הקודמת.
לבסוף, המאפיין metrics.costs
כולל גם מפתח חדש כדי לציין את העלות בפועל שנצברו על סמך המכפלה של האילוץ ומשך הזמן שבו חלון המסירה היה חסר. כלומר:
costPerHourBeforeSoftStartTime
מתוך 2.0- הזמן שחלף בין מועד המסירה בפועל לבין תחילת חלון הזמן: 2.83583 שעות
תוצאה:
model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time
:
5.6716666666666669.
המדדים האלה מאפשרים לבצע ניתוח עלויות כדי לראות את הפשרה בין אילוצים קשיחים לאילוצים רכים, וכך לשנות את האילוצים כך שיתאימו יותר לכללי העסק הספציפיים שלכם. במקרה כזה, העלות הכוללת נמוכה מ-shipment[1].penalty_cost
של 20.0. צוות האופטימיזציה זיהה שחסכוני יותר לספק את המשלוח מוקדם יותר מאשר לדלג על המשלוח.