This is a project I’ve had in the back of my head for a while. Every time I’m sitting in traffic, I like to observe and think about the cars around me, and I’ve gotten pretty good at predicting what a lot of drivers will do. I also have some theories as to what causes traffic and ways to alleviate traffic.
The first goal of this project is to just simulate a basic highway system and see if it is as stable as my personal observations of traffic.
Second, I want to tweak parameters of each car’s behavior to see if I can replicate things like aggressive drivers, passive drivers, etc.
Third, I want to try some of my theories of things that cause traffic and things that alleviate traffic and have some fun with that
This whole project should be caveated with the statement that I’m not out to make the worlds most accurate simulator. This is more a representation of my observations than a representation of reality, although I will try my best to play by nature’s rules. Naturally, this project will be biased by my desire to make it work the way I imagine it.
The first thing that I want to tackle is making a decent model of a single car. This will include basic longitudinal dynamics for accelerating and maintaining speed, as well as lateral dynamics for changing lanes. For the car, I’m using a control loop frequency of 10Hz, since I believe that’s on the same order that humans make decisions. For each loop of the controller, the physics simulation runs 10 steps, giving a simulation rate of 100Hz (0.01s loop time). This can be refined in the future, but for now the single-car simulation is stable.
Code for the car can be found here: https://github.com/araulinaitis/Car-Simulation
My first iteration of longitudinal control is shown below. The test car was started at rest and given a step-input with a target speed. Most of the tuning for this model was to get the response to look physically reasonable: that is there are no sharp accelerations, no acceleration over 0.25g, and limiting the rate of acceleration change to ~0.18g/s. The goal for this was to bring the total vehicle “zero-to-cruise” time to around 15-20 seconds. I might increase this speed later, but for now it looks like a decent response. Furthermore, I intentionally included some overshoot on the target velocity, since most drivers aren’t perfect.
This was accomplished with a control loop that servos on velocity but affecting acceleration with the basic state model shown below, where x-dot is vehicle acceleration of the vehicle, and u is the control input.
I’m using a basic PID controller (with Integral-windup protection) with hand-tuned parameters to give the above response. Most of the response is dominated by the max acceleration and acceleration slew rate, so the PID dynamics don’t change the response very much.
My next steps for longitudinal control are to think about braking (allowing higher accelerations for emergency-braking) and lateral control for changing lanes
After completing the longitudinal control, I essentially copied it over for lateral control, just changing constants and control parameters. Namely, my maximum acceleration for lateral control is 0.1g/s (as opposed to 0.18g/s for longitudinal control) and maximum acceleration was limited to 0.1g (as opposed to 0.25g for longitudinal control). This, coupled with tuning the PID controller (actually only using PD for lateral control at the moment) gives the response below. The goal for this was to have a lane change take ~4-5 seconds.
Again, I intentionally left a little bit of overshoot both to seem a little more “real,” and also to allow for increased rise time.
For vehicles in “follow” mode, I’ve added a headway controller to the vehicles. If a vehicle is in a lane with another vehicle going slower than the rear vehicle desires, its first response will be to follow behind the lead vehicle with a given headway time (headway = gap / current speed). I only switch to the headway controller once the rear vehicle is close to the headway trailing distance. In other words, a vehicle will maintain its higher speed until it reaches close to the point that headway time would dictate. The response is shown below.
Since headway tracking is on a moving target, I’m showing the Y-position error (red) instead of the actual Y position so it all stays neatly in one window. You can see in the graph that for the first ~7.5s, the vehicle remains at 35 m/s speed, and then when it switches to headway control, the speed slows to 30 m/s. It may look that there’s a discontinuity in the error when the controller switches, but that is just an artifact of switching over. When viewing the actual Y position of the vehicle, there are no discontinuities.
In order to make the switch-over of the vehicle between controllers smooth, I’m actually continuing to accumulate error (integral) for the headway controller when it is not active. The thought being that the controller will switch to headway control when it is already close to its setpoint, which means that at that point, the integral term should have already (or nearly) settled to its desired value, so when the controller switches, it already has a control input and doesn’t have to start from zero. I considered a feed-forward term, but that would add complexity to the controller, and this way accomplishes nearly the same result
I’ve also increased the simulation to 20 steps per controller loop, since I was seeing a lot of oscillation, most likely caused by derivative numerical approximation.
Now that I have the vehicle dynamics mostly settled, it’s time to start simulating the actual highway. I have a hierarchy of objects, with the top-level being the highway, which contains a set of lanes, and each lane contains a set of vehicles. In order to prevent each lane stretching to infinity, the lanes wrap-back on themselves, so whenever a vehicle gets to the “end” of a lane, it is placed back at the beginning.
All code for the main simulation can be found here: https://github.com/araulinaitis/Traffic-Simulation/tree/master
There is a lot of back-and-forth between each vehicle and the lanes as it progresses through the world. Each vehicle is both looking ahead in their lane, as well as looking at lanes to the side to determine if it wants to change lanes or not. When a vehicle is at the “end” of a lane and it is looking at the car “in front” of it, it is actually looking at the last vehicle in the lane. Essentially the system is a closed loop, just stretched out in a straight line.
All vehicles are started in the “on-ramp” lane (far-right) and will accelerate to the speed of the lane to their left before merging in. Each vehicle has its own internal criteria for when they will merge in terms of their relative speed compared to the lane they’re merging in to as well as the minimum gap that they will lane-change in to. Each vehicle also has its own desired cruising speed, and will work to achieve that speed, unless there is a slower vehicle in front of it, then it will work to maintain a constant headway (also different for each vehicle).
This first simulation video shows a single-lane highway (one lane, one on-ramp). The simulation is far from perfect, but this is mostly just to test the basic interactions of the vehicles in terms of merging on to a highway, as well as the interactions of the different speed/headway control states of the vehicles. For the most part, it is extremely promising.
The vehicles look funny because of the relative scale of the two axes. In order to keep the vehicles on the screen for any reasonable amount of time, the lanes have to be long (500m in this case), but each vehicle is only about 2m in width, so the Y-axis is squished, while the X-axis is stretched. The gap to the left of the graph is so I can display debugging info when I click on any of the vehicles
The simulation has a “tipping point” where everything starts to become unstable, but I believe this is mostly due to the current inability of vehicles to slow down faster than they can accelerate (i.e. deceleration is limited in the same way as acceleration). There can also be some work done on some of the merging logic, but for a first test of the combined system, I think this is a huge success.
One thing that I find really interesting about these preliminary results is that I am already starting to see one of the major characteristics of a traffic jam: a “wave” moving backward through the vehicles.
I’ve spent some time adding to the mini debug console. This has led me to find a lot of bugs, which has helped smooth out the simulation a lot. You can see in the video below, that now for 10 vehicles, they can on-ramp and join the highway without any collisions or going unstable. In some simulations, the vehicles get a bit too close for comfort, but I’m sure I can smooth that out in the future. I have noticed that 10 vehicles is too much for this stretch of highway, as the vehicles never reach their desired cruising speed.
Now I’m starting to get to the difficult parts of the simulation. Up until this point, every “decision” only had one possibility. Every vehicle wanted to go from the on-ramp to the right-most lane, and if the right-most lane was slower than the vehicle wanted, it would change lanes to the left. I finally started work on the logic to let vehicles decide to change lanes in both directions based on the speed of their current lane and the lanes around them.
Unsurprisingly, this is very complex. The logic needs to be very flexible, in order to account for the relative speed of lanes and determine if it is worth it for the vehicle to change lanes or not. But this can not be done with just the relative speed alone. This video below is my (arguably funny) first attempt at the logic, in which the logic is what I just said is wrong (how else would I know it’s wrong without trying it?). For this simulation, the vehicles would change lanes if the lane next to them was faster than their lane. It becomes very clear why this is a bad idea
I’ve also been noticing that while a vehicle changes lanes, it often clips through another vehicle, or another vehicle clips through it. Currently, the system treats each lane almost independently, and when a vehicle is cruising, it doesn’t take in to account vehicles changing lanes until it is fully in the lane. But we all know that in reality, a vehicle will react to a vehicle that is changing lanes right when it begins its lane change. I have some ideas on how to accomplish this, it’s just low on the list at the moment. I am unhappy with the collisions that are so bad that the cars swap order in a lane (i.e a vehicle in front becomes a vehicle behind), but I think that should also be fixed when I fix the awareness of vehicles changing lanes