pythonmatplotlibanimation

FuncAnimation only shows one frame


I am trying to implement a genetic algorithm in Python and to display the result of my algorithm I wanted to create an animation using FuncAnimation from matplotlib.animation.

However, I ran into the problem that I ended up with only one image. The most interesting thing is that in Google Colab I get one frame, but in Pycharm (the IDE I usually use) I get another one.

Here is my code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

def fitness_function(x, y):
    return x ** 2 + y ** 2

def initialize_population(pop_size, bounds):
    return np.random.uniform(bounds[0], bounds[1], (pop_size, 2))

def select(population, fitnesses):
    indices = np.argsort(fitnesses)[:len(population) // 2]
    return population[indices]

def crossover(parents, offspring_size):
    offsprings = []
    for _ in range(offspring_size):
        p1, p2 = parents[np.random.choice(len(parents), size=2, replace=False)]
        alpha = np.random.rand()
        child = alpha * p1 + (1 - alpha) * p2
        offsprings.append(child)
    return np.array(offsprings)

def mutate(population, bounds, mutation_rate=0.1):
    for i in range(len(population)):
        if np.random.rand() < mutation_rate:
            population[i] += np.random.uniform(-1, 1, size=2)
            population[i] = np.clip(population[i], bounds[0], bounds[1])
    return population

def genetic_algorithm(pop_size=500, generations=500, bounds=(-10, 10)):
    population = initialize_population(pop_size, bounds)
    history = []

    for gen in range(generations):
        fitnesses = np.array([fitness_function(x, y) for x, y in population])
        parents = select(population, fitnesses)
        offspring_size = pop_size - len(parents)
        offspring = crossover(parents, offspring_size)
        population = np.vstack((parents, offspring))
        population = mutate(population, bounds)
        history.append(population.copy())

        if gen % 50 == 0:
            print(f"Generation {gen}: Best fitness = {fitnesses.min():.4f}")

    return history

history = genetic_algorithm()

fig, ax = plt.subplots(figsize=(8, 8))
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_title("Evolution of Genetic Algorithm Population")
ax.set_xlabel("X")
ax.set_ylabel("Y")

colors = np.linspace(0, 1, len(history))

scat = ax.scatter([], [], c=[], cmap='viridis', s=50, vmin=0, vmax=1)

ax.plot([0, 0], [-10, 10], color='blue', linewidth=1)
ax.plot([-10, 10], [0, 0], color='blue', linewidth=1)

def update(frame):
    print(f"Frame: {frame}, History Length: {len(history)}")
    data = history[frame]
    scat.set_offsets(data)
    scat.set_array(np.full(len(data), colors[frame]))
    ax.set_title(f"Generation {frame+1}/{len(history)}")
    return scat,

anim = FuncAnimation(fig, update, frames=len(history), interval=50, repeat=False)
plt.show()
print(plt.get_backend())

print(f"Number of generations: {len(history)}")

I've read a lot of similar questions, but none of the answers worked for me.

For example, I ran the code that is marked as the required answer in this question, however I still only got one frame.

If I just haven't found a question that has an answer to mine, I apologize in advance.

Also, if you suddenly notice any mistakes in the algorithm, I would be grateful if you could point them out to me.

I'm using:

Python 3.9.13 matplotlib 3.9.3 numpy 2.0.2


Solution

  • I ran your code on my own PC and It worked just fine. I don't really know why It doesn't work for you in Pycharm, maybe some environmental version difference in packages? I'm not sure your the one who should find that out yourself but I have this answer for you:

    I ran the code on google colab and It showed 1 frame only like you said. What you should do instead is to use a method called "save" for anim to save your animation as .gif and then it will render the frames and saves your animation you can just double click on it to play it on google colab or show it with "IPython.display".

    Here is a version of your code to use in google colab:

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    
    def fitness_function(x, y):
        return x ** 2 + y ** 2
    
    def initialize_population(pop_size, bounds):
        return np.random.uniform(bounds[0], bounds[1], (pop_size, 2))
    
    def select(population, fitnesses):
        indices = np.argsort(fitnesses)[:len(population) // 2]
        return population[indices]
    
    def crossover(parents, offspring_size):
        offsprings = []
        for _ in range(offspring_size):
            p1, p2 = parents[np.random.choice(len(parents), size=2, replace=False)]
            alpha = np.random.rand()
            child = alpha * p1 + (1 - alpha) * p2
            offsprings.append(child)
        return np.array(offsprings)
    
    def mutate(population, bounds, mutation_rate=0.1):
        for i in range(len(population)):
            if np.random.rand() < mutation_rate:
                population[i] += np.random.uniform(-1, 1, size=2)
                population[i] = np.clip(population[i], bounds[0], bounds[1])
        return population
    
    def genetic_algorithm(pop_size=500, generations=500, bounds=(-10, 10)):
        population = initialize_population(pop_size, bounds)
        history = []
    
        for gen in range(generations):
            fitnesses = np.array([fitness_function(x, y) for x, y in population])
            parents = select(population, fitnesses)
            offspring_size = pop_size - len(parents)
            offspring = crossover(parents, offspring_size)
            population = np.vstack((parents, offspring))
            population = mutate(population, bounds)
            history.append(population.copy())
    
            if gen % 50 == 0:
                print(f"Generation {gen}: Best fitness = {fitnesses.min():.4f}")
    
        return history
    
    history = genetic_algorithm()
    
    fig, ax = plt.subplots(figsize=(8, 8))
    ax.set_xlim(-10, 10)
    ax.set_ylim(-10, 10)
    ax.set_title("Evolution of Genetic Algorithm Population")
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    
    colors = np.linspace(0, 1, len(history))
    
    scat = ax.scatter([], [], c=[], cmap='viridis', s=50, vmin=0, vmax=1)
    
    ax.plot([0, 0], [-10, 10], color='blue', linewidth=1)
    ax.plot([-10, 10], [0, 0], color='blue', linewidth=1)
    
    def update(frame):
        print(f"Frame: {frame}, History Length: {len(history)}")
        data = history[frame]
        scat.set_offsets(data)
        scat.set_array(np.full(len(data), colors[frame]))
        ax.set_title(f"Generation {frame+1}/{len(history)}")
        return scat,
    
    anim = FuncAnimation(fig, update, frames=len(history), interval=50, repeat=False)
    plt.show()
    anim.save('myanim.gif', writer='pillow')
    print(plt.get_backend())
    
    print(f"Number of generations: {len(history)}")
    
    import IPython.display as display
    display.Image(open('myanim.gif','rb').read())
    

    Let me know if this helps you or not.