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:50 -
shipments[1]
доставляются в 18:00 -
shipments[0]
доставлено в 18:07
В примере запроса указаны жесткие ограничения временных окон, требующие, чтобы доставки были завершены в пределах этих окон. Если завершение VisitRequests
отправки в пределах любого из ее временных окон нецелесообразно или экономически невыгодно, оптимизатор пропускает отправку. Если отправка имеет penaltyCost
, оптимизатор добавляет ее к расходам, указанным в metrics
ответа. В противном случае свойство skippedMandatoryShipmentCount
сообщения OptimizeToursResponse
( REST , gRPC ) увеличивается.
Если изменить временные окна, сдвинув окно shipment[1]
на несколько часов вперед (с 18:00 на 21: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. Оптимизатор определил, что более рентабельно доставить груз раньше, чем пропустить груз.