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 כולל גם מפתח חדש שמציין את העלות בפועל שנוצרה על סמך מכפלת האילוץ ואורך הזמן שחלף מאז חלון המסירה. כלומר:
-
costPerHourBeforeSoftStartTime2.0 ומעלה - הזמן שחלף בין המסירה בפועל לבין תחילת חלון הזמן: 2.83583 שעות
תוצאה:
model.shipments.deliveries.time_windows.cost_per_hour_before_soft_start_time:
5.6716666666666669.
המדדים האלה מאפשרים לכם לבצע ניתוח עלויות כדי לראות את האיזון בין מגבלות מחמירות למגבלות גמישות. כך תוכלו להתאים את המגבלות כך שיתאימו יותר לכללים העסקיים הספציפיים שלכם. במקרה הזה, העלות הכוללת נמוכה מ-shipment[1].penalty_cost של 20.0. הכלי לאופטימיזציה זיהה שמשתלם יותר לשלוח את המשלוח מוקדם יותר מאשר לדלג על המשלוח.