Trucker - eRedekopp/trucker.alp GitHub Wiki

Overview

The Trucker agent represents a single truck driver hauling loads across the simulation area. This class contains most of the heaviest business logic in this model, and is perhaps a little bit intimidating due to its large size. This document aims to explain the Trucker agent as well as possible and demonstrate that it's really not so bad.

At its core, a Trucker simply alternates between different sub-states of Driving and Stationary. Trucks drive between randomly assigned destinations, stopping for breaks along the way to comply with regulations. Different types of destination have different properties such as its effect on the driver or the length of the stay there, but essentially they all serve as a place for a Trucker to stay for some stretch of time before starting to drive again.

Essentially everything of consequence happens at the points where a Trucker starts or stops driving. The only exception to this is when the clock hits midnight and every Trucker logs their duty hours in the past 24 hours no matter their current state.

Variables

  • destination: The city where we are retrieving/delivering our next load.
  • nextStop: A duple containing the RestType and StoppableLocation that we've decided on for our next stop along our journey. If this is the destination, then the StoppableLocation field is null.
  • gallonsConsumed: The quantity of fuel consumed since we last refuelled.
  • home: The Trucker's home city.
  • isGoingHome: Are we currently heading home? As opposed to heading toward a shipper/receiver.
  • plannedStops: A list containing all the stops that we've already planned on taking, as duples of RestType and StoppableLocation. This list does not include the next stop, which is instead stored in nextStop.
  • nextRestType: The RestType that we will take when we arrive at our next stop.
    • This variable exists so that we don't have to repeatedly decide which type of rest to take when the rest type indicated in nextStop is a Long Rest. When the Trucker stops, look at the rest type in nextStop. If it is a Long Rest, decide which rest to take and store that in this variable. Otherwise, just save the rest type in the variable directly.
  • currentRoute: Navigation information including nearby stops for the current trip, provided by JHighway.
  • restCreditHours: See the Rest Credit section of this document.
  • homeRestHours: How long is our next home rest going to be?
    • This is its own variable that gets set immediately before arriving at home and before the simulation start. It was somewhat easier to have this as its own variable so that we had a little more freedom on how to set the length of the timeout transition out of the AtHome state. In hindsight it seems like a bit of a hack.
  • Time Tracking variables
    • These are all self-explanatory, except hoursOnDutyPerDayThisPeriod which is explained in the Time Tracking section of this document.

Initialization

At startup, the Trucker agents are created and each calls its initialize function. This assigns them a random home city, creates a Storyteller agent to keep track of their events, and sets its route provider to the global JHighwayRouteProvider instance (see Routing for more on JHighway).

Each agent starts the simulation in their home city and takes a home rest before starting their first driving period. This amount of time is generated differently than it typically is when a Trucker arrives at home for a home rest -- namely, it has higher mean and stdev in its distribution. By spreading out agent start times, we hope to reduce the amount of burn-in time required (though we've never actually tested whether this is true).

Main Behaviour Loop

There are two broad loops that drive trucker behaviour. The simpler of these is the Loaded/Unloaded loop. A trucker is assigned destinations where they will pick up or drop off their next load. They alternate between Loaded (carrying a trailer to a receiver) and Unloaded (empty and travelling to a shipper to get a new load) constantly throughout the simulation. More complex is the Driving/Stationary loop, where the trucker drives toward their current destination while stopping along the way to comply with regulations. This involves some fairly complex decision processes which are explained below.

A Trucker lives a Sisyphean existence in which they can only ever be doing one of the following things:

  • Home Rest: At home, resting before starting another driving period.
  • Detention: At a shipper/receiver, waiting to start driving toward their next destination.
  • Resting: At a StoppableLocation (see Location agent documentation) resting before driving again in order to comply with regulations.
  • Driving: On the road heading toward the next stop in their journey.

Driving

While driving, a Trucker travels at a constant speed of driverMilesPerHour along the route provided by JHighway (see Routing), and consumes a constant amount of fuel driverMPG. There is no consideration of traffic, road closures, or any other driving conditions. Truckers do not perform any actions while driving other than moving along the route -- the only exception to this is when the clock hits midnight and the driver logs their duty hours in the past day.

Destination Selection

This behaviour is illustrated in the Destination Selection Logic flowchart in the private Google Drive folder for this project.

Truckers are always assigned their next city randomly, other than when they are told to go home for home rest. There are two different ways that these random cities are drawn:

Loaded

When the Trucker is Loaded, we use the gravity model found in the Main agent. This will assign a new city randomly, weighted by a combination of each city's population and distance from the Trucker's current position, and tuned by model parameters. You can find more information about the gravity model by looking at the documentation for the GravityModelDistribution package and the gravityModelTau parameter.

Unloaded

When the Trucker is Unloaded, we don't want them to have to drive too far to find their next load. Before selecting a random city, we first draw a random number to decide whether the new load can be found in the current city with probability probShipperIsInSameCityAsReceiver. If yes, the driver instantaneously arrives at the same destination again (i.e. technically starts and then stops driving, but drives for 0 distance) to pick up a new load. If no, then randomly select any city within maxMilesToShipper miles with uniform probabilities and pick up our load from there.

Home Rest

If home rest is enabled in the experiment parameters, then Trucker agents will periodically return home for an extended rest, known as a home rest. When getting their next destination in the Loaded state, Trucker agents will first check how long it's been since they started their current driving period. If they are within within 48 hours of targetDaysInDrivingPeriod, they are told to deliver the load to their home city instead of randomly drawing a new one. This behaviour is not possible when the Trucker is Unloaded; instead they will go get a new load before deciding to drive home.

Stop Selection

This behaviour is illustrated in the Truck Stop Selection Logic flowchart in the private Google Drive folder for this project. This section contains a general overview of the process; details can be found in the flow chart. The stop selection logic is critical to the model's behaviour, as it is a key driver of when/how truck stops become crowded with drivers.

Each time a Trucker starts driving, if they haven't already decided on their next stop, they run the decision process described in the Truck Stop Selection Logic flowchart. They start by making a list of stops that will result in them arriving near their preferred time in the evening and try to drive to the farthest one. If there isn't a reasonable stop in that range, then they expand the arrival time range and try again. If they still can't find a reasonable stop, then they make a list of all possible stops with any arrival time and drive to the one that maximizes their driving hours for the day. If they still aren't able to find a stop after all this, then they log an error message and drive to the nearest stop in any direction for a long rest.

Rest Selection

This behaviour is illustrated in the Rest Type Selection Logic flowchart in the private Google Drive folder for this project. This behaviour only applies when the 7/8 day rule is enabled for the experiment. When a Trucker selects their next stop through the process described above, they don't immediately choose whether their next rest will be a SleeperRest or a RestartRest. Instead they decide which one immediately before they start resting.

If the 7/8 day rule is disabled, then we always decide to take a SleeperRest and never a RestartRest. Similarly, a Trucker will never take a RestartRest anywhere except a truck stop.

If none of the above applies and we need to select which rest type to take, then we ask two questions:

  1. If I stopped right now, how many hours could I drive after taking a SleeperRest?
  2. If I stopped right now, took a SleeperRest, drove the whole number of hours from 1, then stopped for another SleeperRest, how many hours could I drive before I would need to take a third SleeperRest?

If the answer to 1 or 2 is less than 3 hours, then take a RestartRest, otherwise take a SleeperRest.

Fuelling

When a Trucker takes any sort of rest at a GasStation or TruckStop, they will check whether their fuel level (as a proportion of total capacity) is less than refuelThresholdPct. They will also stop specifically for fuel when they detect that they need it (see the Truck Stop Selection Logic flowchart), but I assume this doesn't happen very often.

Refuelling takes a constant duration of refuelTimeHours regardless of the amount of fuel required, and this time is considered to be on-duty. Truckers are still considered to be parked at the location while refuelling and don't notify the stop of their departure until they have finished. Trucker agents do not compete for fuel facilities like they do for parking. If a Trucker is parked at a GasStation or TruckStop, it is assumed that they can begin refuelling at any time regardless of the number of other trucks parked.

Time Tracking

Each Trucker keeps track of the number of hours that they were on duty in the past day each day at midnight no matter their current state. This is stored in the hoursOnDutyPerDayThisPeriod variable, which is a Map storing these numbers of hours keyed by the day on which they occurred. The main purpose of this variable is to calculate our numbers of hours for the 7/8 day rules. The variable is reset every time we take a home rest and saves the hours for all days since the start of the current driving period.

There is a small imperfection with the way that drivers track their hours. If a driver is off duty the calculation is very simple: simply return the tally of duty hours that we logged for the day. If a driver is currently driving, then we return the tally of duty hours logged plus the time since they started driving. The imperfect calculation happens when the driver is in detention at midnight. We can't know in advance whether the driver's detention hours will be returned as rest credit, and thus whether the detention hours will count as duty hours. The Trucker agent is programmed to assume that their detention hours will count as duty hours when logging at midnight, and no correction is made if that ends up not being the case. This could potentially mess with how drivers figure out their N-day rules -- I haven't done any testing on this yet.

Stops

This section discusses the process of arriving at a Location from the Trucker's perspective. This behaviour is also discussed in the Location agent documentation. Note that some of this information is better illustrated by the Truck Stop Selection Logic flowchart.

The process for arriving at a stop differs depending on whether it is a Location or a StoppableLocation. The two cases are described below. Note that any time a Trucker stops for a SleeperRest anywhere except a truck stop, they will go to the nearest truck stop en route for a short rest afterward if one is available. We imagine that in the real-world scenario the driver would use this opportunity to buy food, shower, etc. We call this type of rest an early short rest.

Stoppable Location

Inquiries

When a Trucker arrives at a StoppableLocation, they first must inquire with the stop about whether there is enough space for them. This occurs as soon as the truck arrives at the location; the Trucker has no knowledge of the StoppableLocation's state before arriving. If the Trucker is rejected from the stop, they are immediately sent away without ever entering the location or connecting to its network. After a rejection, a Trucker runs the normal process from the Truck Stop Selection Logic flowchart to search for more available stops.

When a Trucker departs from a StoppableLocation after being accepted and staying for a rest, they must send a message to the location notifying it of its departure so that it can be accounted for and removed from the network.

Networks

StoppableLocation agents have a network containing all currently-parked Trucker agents. Currently, this doesn't have any effect besides connecting and disconnecting agents as they arrive and depart. This scaffolding is here so that we can easily implement interactions between Trucker agents in future iterations of this model.

Shipper / Receiver

Detention

When a Trucker arrives at a shipper / receiver (i.e. a City), they are randomly assigned a length of time that they will be required to stay there before starting back on their journey. This is called the detention time. Time spent in detention is considered to be on duty and will count toward duty hours but not driving hours. A driver will stay in detention for the full length of time regardless of their remaining duty or driving hours at the time of their arrival.

Rest Credit

When a Trucker ends their detention and they are past their legal duty or driving hours (e.g. their duty hour limit came up while they were in detention), then they are given back the time they spent in detention as rest credit. The Trucker will immediately take a SleeperRest at their current location, minus their number of rest credit hours, and this does not count as an hours or parking violation. We imagine that in the real-world scenario, the driver is aware that their hours will run out while in detention and so immediately hop into the sleeper cab to start their rest while the shipper/receiver handles the load.

Routing

Routing is performed through JHighway, which is a separate package from Trucker.alp also written by me. More details can be found in the JHighway documentation. This section will focus on specifically how JHighway is used in Trucker.alp.

JHighway is loaded with a set of highways and a list of locations. It provides routes along the highways along with locations nearby to points along that route. This provides the Trucker with an efficient and nicely refined list of potential stops along their route and avoids having to frequently consider all locations at once.

Note the classes JHighwayWrapper and RouteDataWrapper which provide nicer interfaces to JHighway objects designed for this specific use case, and do some translation into objects suitable for use within AnyLogic.

JHighwayRouteProvider

The .alp file contains the Java class JHighwayRouteProvider, which is used for all routing in the model. There is only a single instance of JHighwayRouteProvider in the model which is set as the route provider for all Trucker agents.

Using this class requires a bit of a hack which was necessary to improve efficiency. It is faster to make one large query to JHighway than to make many small ones, and having the entire route and possible stops pre-loaded at the start of the trip helps a lot with efficiency more generally. This approach is directly in conflict with the approach of the AnyLogic RouteProvider class which is only designed to find the route for the current stretch of travel. As far as I can tell, there is no way for the RouteProvider to know which agent is requesting the route, and thus it can't refer to some cached set of routes for all agents.

The "hack" allows us to mimic this caching behaviour. The route is cached within the Trucker agent and that agent must always call the setCurrentRouteData method before calling the moveTo method. Because ABMs are inherently single-threaded, there is no risk of agents clobbering each other's routes by requesting them at the same time. Once the route has been provided for a given stretch of the trip, that geographic data is saved within the Trucker and stays there until they need to request a route for the next stretch. Testing has confirmed that this has no effect on the actual behaviour of the RouteProvider; however this is gross and messy and really should be improved if at all possible.

Possible Improvements

Rather than starting to drive home if we're within 48 hours of targetDaysInDrivingPeriod, we should have a parameter that defines when we decide to start driving home and take the 48 hour thing out.

The handling of fuel is kinda dumb, like what are we even modelling here? Is fuel really relevant? I doubt it's a limiting factor very often. Should we just remove it from the model?

What the heck is up with getNextRestType? That looks like a gross hack and I can't remember why I did it that way.

In the future, it might be a problem that we select the driver's next stop when they start driving and don't allow them to change their mind along the way. Make sure that future implementations keep this option open.

Find a workaround for the JHighwayRouteProvider hack