Single Vehicle

Jimmy and Sally have just started a lighting company to service a number of commercial addresses. Lucky for them, their first major assignment is a government contract. They start and end their day at the White House, and here are the places they were assigned to service.

  • The Washington Monument: 38.889484, -77.035278
  • Ford’s Theatre: 38.8967, -77.0257
  • Abe Lincoln Memorial: 38.8893, -77.0502
  • The Vietnam Memorial: 38.8913, -77.0477
  • The Thomas Jefferson Memorial: 38.8814, -77.0365

We begin by building out a JSON payload and add the locations of the job sites as a simple array of locations objects:

"locations": [
    {
      "longitude": -77.0502,
      "latitude": 38.8893,
      "location_id": "Lincoln Memorial"
    },
    {
      "longitude": -77.035278,
      "latitude": 38.889484,
      "location_id": "Washington Monument"
    },
    {
      "longitude": -77.0257,
      "latitude": 38.8967,
      "location_id": "Fords Theater"
    },
    {
      "longitude": -77.0477,
      "latitude": 38.8913,
      "location_id": "Vietnam Memorial"
    },
    {
      "longitude": -77.0365,
      "latitude": 38.8814,
      "location_id": "Thomas Jefferson Memorial"
    },
    {
      "longitude": -77.0365,
      "latitude": 38.8977,
      "location_id": "White House"
    }
  ],

Jimmy and Sally also think about the time they want to start and end their day. They estimate the amount of time (in seconds) required to service each order and add this as the duration. They also prioritize the orders since there is a chance they may not be able to service all the orders in a single day (shift). Note that an order is directly related to a location. Conceptually, orders represent a request for service (such as delivering goods to a store), and a higher priority indicates that if it is not possible to visit all orders, then the higher priority orders should be favored.

"orders": [
    {
      "order_id": "Service Washington Monument",
      "duration": 7200,
      "location_id": "Washington Monument",
      "priority": 10
    },
    {
      "order_id": "Service Fords Theatre",
      "duration": 7200,
      "location_id": "Fords Theater",
      "priority": 5
    },
    {
      "order_id": "Service the Abe Lincoln Memorial",
      "duration": 7200,
      "location_id": "Lincoln Memorial",
      "priority": 3
    },
    {
      "order_id": "Service the Vietnam Memorial",
      "duration": 3600,
      "location_id": "Vietnam Memorial",
      "priority": 1
    },
    {
      "order_id": "Service the Thomas Jefferson Memorial",
      "duration": 3600,
      "location_id": "Thomas Jefferson Memorial",
      "priority": 2
    }
  ]

With the locations and orders specified, the next step is to define constraints. the Optimization API has an extensive library of constraints that allows you to very precisely define what a “good” solution looks like. In this case, the objective is simple: visit as many orders as possible, minimize travel time, and make sure that if we can’t visit all the orders, we at least visit the highest priority orders. Each constraint has a penalty which determines how important it is to satisfy. For many constraints, the degree of the constraint violation can also be included in the penalty via the “violation_increment”. We do this below with the travel_time constraint so that each second that a vehicle travels is assessed a penalty of 1. This gives us a standard way of thinking about penalties and is recommended in all use cases. The visit_range constraint tells the Optimization API that we want to visit as many orders as possible. Since the penalty for this constraint is 10,000, if we miss an order then the solution is assessed a penalty of 10,000. The order_priority constraint indicates that orders with higher priority should be favored if all orders cannot be serviced (see the documentation of this constraint for additional details).

"constraints": [
    {
      "constraint_type": "travel_time",
      "constraint_name": "Minimize travel time",
      "violation_increment": 1,
      "penalty_per_violation": 1,
      "max_travel_time_seconds": 0
    },
    {
      "constraint_type": "visit_range",
      "constraint_name": "Visit all orders",
      "penalty_per_violation": 10000
    },
    {
      "constraint_type": "order_priority",
      "constraint_name": "Visit high priority orders",
      "violation_increment": 1,
      "penalty_per_violation": 1000
    }
  ]

Sally and Jimmy also need to specify a vehicle and their shift. The shift is how the Optimization API knows when the vehicle and associated workers are allowed to operate. In this case, Sally and Jimmy define the shift as being from 8:30am to 5:00pm, and they also specify where the shift must start and end (the White House). Note that a shift can start and end at somewhere other than an order, for example a worker’s home address. Also, they specify vehicle type “car”. If routes for trucks and other restricted vehicles are desired, then the vehicle type can be set to “truck” with other parameters in order to ensure that the routes returned by the Optimization API are compliant.

"vehicles": [
    {
      "vehicle_id": "Jim and Sally's Car",
      "type": "car",
      "shifts": [
        {
          "shift_end": "2018-10-01T17:00:00-04:00",
          "shift_id": "jimmy_sally_shift",
          "shift_start": "2018-10-01T08:30:00-04:00",
          "start_location_id": "White House",
          "end_location_id": "White House"
        }
      ]
    }
  ],

You’ll notice that the vehicles parameter is actually an array. This is because the Optimization API can support multiple vehicles for a given problem. Additionally, each vehicle can operate for multiple shifts so that the Optimization API is able to solve problem involving many disjoint shifts. We will return to this more advanced functionality in later examples. Putting it all together, the payload looks like this.

{
   "constraints": [
     {
       "constraint_type": "travel_time",
       "constraint_name": "Minimize travel time",
       "violation_increment": 1,
       "penalty_per_violation": 1,
       "max_travel_time_seconds": 0
     },
     {
       "constraint_type": "visit_range",
       "constraint_name": "Visit all orders",
       "violation_increment": 1,
       "penalty_per_violation": 10000
     },
     {
       "constraint_type": "order_priority",
       "constraint_name": "Visit high priority orders",
       "violation_increment": 1,
       "penalty_per_violation": 1000
     }
   ],
   "vehicles": [
     {
       "vehicle_id": "Jim and Sally's Car",
       "type": "car",
       "shifts": [
         {
           "shift_end": "2018-10-01T17:00:00-04:00",
           "shift_id": "jimmy_sally_shift",
           "shift_start": "2018-10-01T08:30:00-04:00",
           "start_location_id": "White House",
           "end_location_id": "White House"
         }
       ]
     }
   ],
   "locations": [
     {
       "longitude": -77.0502,
       "latitude": 38.8893,
       "location_id": "Lincoln Memorial"
     },
     {
       "longitude": -77.035278,
       "latitude": 38.889484,
       "location_id": "Washington Monument"
     },
     {
       "longitude": -77.0257,
       "latitude": 38.8967,
       "location_id": "Fords Theater"
     },
     {
       "longitude": -77.0477,
       "latitude": 38.8913,
       "location_id": "Vietnam Memorial"
     },
     {
       "longitude": -77.0365,
       "latitude": 38.8814,
       "location_id": "Thomas Jefferson Memorial"
     },
     {
       "longitude": -77.0365,
       "latitude": 38.8977,
       "location_id": "White House"
     }
   ],
   "orders": [
     {
       "order_id": "Service Washington Monument",
       "duration": 7200,
       "location_id": "Washington Monument",
       "priority": 10
     },
     {
       "order_id": "Service Fords Theatre",
       "duration": 7200,
       "location_id": "Fords Theater",
       "priority": 5
     },
     {
       "order_id": "Service the Abe Lincoln Memorial",
       "duration": 7200,
       "location_id": "Lincoln Memorial",
       "priority": 3
     },
     {
       "order_id": "Service the Vietnam Memorial",
       "duration": 3600,
       "location_id": "Vietnam Memorial",
       "priority": 1
     },
     {
       "order_id": "Service the Thomas Jefferson Memorial",
       "duration": 3600,
       "location_id": "Thomas Jefferson Memorial",
       "priority": 2
     }
   ]
 }

img

Four of the five orders are visited. The lowest priority Vietnam Memorial (red pin) is missed, but otherwise the route is efficient in terms of travel and meets our objectives.

Urgency

The urgency constraint is different from Order Priority in that it tries to satisfy visiting more urgent orders earlier during the planning period (represented by a vehicle’s shift object). In the original solution, we visited the Lincoln Memorial as the last order. Now suppose we need to visit the Lincoln Memorial and Ford’s Theater earlier in the planning horizon. We could either add time windows for these orders or use the urgency constraint to encourage visiting them earlier in the day. First we will demonstrate this capability by adding urgency values for these two orders and then adding a new constraint of type urgency. The new request is below (only change to first request is addition of urgency constraint).

{
  "constraints": [
    {
      "constraint_type": "travel_time",
      "constraint_name": "Minimize travel time",
      "violation_increment": 1,
      "penalty_per_violation": 1,
      "max_travel_time_seconds": 0
    },
    {
      "constraint_type": "visit_range",
      "constraint_name": "Visit all orders",
      "violation_increment": 1,
      "penalty_per_violation": 10000
    },
    {
      "constraint_type": "order_priority",
      "constraint_name": "Visit high priority orders",
      "violation_increment": 1,
      "penalty_per_violation": 1000
    },
    {
      "constraint_type": "urgency",
      "constraint_name": "Visit urgent orders first",
      "violation_increment": 1,
      "penalty_per_violation": 1000
    }
  ],
  "vehicles": [
    {
      "vehicle_id": "Jim and Sally's Car",
      "type": "car",
      "shifts": [
        {
          "shift_end": "2018-10-01T17:00:00-04:00",
          "shift_id": "jimmy_sally_shift",
          "shift_start": "2018-10-01T08:30:00-04:00",
          "start_location_id": "White House",
          "end_location_id": "White House"
        }
      ]
    }
  ],
  "locations": [
    {
      "longitude": -77.0502,
      "latitude": 38.8893,
      "location_id": "Lincoln Memorial"
    },
    {
      "longitude": -77.035278,
      "latitude": 38.889484,
      "location_id": "Washington Monument"
    },
    {
      "longitude": -77.0257,
      "latitude": 38.8967,
      "location_id": "Fords Theater"
    },
    {
      "longitude": -77.0477,
      "latitude": 38.8913,
      "location_id": "Vietnam Memorial"
    },
    {
      "longitude": -77.0365,
      "latitude": 38.8814,
      "location_id": "Thomas Jefferson Memorial"
    },
    {
      "longitude": -77.0365,
      "latitude": 38.8977,
      "location_id": "White House"
    }
  ],
  "orders": [
    {
      "order_id": "Service Washington Monument",
      "duration": 7200,
      "location_id": "Washington Monument",
      "priority": 10
    },
    {
      "order_id": "Service Fords Theatre",
      "duration": 7200,
      "location_id": "Fords Theater",
      "priority": 5,
      "urgency": 90
    },
    {
      "order_id": "Service the Abe Lincoln Memorial",
      "duration": 7200,
      "location_id": "Lincoln Memorial",
      "priority": 3,
      "urgency": 90
    },
    {
      "order_id": "Service the Vietnam Memorial",
      "duration": 3600,
      "location_id": "Vietnam Memorial",
      "priority": 1
    },
    {
      "order_id": "Service the Thomas Jefferson Memorial",
      "duration": 3600,
      "location_id": "Thomas Jefferson Memorial",
      "priority": 2
    }
  ]
}

img

img

The high urgency orders at Ford’s Theater and the Lincoln Memorial are now visited earlier in the day. The route is less efficient from a travel perspective but the sequence better meets our objectives. The Vietnam memorial is still not visited since we still have the order_priority constraint in place.

Scheduled Appointments

Sally and Jimmy received some changes – they will also have to service the Ravens football stadium in Baltimore. They reflected on how they might change their trip around. Some of the job sites are more complex than others and require more time. Instead of spending one quick day in DC, they need to spread their trip over two days. As such, they reserve a night or two at a hotel north of DC. Sally appends this location (39.064295, -76.965838) to their list. They will start each day at the hotel, plan on ending at the White House on day 1 to do some sightseeing, and will also plan on celebrating the end of the DC work by going to a sushi restaurant in Annapolis – this will be the end_location of their shift on the second day.

Let’s summarize how these stipulations change the the Optimization API payload.

  • LOCATIONS — Not much changes here. Sally just adds the latitude and longitude of the stadium, hotel and restaurant to the array.

  • SHIFTS — Sally adds another shift to the shift array to account for the extra day. Additionally, with all this new work they will be hungry so lunch breaks are now added each day from noon to 1pm.

  • **ORDERS — ** The amount of work changes at each order, so the durations change, we add an appointment at the Lincoln Memorial and Vietnam Memorial, and also add a new long job at the Ravens stadium. Note that adding the appointment to the order doesn’t guarantee that we will visit it – we need a constraint to enforce that.

  • CONSTRAINTS — Sally now specifies a scheduled_appointment constraint with a high penalty to ensure the appointments are met.

{
  "orders": [
    {
      "location_id": "Washington Monument",
      "order_id": "Service Washington Monument",
      "duration": 3600
    },
    {
      "location_id": "Fords Theater",
      "order_id": "Service Fords Theatre",
      "duration": 3600
    },
    {
      "appointments": [
        {
          "appointment_start": "2018-10-01T14:15:00-04:00",
          "appointment_end": "2018-10-01T16:15:00-04:00"
        }
      ],
      "location_id": "Lincoln Memorial",
      "order_id": "Service the Abe Lincoln Memorial",
      "duration": 7200
    },
    {
      "appointments": [
        {
          "appointment_start": "2018-10-02T10:15:00-04:00",
          "appointment_end": "2018-10-02T11:15:00-04:00"
        }
      ],
      "location_id": "Vietnam Memorial",
      "order_id": "Service the Vietnam Memorial",
      "duration": 3600
    },
    {
      "location_id": "Thomas Jefferson Memorial",
      "order_id": "Service the Thomas Jefferson Memorial",
      "duration": 7200
    },
    {
      "location_id": "Ravens Stadium",
      "order_id": "Service Ravens Stadium",
      "duration": 10000
    }
  ],
  "constraints": [
    {
      "penalty_per_violation": 1,
      "violation_increment": 1,
      "constraint_name": "Minimize travel time",
      "max_travel_time_seconds": 0,
      "constraint_type": "travel_time"
    },
    {
      "penalty_per_violation": 10000,
      "violation_increment": 1,
      "constraint_name": "Visit all orders",
      "constraint_type": "visit_range"
    },
    {
      "penalty_per_violation": 10000,
      "constraint_name": "Meet all appointments",
      "constraint_type": "scheduled_appointment"
    }
  ],
  "locations": [
    {
      "latitude": 38.8893,
      "location_id": "Lincoln Memorial",
      "longitude": -77.0502
    },
    {
      "latitude": 38.889484,
      "location_id": "Washington Monument",
      "longitude": -77.035278
    },
    {
      "latitude": 38.8967,
      "location_id": "Fords Theater",
      "longitude": -77.0257
    },
    {
      "latitude": 38.8913,
      "location_id": "Vietnam Memorial",
      "longitude": -77.0477
    },
    {
      "latitude": 38.8814,
      "location_id": "Thomas Jefferson Memorial",
      "longitude": -77.0365
    },
    {
      "latitude": 39.278,
      "location_id": "Ravens Stadium",
      "longitude": -76.6227
    },
    {
      "latitude": 39.064295,
      "location_id": "Hotel",
      "longitude": -76.965838
    },
    {
      "latitude": 38.9779,
      "location_id": "Sushi Restaurant",
      "longitude": -76.491
    },
    {
      "latitude": 38.8977,
      "location_id": "White House",
      "longitude": -77.0365
    }
  ],
  "vehicles": [
    {
      "type": "car",
      "vehicle_id": "Jim and Sally's Car",
      "shifts": [
        {
          "shift_id": "jimmy_sally_shift day 1",
          "shift_end": "2018-10-01T17:00:00-04:00",
          "shift_start": "2018-10-01T08:30:00-04:00",
          "end_location_id": "White House",
          "break_end": [
            "2018-10-01T17:13:00-04:00"
          ],
          "break_start": [
            "2018-10-01T17:12:00-04:00"
          ],
          "start_location_id": "Hotel"
        },
        {
          "shift_id": "jimmy_sally_shift day 2",
          "shift_end": "2018-10-02T19:00:00-04:00",
          "shift_start": "2018-10-02T09:30:00-04:00",
          "end_location_id": "Sushi Restaurant",
          "break_end": [
            "2018-10-02T17:13:00-04:00"
          ],
          "break_start": [
            "2018-10-02T17:12:00-04:00"
          ],
          "start_location_id": "Hotel"
        }
      ]
    }
  ]
}

Luckily it is still possible for the pair to complete all the work, do some sightseeing and finish with a celebration in Annapolis. Since the Optimization API accounts for predicted traffic in its routing calculations, the pair takes paths between locations that minimize the estimated travel time. Nevertheless, the pair expect their total travel time to be about 39 minutes longer due to traffic over the two day period (total_traffic_time in the response).

img

The first day in purple, the second day is in green. Note that the shifts are for the same vehicle but the Optimization API allows you to have different start or end locations for individual shifts.

Time Windows

Sally and Jimmy have now received word that some of the locations they are servicing have other events planned, and they must arrive at the orders only during certain times. These are referred to as time windows and the Optimization API allows us to ensure that service at an order only begins during a time window. We remove the appointments from the existing orders and now update each order with a time window. This will lead to a less efficient route from a travel perspective, but they will now be starting service at each order only during the allowed window.

  • Orders: Each order now receives a time window.
  • Constraints: The scheduled_appointment constraint is removed in favor of a time_window constraint. Note that we use a violation_increment of 3600 in this constraint which makes the penalty depend on by how much we violate a time window. For example, if we are 3 hours late for a time window, then we will be hit with a penalty of 5000 for the initial violation plus another 3*5000 = 15,000 to penalize the fact that we are 3 hours late. The total penalty in this case is then 20,000 – equivalent to about 6 hours of travel.
{
  "constraints": [
    {
      "violation_increment": 1,
      "constraint_name": "Minimize travel time",
      "max_travel_time_seconds": 0,
      "constraint_type": "travel_time",
      "penalty_per_violation": 1
    },
    {
      "violation_increment": 1,
      "constraint_name": "Visit all orders",
      "constraint_type": "visit_range",
      "penalty_per_violation": 10000
    },
    {
      "constraint_name": "Adhere to time windows",
      "constraint_type": "time_window",
      "penalty_per_violation": 5000,
      "violation_increment": 3600
    }
  ],
  "orders": [
    {
      "location_id": "Washington Monument",
      "duration": 3600,
      "order_id": "Service Washington Monument",
      "time_windows": [
      {
        "start_time_window": "2018-10-02T12:30:00-04:00",
        "end_time_window":   "2018-10-02T15:00:00-04:00"
      }
      ]
    },
    {
      "location_id": "Fords Theater",
      "duration": 3600,
      "order_id": "Service Fords Theatre",
      "time_windows": [
      {
        "start_time_window": "2018-10-01T13:30:00-04:00",
        "end_time_window":   "2018-10-01T17:00:00-04:00"
      }
      ]
    },
    {
      "location_id": "Lincoln Memorial",
      "duration": 3600,
      "order_id": "Service the Abe Lincoln Memorial",
      "time_windows": [
      {
        "start_time_window": "2018-10-02T08:30:00-04:00",
        "end_time_window":   "2018-10-02T11:00:00-04:00"
      }
      ]
    },
    {
      "location_id": "Vietnam Memorial",
      "duration": 3600,
      "order_id": "Service the Vietnam Memorial",
      "time_windows": [
      {
        "start_time_window": "2018-10-01T08:30:00-04:00",
        "end_time_window":   "2018-10-01T17:00:00-04:00"
      }
      ]
    },
    {
      "location_id": "Thomas Jefferson Memorial",
      "duration": 7200,
      "order_id": "Service the Thomas Jefferson Memorial",
      "time_windows": [
      {
        "start_time_window": "2018-10-01T08:30:00-04:00",
        "end_time_window":   "2018-10-02T17:00:00-04:00"
      }
      ]
    },
    {
      "location_id": "Ravens Stadium",
      "duration": 10000,
      "order_id": "Service Ravens Stadium",
      "time_windows": [
      {
        "start_time_window": "2018-10-02T13:30:00-04:00",
        "end_time_window":   "2018-10-02T17:45:00-04:00"
      }
      ]
    }
  ],
  "vehicles": [
    {
      "shifts": [
        {
          "shift_end": "2018-10-01T17:00:00-04:00",
          "shift_id": "jimmy_sally_shift day 1",
          "shift_start": "2018-10-01T08:30:00-04:00",
          "start_location_id": "Hotel",
          "end_location_id": "White House",
          "break_end": [
            "2018-10-01T13:00:00-04:00"
          ],
          "break_start": [
            "2018-10-01T12:00:00-04:00"
          ]
        },
        {
          "shift_end": "2018-10-02T19:00:00-04:00",
          "shift_id": "jimmy_sally_shift day 2",
          "shift_start": "2018-10-02T09:30:00-04:00",
          "start_location_id": "Hotel",
          "end_location_id": "Sushi Restaurant",
          "break_end": [
            "2018-10-02T13:00:00-04:00"
          ],
          "break_start": [
            "2018-10-02T12:00:00-04:00"
          ]
        }
      ],
      "vehicle_id": "Jim and Sally's Car",
      "type": "car"
    }
  ],
  "locations": [
    {
      "latitude": 38.8893,
      "longitude": -77.0502,
      "location_id": "Lincoln Memorial"
    },
    {
      "latitude": 38.889484,
      "longitude": -77.035278,
      "location_id": "Washington Monument"
    },
    {
      "latitude": 38.8967,
      "longitude": -77.0257,
      "location_id": "Fords Theater"
    },
    {
      "latitude": 38.8913,
      "longitude": -77.0477,
      "location_id": "Vietnam Memorial"
    },
    {
      "latitude": 38.8814,
      "longitude": -77.0365,
      "location_id": "Thomas Jefferson Memorial"
    },
    {
      "latitude": 39.278,
      "longitude": -76.6227,
      "location_id": "Ravens Stadium"
    },
    {
      "latitude": 39.064295,
      "longitude": -76.965838,
      "location_id": "Hotel"
    },
    {
      "latitude": 38.9779,
      "longitude": -76.491,
      "location_id": "Sushi Restaurant"
    },
    {
      "latitude": 38.8977,
      "longitude": -77.0365,
      "location_id": "White House"
    }
  ]
}

Luckily it is still possible to service all the orders where the pair arrives during a time window, eat lunch, and start/end each day at the desired location. Now the workload is more spread out across each day with 3 orders serviced on each day.

img

The Full solution for the two-day situation where all time windows are honored.

imgZoomed in view of the 5 orders visited in the city. Note that time windows often force us to visit orders in a less efficient sequence than we would do otherwise.