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 theRestType
andStoppableLocation
that we've decided on for our next stop along our journey. If this is the destination, then theStoppableLocation
field isnull
.gallonsConsumed
: The quantity of fuel consumed since we last refuelled.home
: TheTrucker
'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 ofRestType
andStoppableLocation
. This list does not include the next stop, which is instead stored innextStop
.nextRestType
: TheRestType
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 theTrucker
stops, look at the rest type innextStop
. 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.
- This variable exists so that we don't have to repeatedly decide which type of rest to take when the rest type indicated in
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.
- 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
- Time Tracking variables
- These are all self-explanatory, except
hoursOnDutyPerDayThisPeriod
which is explained in the Time Tracking section of this document.
- These are all self-explanatory, except
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:
- If I stopped right now, how many hours could I drive after taking a
SleeperRest
? - If I stopped right now, took a
SleeperRest
, drove the whole number of hours from 1, then stopped for anotherSleeperRest
, how many hours could I drive before I would need to take a thirdSleeperRest
?
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