Animation in SimPy: Making Simulations Come Alive
Static charts tell a story. Animation tells it better. Watch your simulation unfold in real time.
Why Animate?
- Debug visually - see when things go wrong
- Present to stakeholders - they understand pictures
- Build intuition - watch patterns emerge
- Validate - does the animation match reality?
matplotlib Animation Basics
import simpy
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
class AnimatedSimulation:
def __init__(self):
self.time_data = []
self.queue_data = []
def run_step(self, env, server):
"""Run simulation and collect data."""
self.time_data.append(env.now)
self.queue_data.append(len(server.queue))
def animate_queue():
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 20)
ax.set_xlabel('Time')
ax.set_ylabel('Queue Length')
line, = ax.plot([], [], lw=2)
sim_data = AnimatedSimulation()
def init():
line.set_data([], [])
return line,
def update(frame):
# Update simulation
sim_data.time_data.append(frame)
sim_data.queue_data.append(np.random.poisson(5))
line.set_data(sim_data.time_data, sim_data.queue_data)
return line,
ani = FuncAnimation(fig, update, frames=range(100),
init_func=init, blit=True, interval=100)
plt.show()
Real-Time Simulation Display
import simpy.rt
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import threading
import queue
class RealTimeDisplay:
def __init__(self):
self.data_queue = queue.Queue()
self.times = []
self.values = []
def update_data(self, time, value):
self.data_queue.put((time, value))
def run_animation(self):
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 20)
line, = ax.plot([], [], lw=2)
def update(frame):
while not self.data_queue.empty():
t, v = self.data_queue.get()
self.times.append(t)
self.values.append(v)
line.set_data(self.times, self.values)
if self.times:
ax.set_xlim(0, max(self.times) + 10)
return line,
ani = FuncAnimation(fig, update, interval=100)
plt.show()
# Run simulation in separate thread
def simulation_thread(display):
env = simpy.rt.RealtimeEnvironment(factor=0.1)
server = simpy.Resource(env, capacity=2)
def monitor(env, server, display):
while True:
display.update_data(env.now, len(server.queue))
yield env.timeout(1)
env.process(monitor(env, server, display))
# ... add other processes
env.run(until=100)
display = RealTimeDisplay()
sim_thread = threading.Thread(target=simulation_thread, args=(display,))
sim_thread.start()
display.run_animation()
Simple Text Animation
For terminal-based visualisation:
import time
import os
def clear_screen():
os.system('cls' if os.name == 'nt' else 'clear')
def text_animation(env, server):
while True:
clear_screen()
# Draw queue
queue_len = len(server.queue)
busy = server.count
print(f"Time: {env.now:.1f}")
print(f"Server: {'[BUSY]' * busy}{'[ ]' * (server.capacity - busy)}")
print(f"Queue: {'o' * queue_len}")
print(f"\nWaiting: {queue_len}, In service: {busy}")
yield env.timeout(0.5)
# Use with RealtimeEnvironment
env = simpy.rt.RealtimeEnvironment(factor=1)
server = simpy.Resource(env, capacity=2)
env.process(text_animation(env, server))
Pygame Visualisation
For more sophisticated animation:
# Note: Requires pygame
import pygame
import simpy
import random
class PygameVisualisation:
def __init__(self, width=800, height=600):
pygame.init()
self.screen = pygame.display.set_mode((width, height))
self.clock = pygame.time.Clock()
self.width = width
self.height = height
# Simulation state
self.entities = []
self.server_busy = False
def draw(self):
self.screen.fill((255, 255, 255))
# Draw server
color = (255, 0, 0) if self.server_busy else (0, 255, 0)
pygame.draw.rect(self.screen, color, (350, 250, 100, 100))
# Draw queue
for i, entity in enumerate(self.entities):
pygame.draw.circle(self.screen, (0, 0, 255),
(100 + i * 30, 300), 10)
# Update display
pygame.display.flip()
def update(self, queue_length, server_busy):
self.entities = list(range(queue_length))
self.server_busy = server_busy
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return False
return True
# Integration with SimPy
def simulation_with_pygame(vis):
env = simpy.rt.RealtimeEnvironment(factor=0.5)
server = simpy.Resource(env, capacity=1)
def display_update(env, server, vis):
while True:
if not vis.update(len(server.queue), server.count > 0):
break
vis.draw()
yield env.timeout(0.1)
env.process(display_update(env, server, vis))
# ... add customers
env.run(until=60)
Plotly Animation
Interactive web-based animation:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def create_animated_plot(time_series):
"""Create animated plot with Plotly."""
fig = go.Figure(
data=[go.Scatter(x=[], y=[], mode='lines')],
layout=go.Layout(
xaxis=dict(range=[0, max(r['time'] for r in time_series)]),
yaxis=dict(range=[0, max(r['queue'] for r in time_series) + 1]),
title="Queue Length Animation",
updatemenus=[dict(
type="buttons",
buttons=[dict(label="Play",
method="animate",
args=[None, {"frame": {"duration": 100}}])]
)]
),
frames=[
go.Frame(data=[go.Scatter(
x=[r['time'] for r in time_series[:i]],
y=[r['queue'] for r in time_series[:i]]
)])
for i in range(1, len(time_series))
]
)
fig.write_html('animation.html')
fig.show()
GIF Export
from matplotlib.animation import FuncAnimation, PillowWriter
def create_gif(time_series, filename='simulation.gif'):
fig, ax = plt.subplots()
times = [r['time'] for r in time_series]
queues = [r['queue'] for r in time_series]
ax.set_xlim(0, max(times))
ax.set_ylim(0, max(queues) + 1)
ax.set_xlabel('Time')
ax.set_ylabel('Queue Length')
line, = ax.plot([], [], lw=2)
def update(frame):
line.set_data(times[:frame], queues[:frame])
return line,
ani = FuncAnimation(fig, update, frames=len(times), interval=50)
ani.save(filename, writer=PillowWriter(fps=20))
print(f"Saved animation to {filename}")
Live Dashboard with Dash
# Note: Requires dash
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
app = Dash(__name__)
# Shared data
simulation_data = {'time': [], 'queue': []}
app.layout = html.Div([
dcc.Graph(id='live-graph'),
dcc.Interval(id='interval', interval=500) # Update every 500ms
])
@app.callback(Output('live-graph', 'figure'),
Input('interval', 'n_intervals'))
def update_graph(n):
return {
'data': [go.Scatter(
x=simulation_data['time'],
y=simulation_data['queue'],
mode='lines'
)],
'layout': go.Layout(
title='Live Queue Length',
xaxis={'title': 'Time'},
yaxis={'title': 'Queue'}
)
}
# Run simulation in background, update simulation_data
# app.run_server(debug=True)
Summary
Animation options: - matplotlib - Simple, integrated - Terminal - Text-based, lightweight - Pygame - Game-like, interactive - Plotly - Web-based, interactive - Dash - Live dashboards
Pick the right tool for your audience.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp