pythonmatplotlibanimationtraffic-simulation

Animation of a multiple-lane highway with periodic boundary using matplotlib.animation.FuncAnimate


Below is a code, fully working, that animates a one-lane road with periodic boundaries. Now I'd like to animate two, or even more lanes. I have a working code for that where the position vector now is a matrix, each column representing a lane. From that, I can create another theta vector which I'd like to be shown outside of the first one in the animation (by setting the radius a bit bigger). I have tried to put matrices (theta and r) in the ax.scatter(theta, r, c=color), but that does not work. Does anyone have a suggestion on how to approach this problem? I could use something else than matplotlib's animation, but as it worked fine for the one-lane problem it would be easiest for me.

To wrap it up. How can I animate two or more vectors at the same time? For example, if I have r1, r2 and theta1, theta2 and want to 'plot' them both at each time, instead of just r and theta as in the code.

Thanks a lot for any help.

import numpy as np
from numpy.random import random as rnd
import matplotlib.pyplot as plt
from matplotlib import animation

roadlength = 50
numcars = 10
numframes = 1000  #Time
v_max = 5
p = 0.5

positions = np.zeros(numcars)
velocities = np.zeros(numcars)
theta = np.zeros(numcars)
color = np.linspace(0,numcars-1,numcars)

#Initiate r so roadlength = circumference of one lap
r = []
for i in range(numcars):
    r.append(roadlength/(2*np.pi))

#Initiate positions so the cars are spread out over the road
for i in range(1,numcars):
    positions[i] = positions[i-1] + (roadlength/numcars)

#Create figure        
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
ax.axis('off')

#Update positions, animate function runs framenr times
def animate(framenr):
    positions_tmp = np.array(positions, copy=True)

    #Update position and velocity for each car
    for i in range(numcars):

        #Increase velocity if below max
        if velocities[i] < v_max:
                velocities[i] += 1
        #Decrease velocity if car in front is close
        d = positions_tmp[(i+1)%numcars] - positions_tmp[i]
        if d <= 0:
            d += roadlength
        if velocities[i] >= d:
            velocities[i] = d-1
        #Decrease velocity randomly
        if velocities[i] > 0:
            if rnd() < p:
                velocities[i] -= 1

        positions[i] = positions_tmp[i] + velocities[i]
        theta[i] = positions[i]*2*np.pi/roadlength

    return ax.scatter(theta, r, c=color),

# Call the animator, blit=True means only re-draw parts that have changed
anim = animation.FuncAnimation(fig, animate, frames=numframes, interval=100, blit=True, repeat=False)
plt.show()

Solution

  • FuncAnimation() expects the animate() function to return an array of artists that have been updated at each frame. If you need to animate several "plots" (or artists, or whatever), simply store the result of each plot, and return a list of all the variables

    In addition, your code is calling ax.scatter() repeatedly in the animate function, which causes new points to be plotted everytime and the time it takes to draw the frame to increase over time. I don't know if that's really what you really wanted to do. The best practice for animations is to create the artist(s) before the start of the animation, and then update their properties inside the animate() function rather than create new ones.

    import numpy as np
    from numpy.random import random as rnd
    import matplotlib.pyplot as plt
    from matplotlib import animation
    
    roadlength = 50
    numcars = 10
    numframes = 1000  #Time
    v_max = 5
    p = 0.5
    
    positions = np.zeros(numcars)
    velocities = np.zeros(numcars)
    theta = np.zeros(numcars)
    color = np.linspace(0,numcars-1,numcars)
    
    #Initiate r so roadlength = circumference of one lap
    r = roadlength/(2*np.pi)
    
    #Initiate positions so the cars are spread out over the road
    for i in range(1,numcars):
        positions[i] = positions[i-1] + (roadlength/numcars)
    
    #Create figure        
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='polar')
    ax.axis('off')
    
    plot1 = ax.scatter(theta, [r]*numcars, c=color)
    plot2 = ax.scatter(theta, [r+1]*numcars, c=color)
    
    ax.set_ylim(0,r+2)
    
    #Update positions, animate function runs framenr times
    def animate(framenr):
        positions_tmp = np.array(positions, copy=True)
    
        #Update position and velocity for each car
        for i in range(numcars):
    
            #Increase velocity if below max
            if velocities[i] < v_max:
                    velocities[i] += 1
            #Decrease velocity if car in front is close
            d = positions_tmp[(i+1)%numcars] - positions_tmp[i]
            if d <= 0:
                d += roadlength
            if velocities[i] >= d:
                velocities[i] = d-1
            #Decrease velocity randomly
            if velocities[i] > 0:
                if rnd() < p:
                    velocities[i] -= 1
    
            positions[i] = positions_tmp[i] + velocities[i]
            theta[i] = positions[i]*2*np.pi/roadlength
    
        plot1.set_offsets(np.c_[theta, [r]*numcars])
        plot2.set_offsets(np.c_[theta, [r+1]*numcars])
    
        return [plot1, plot2]
    
    # Call the animator, blit=True means only re-draw parts that have changed
    anim = animation.FuncAnimation(fig, animate, frames=numframes, interval=100, blit=True, repeat=True)
    plt.show()
    

    enter image description here