Simulating Vehicles

We are trying to simulate traffic flowing through downtown Austin. At the very least this means we need a class that represents a vehicle for the simulation. Exactly what attributes this class will need is yet to be determined, but we can use out Poolball class as a starting point. Some simplifications will be in order.

The Highway

We are modeling the I-35 corridor through downtown Austin, which is three lanes wide, and has a number of on and off ramps where traffic can also enter and leave, in addition to the beginning and end of our simulated read segment. Obviously, we will need to define where these ramps are located along the route To do that, I will use Google Maps to find out where they all are.

Here is the data we will use:

  • Segment start: Town Lake bridge (MM 232)

  • Off ramp to 1st street (MM 232 + 280ft)

  • On ramp from Bridge access road (MM 232 + 2600ft)

  • Off ramp to Cesar Chavez (MM 233 + 500ft)

  • On ramp from Cesar Chavez (MM 233+2500ft)

  • Off ramp to 6th Street (MM 233 + 3000ft)

  • On ramp from 7th Street (MM 234)

  • Off ramp to 15th Street (MM 234 + 1100ft)

  • On ramp from 12th Street (MM 234 + 2300ft)

  • On ramp from MLK (MM 235 + 100ft)

  • Simulation end (MM 236)

..` note:

These are estimates based on playing with distances using Google Maps. YMMV!

That amounts to four miles of highway, with nine ramps in between. We all know that during rush hour these ramps feed in a lot of traffic and remove some as well In total it is often pretty full of slow moving cars. Our simulation seeks to find out what is going on.

We do not need to worry about curves in this road, so we really do not need to maintain much data about the road. In fact, we can draw the road on the screen using just straight lines showing the lane boundaries, and we could even use additional lanes chunks to represent entry and exit ramps. Cars can appear at entry ramps as needed, and exit at off ramps as well.

Managing the Vehicles

We will maintain the simulated vehicles in a linked list so the total number of vehicles can grow or shrink as needed. Each vehicle will be able to tell us which lane it is in, and what speed it is moving at, and we will also track the location as a distance from the entry point of our road segment.

we will need a way to add vehicles to our highway system, and we will need a way to remove them as well.

That raises an interesting point. We will be creating new objects as we add vehicles to our simulation. Normally, those objects will be assigned memory by using the new operator, which grabs a suitable chink of memory from the heap. The object can be released by using the delete operator, and this returns the memory to the heap.

If you do this a lot, the heap will become fragmented, and while you may have available memory, finding a chunk can be time consuming as we search for a block big enough for the new object.

A better management scheme involves maintaining two lists of objects. We will create new vehicle objects as the simulation starts, and add them to the simulation linked list. However, as vehicles leave the simulation, we will take those objects, unhook them from the simulation list, and add them to a second, unused, linked list. When we need a new vehicle for our simulation, we will check the available (unused) list for an unused object, and initialize that object as we connect it back into the simulation list. If no unused objects are available, we will generate a new one from the heap. This will mean we never have to release nodes to the heap, although we could set some threshold where we do release nodes from the unused list if that memory is needed.

Note

Managing the heap can get complex. As you allocate and deallocate blocks of memory, you may need to stop and reorder this entire memory are to make sure allocations can continue. Formally, this is called “garbage collection” and there are tools around to manage this process for you. Those tools are for a later course.!

Generating Traffic

Obviously, our simulation is driven by the vehicles entering and leaving our simulated highway. We have a total of six points where traffic can enter our road, and five points where traffic can leave. We will use random number generators to determine when vehicles arrive at each entry point, and we will need a “load factor” to drive the number of vehicles that arrive in any given period of time.

We will also assign a destination code to each vehicle we add to the simulation, to be used to determine which exit point that vehicle will head toward. We will set this exit number at random as we add vehicles to the simulation. Hey, this can get quite interesting.

How Vehicles Move

Our vehicles will enter our simulation at one end of our simulated road segment, or at on ramps. They will be in some lane, and will be moving at some speed. The simulation will obviously make changes to the speed, and perhaps the lane as well. We will defer that discussion until later. For now, we can start off our Vehicle class with this specification:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#pragma once

class Vehicle {
    private:
        float speed;    // vehicle speed in mph
        int lane;       // what lane it this vehicle in
        float location; // distance from start point in feet
    public:
        // constructors
        Vehicle();                      // default constructor
        Vehicle( int lane, float loc ); // convenience constructor
        void move( float time );        // reposition vehicle
};

In this start to our class, we have two constructors which we will use to create new vehicles for the simulation. We also have a generic move method that will use some time interval to calculate the position of the vehicle for the next cycle of our simulation.

In this simple simulation, the vehicle will maintain a constant speed for each cycle of the simulation We will be able to change the vehicles speed as we evaluate the situation after that move. Exactly how we do this is going to take some thought, as many things could enter into the decisions we make here.

Changing lanes

When our vehicle decides to change lanes, or exit the simulated road at an off ramp, it will take some time to accomplish this. To keep the simulation simple, we will use a fixed time interval for the lane change, and we will simply use that interval to figure out the position of the vehicle between lanes. When the transition is complete, we will update the lane number.

Random Number Controls

The first real problem we need to face is how we will generate our traffic. Vehicles should arrive at our entry points according to some random interval of time. If traffic is light, there might be some time between arrivals of new vehicles, so the maximum time interval will belong. If traffic is heavy, the time between arrivals will be short. Those vehicles will also need to have an initial speed. If traffic is light, that arrival speed will be somewhere around the speed limit for the road. (OK, this is Texas, so maybe that speed is the speed limit plus about, says, 20 miles per hour more!) If traffic is heavy, the odds are that the speed will seldom be that high, and may be very slow. If we allow our initial speed to be too high, we may generate crashes right away, so we may need to lower the initial speed as we increase the traffic load.

C++ Random Numbers

Most programming languages provide a basic random number generator function. Usually, this tool generates a random number that might be a floating point number between zero and one, or it might be an integer number between zero and some implementation defined max value. The problem with such functions is that they always generate the same sequence of random numbers if we start them off the same way every time. To counter this problem, most systems provide a way to “seed the generator by jumping into the sequence at some random point, then letting it run from there.

In C++, the random number generator functions are part of the cstdlib library. The rand function delivers a random integer number. The srand function can be used to “seed” the generator to give a more random result

We can use the mod operator (%) to limit the numbers produced to some set range of values. Finally, we can slide those numbers up and down by adding a lower bound to each number produced. Combining all of these schemes, we can come up with two function we can use in our simulation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cstdlib>
#include <iostream>
using namespace std;

void seed( void ) {
    srand((unsigned) time(0));
}

int random( int min, int max ) {
    int range = max - min + 1;
    return min + (rand() % range);
}

int main( int argc, char *argv[] ) {
    seed();
    int min = 20;
    int max = 80;
    for( int i = 0; i< 10; i++ ) {
        cout << random( min, max ) << endl;
    }
}

The random function can be used to generate random numbers between the user defined minimum and maximum values.

Here is a sample run:

58
66
42
49
37
53
62
38
48
20

We can use this function to determine when vehicles arrive in our simulation, and what speed they might have, and which exit they will seek as well.

Traffic Load

We need a way to control the traffic load, to simulate “rush hour” (which in Austin runs from midnight to 11:59pm, it seems!) This load factor will increase the number of vehicles entering the simulation over time, and will decrease the speed range for these vehicles. We will need a way to set both values.

This is obviously an important part of the simulation, and may need experimenting to get something reasonable.

To even start our simulation, we need to come up with a scheme that controls the arrival interval we will use. Think about how traffic works. If vehicles are moving fast, they tend to be further apart. (Or, they should be further apart. Do you remember the three second rule?) Here are some numbers to give you a feel for the distance we should have between vehicles at a minimum:

Speed

feet per second

25

37

35

52

45

66

55

81

65

96

75

111

Note

A little math will generate these data values. The speed defines the distance traveled!

With this data and the three second rule, you can figure out how often cars should arrive. We might start off our simulation by making the (silly) assumption that drivers are all operating safely when they enter our simulation. We will let their behavior control what happens next!

Perhaps a simple starting point for our simulation is to just use a simple load factor to control the maximum speed of any vehicle entering our simulation, then pick some upper bound for arrival times to spread vehicles out a bit.

It should be obvious to you that the speed of any two arriving vehicles cannot be totally arbitrary. We cannot have a vehicle traveling at 80 mph arrive one second behind a vehicle traveling at 20 mph. Well we could, but there will be a crash very soon after that fast car is added to our simulation!

Perhaps the speed of each vehicle should be set to something that ensures they could stop before hitting that car in front of them. How do we calculate that speed?

We can use a random number generator to set the distance between vehicles. The speed of the vehicle arriving next in any lane can be set to at most the speed of the previous vehicle plus an adjustment based on how far apart we determine them to be. Phew, this is getting complicated. That is why simulations are such fun, they make you think about how the world actually works.

Let’s assume the vehicle “could” stop if it is three seconds behind the car in front of them. The distance traveled is found using this formula:

  • distance (feet) = speed (miles/hour) / 3600 (seconds/hour) * 5280 (feet/mile) * 3 (seconds).

Rearranging this formula give this:

  • speed (in mph) = distance (feet) * 3600 (seconds/hour) / 3 (seconds) / 5280 (feet/mile)

That last formula will tell us the maximum speed allowed for the next vehicle. The speed may well be lower, we will let our random number generator figure that out. If the allowed speed exceeds the speed limit, we will arbitrarily limit the speed to that limit, just to get started.

With these ideas, here are some calculations to help us set up arrival times:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

const int LOAD = 50;                        // 0 - 100
const int LENGTH = 15;                      // feet
const int MAX_SPEED = 80;                   // mph
const int MIN_SPEED = 2;                    // mph
const int MIN_SEPARATION = 3;               // feet between cars
const int MAX_SEPARATION = 1000;            // feet between cars.

float min_fps = MIN_SPEED / 3600.0 * 5280;  // distance traveled per sec
float max_fps = MAX_SPEED / 3600.0 * 5280;  // distance traveled per sec

int main( int argc, char *argv[] ) {
    cout << min_fps << endl;
    cout << max_fps << endl;

    float max_speed = MAX_SEPARATION * 3600.0 / 3 / 5280.0;
    float min_speed = MIN_SEPARATION * 3600.0 / 3 / 5280.0;

    cout << max_speed << endl;
    cout << min_speed << endl;
}

Running this code gives this output:

2.93333
117.333
227.273
0.68182

The first two numbers are the feet per second traveled at the minimum and maximum speeds. The last two numbers are the speeds we could be traveling based on the minimum and maximum separation and the three second rule. Vehicles traveling at those speeds could avoid a crash in three seconds if maintaining the minimum and maximum separations we are using. ( I doubt that any vehicles n I-35 can move at 227mph!

Obviously we have work to do to get this simulation moving. So far we have worked up some ideas that tell us we need the following code:

  • A linked list to manage active vehicles

  • A linked list to manage inactive vehicle objects

  • Code to add vehicles to the simulation

  • Code to remove vehicles from the simulation and move objects to the inactive list

  • A random number generator

  • Code to control the arrival times for vehicles and set their speed.

    • We need to use this code for each lane at the start point, and for each on ramp

  • Code that sets the destination for each vehicle (either at the exit point or at an off ramp)

  • A simulation clock to tick and control our simulation!

Are We Done?

Not hardly! We have not even begun to think through how the driver will act during the transit of our chunk or road. Are they aggressive, or more controlled? Are they prone to speed or not? Are they willing to tail-gate? How much room do they need to try a lane change? What is their reaction time? And so on. We have a lot to add to this simulation, but we have a start.