Demandas y límites de carga

Desarrolladores del Espacio Económico Europeo (EEE)

En esta guía, se describen loadDemands y loadLimits, y cómo se relacionan entre sí.

Como se mencionó en Restricciones de la ventana de hora de retiro y entrega, el mensaje OptimizeToursRequest (REST, gRPC) contiene varias propiedades que especifican restricciones sobre el problema que se optimiza. Varias propiedades de OptimizeToursRequest representan restricciones de carga.

Los vehículos y los envíos tienen propiedades físicas que se deben tener en cuenta cuando se planifica una ruta.

  • Vehículos: La propiedad loadLimits especifica la carga máxima que puede soportar el vehículo. Consulta la documentación del mensaje Vehicle (REST, gRPC).
  • Shipments: La propiedad loadDemands especifica cuánta carga consume un envío determinado. Consulta la documentación del mensaje Shipment (REST, gRPC).

En conjunto, estas dos restricciones permiten que el optimizador asigne los envíos a los vehículos de forma adecuada y de la manera que mejor se adapte a la capacidad de tu flota y a las demandas de envíos.

En el resto de este documento, se analizan loadLimits y loadDemands en detalle.

Demandas y límites de carga: tipos

Cada restricción de demanda y límite de carga se expresa en términos de un tipo.

Puedes proporcionar tu propio conjunto de tipos de carga, como en los siguientes ejemplos:

  • peso
  • Volumen
  • Mediciones lineales
  • Nombres de los artículos o equipos que se transportan

En esta guía, se usa weightKg como ejemplo de tipo.

Tanto Shipment.loadDemands como Vehicle.loadLimits usan el tipo Protocol Buffersmap, con claves string que representan los tipos de carga.

Los valores de Shipment.loadDemands usan el mensaje Load (REST, gRPC). El mensaje Load tiene una sola propiedad amount que representa la capacidad necesaria para completar el envío en el tipo especificado.

Los valores de Vehicle.loadLimits usan el mensaje LoadLimit (REST, gRPC). El mensaje LoadLimit tiene varias propiedades, y maxLoad representa la capacidad de carga máxima del vehículo en el tipo especificado.

El loadDemands de un envío consume el loadLimits del vehículo asignado solo si ambos tienen claves de tipo de carga coincidentes. Por ejemplo, un envío con loadDemands de:

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

requiere 50 unidades de carga del tipo weightKg para que se complete el envío. Un vehículo con loadLimits de lo siguiente:

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

puede completar el envío, ya que el maxLoad del vehículo en el tipo weightKg es mayor o igual que el loadDemands del envío en el tipo weightKg. Sin embargo, un vehículo con loadLimits de:

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

implícitamente, tiene una capacidad de weightKg ilimitada debido a la ausencia de un límite de carga de weightKg, por lo que el vehículo no está limitado por la demanda de peso del envío.

Transferencia de carga entre envíos y vehículos

A medida que los vehículos recogen y entregan los envíos, el loadDemand del envío se transfiere entre el envío y el vehículo. Puedes ver las cargas del vehículo en la entrada routes.transitions del mensaje OptimizeToursResponse (REST, gRPC) para un vehículo determinado. La secuencia es la siguiente:

  1. La capacidad de carga requerida se define para el envío como un loadDemand.
  2. El vehículo asignado recoge el envío y el vehicleLoads del vehículo aumenta en la cantidad del loadDemand del envío. Esta transferencia se representa con un visits.loadDemands positivo en el mensaje de respuesta.
  3. El vehículo entrega el envío y su vehicleLoads disminuye en la cantidad del loadDemand del envío entregado. Esta transferencia se representa con negativo visits.loadDemands en el mensaje de respuesta.

El vehicleLoads de un vehículo no puede superar su loadLimits especificado en ningún punto de su ruta.

Un ejemplo completo con demandas y límites de carga

Consulta un ejemplo de solicitud con límites y demandas de carga

{
  "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
          }
        }
      }
    ]
  }
}
    

La solicitud de ejemplo contiene varios parámetros relacionados con la carga:

  • shipments[0] tiene una demanda de carga de 50 weightKg.
  • shipments[1] tiene una demanda de carga de 10 weightKg.
  • shipments[2] tiene una demanda de carga de 80 weightKg.
  • vehicles[0] tiene un límite de carga de 100 weightKg.

Ver una respuesta a la solicitud con las demandas y los límites de carga

{
  "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
    }
  }
}
    

Las restricciones de carga agregadas afectan el orden de visits de la siguiente manera:

  1. Se recoge shipment[0]
  2. Se recoge shipment[1]
  3. Se entregó shipment[0]
  4. Se entregó shipment[1]
  5. Se recoge shipment[2]
  6. Se entregó shipment[2]

Este pedido refleja que el vehículo no puede completar tres envíos al mismo tiempo porque su loadDemands total supera el loadLimits del vehículo.

Cada entrada de visits incluye el cambio en la carga del vehículo que resulta de la finalización de Visit. Los valores de carga positivos representan la carga del envío, mientras que los valores negativos representan la descarga del envío.

Cada entrada de transitions incluye la carga total del vehículo durante el Transition. transitions[2], por ejemplo, tiene una carga de weightKg de 60, que representa las cargas combinadas de shipment[0] y shipment[1].

Los objetos de métricas routes[0].metrics y metrics.aggregatedRouteMetrics incluyen una propiedad maxLoads. El valor del tipo weightKg es 80, lo que representa la parte de la ruta del vehículo que transportó shipments[2] a su ubicación de entrega.

Restricciones de límite de carga flexible

Al igual que con los períodos descritos en Restricciones de períodos de retiro y entrega, las restricciones de límite de carga tienen variantes estrictas y flexibles. La propiedad maxLoad del mensaje LoadLimit expresa una restricción estricta: El vehículo nunca debe transportar una carga que exceda el valor de maxLoad en el tipo especificado. Las propiedades softMaxLoad y costPerUnitAboveSoftMax expresan una restricción flexible, en la que cada unidad que supera softMaxLoad genera un costo de costPerUnitAboveSoftMax.

Las restricciones de límite de carga flexible tienen varios usos, como los siguientes:

  • Balancear los envíos en más vehículos de los necesarios cuando es rentable hacerlo
  • Expresar la preferencia del conductor por la cantidad de artículos que puede recoger y entregar cómodamente en una ruta determinada
  • Cargar los vehículos por debajo de su capacidad física máxima para limitar el desgaste y reducir los costos de mantenimiento

Las restricciones de límite de carga flexibles y rígidas se pueden usar en conjunto. Por ejemplo, un límite de carga estricto puede expresar el peso máximo de carga que un vehículo puede transportar de forma segura o la cantidad máxima de artículos que caben en un vehículo a la vez, mientras que un límite de carga flexible puede ser el peso o la cantidad máximos de artículos que dificultarían la capacidad del conductor para colocar todo en el vehículo.