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. Оптимизатор определил, что доставить партию раньше экономически выгоднее , чем пропустить ее.