Load demands and limits

This guide describes loadDemands and loadLimits, and how they relate to each other.

As mentioned in Pickup and Delivery Time Window Constraints, the OptimizeToursRequest message (REST, gRPC) contains a number of properties that specify constraints on the problem being optimized. Several OptimizeToursRequest properties represent load constraints.

Vehicles and shipments have physical properties that must be considered when planning a route.

  • Vehicles: The loadLimits property specifics the maximum load the vehicle can handle. See the Vehicle message's (REST, gRPC) documentation.
  • Shipments: The loadDemands property specifies how much load a given shipment consumes. See the Shipment message's (REST, gRPC) documentation.

Together, these two constraints make it possible for the optimizer to appropriately assign shipments to vehicles in a manner that best matches your fleet capacity and shipment demands.

This rest of this document discusses loadLimits and loadDemands in detail.

Load demands and limits: types

You express each load demand and limit constraint in terms of a type.

You can provide your own set of load types, like the following examples:

  • weight
  • volume
  • linear measurements
  • names of items or equipment being transported

This guide uses weightKg as an example type.

Both Shipment.loadDemands and Vehicle.loadLimits use the Protocol Buffers map type, with string keys that represent the types of load.

Shipment.loadDemands values use the Load message (REST, gRPC). The Load message has a single amount property representing how much capacity is required to complete the shipment in the specified type.

Vehicle.loadLimits values use the LoadLimit message (REST, gRPC). The LoadLimit message has several properties, with maxLoad representing the vehicle's maximum load capacity in the specified type.

A shipment's loadDemands consumes its assigned vehicle's loadLimits only if the two have matching load type keys. For example, a shipment with loadDemands of:

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

requires 50 load units in the weightKg type for the shipment to be completed. A vehicle with loadLimits of:

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

may be able to complete the shipment, as the vehicle's maxLoad in the weightKg type is greater than or equal to the shipment's loadDemands in the weightKg type. However, a vehicle with loadLimits of:

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

implicitly has unlimited weightKg capacity due to the absence of a weightKg load limit, so the vehicle is not constrained by the shipment's weight demand.

Load transfer between shipments and vehicles

As shipments are picked up and delivered by vehicles, the shipment's loadDemand transfers between the shipment and the vehicle. You can see the vehicle's loads in the OptimizeToursResponse message's (REST, gRPC)routes.transitions entry for a given vehicle. The sequence is as follows:

  1. The required load capacity is defined for the shipment as a loadDemand.
  2. The shipment is picked up by its assigned vehicle and the vehicle's vehicleLoads increases by the amount of the shipment's loadDemand. This transfer is represented by positive visits.loadDemands in the response message.
  3. The vehicle delivers the shipment and the vehicle's vehicleLoads decreases by the amount of the delivered shipment's loadDemand. This transfer is represented by negative visits.loadDemands in the response message.

A vehicle's vehicleLoads cannot exceed its specified loadLimits at any point on its route.

A complete example with load demands and limits

See an example request with load demands and limits

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

The example request contains several load-related parameters:

  • shipments[0] has a load demand of 50 weightKg.
  • shipments[1] has a load demand of 10 weightKg.
  • shipments[2] has a load demand of 80 weightKg.
  • vehicles[0] has a load limit of 100 weightKg.

See a response to the request with load demands and limits

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

The added load constraints affect the order of visits:

  1. shipment[0] is picked up
  2. shipment[1] is picked up
  3. shipment[0] is delivered
  4. shipment[1] is delivered
  5. shipment[2] is picked up
  6. shipment[2] is delivered

This order reflects that three shipments cannot be completed by the vehicle at the same time because their total loadDemands exceed the vehicle's loadLimits.

Each visits entry includes the change in vehicle load resulting from the completion of the Visit. Positive load values represent shipment loading while negative values represent shipment unloading.

Each transitions entry includes the total vehicle load during the Transition. transitions[2], for example, has a weightKg load of 60, representing the combined loads of shipment[0] and shipment[1].

Metrics objects routes[0].metrics and metrics.aggregatedRouteMetrics include a maxLoads property. The value for type weightKg is 80, representing the portion of the vehicle's route that transported shipments[2] to its delivery location.

Soft load limit constraints

As with time windows described in Pickup and Delivery Time Window Constraints, load limit constraints have hard and soft variants. The LoadLimit message's maxLoad property expresses a hard constraint: the vehicle must never carry load exceeding the maxLoad value in the specified type. Properties softMaxLoad and costPerUnitAboveSoftMax express a soft constraint, with every unit exceeding softMaxLoad incurring a costPerUnitAboveSoftMax cost.

Soft load limit constraints have several uses, such as:

  • balancing shipments across more vehicles than the minimum number necessary when it is cost effective to do so
  • expressing driver preference for the number of items they can comfortably pickup and deliver on a given route
  • loading vehicles below their maximum physical capacity to limit wear and reduce maintenance costs

Hard and soft load limit constraints can be used together. For example, a hard load limit might express the maximum weight of cargo a vehicle can safely carry or the maximum number of items that will fit in a vehicle at one time, while a soft load limit might be the maximum weight or number of items that would tax the driver's ability to fit everything in the vehicle.