Building Your First Simulation
This tutorial walks you through building a complete discrete event simulation from scratch.
What You’ll Learn
Creating a
SimulationsubclassScheduling and handling events
Creating and tracking entities
Using resources (servers and queues)
Collecting statistics
Running and analyzing results
The Scenario
We’ll build a simple bank simulation where:
Customers arrive randomly
They wait in a queue if all tellers are busy
They get served by a teller
We track wait times and teller utilization
Step 1: Create the Simulation Class
Start by creating a new file bank_simulation.py:
import simcraft
class BankSimulation(simcraft.Simulation):
"""A simple bank with customers and tellers."""
def __init__(self, num_tellers: int = 2, arrival_rate: float = 1.0):
super().__init__()
self.arrival_rate = arrival_rate
# Create a server with multiple tellers
self.tellers = simcraft.Server(capacity=num_tellers)
# Statistics
self.wait_times = simcraft.Tally()
self.customers_served = simcraft.Counter()
def on_init(self):
"""Called when simulation starts."""
# Schedule first customer arrival
self.schedule(self.customer_arrival, delay=0.0)
# Set up callbacks for server events
self.tellers.on_service_start(self.on_service_start)
self.tellers.on_departure(self.on_service_complete)
Step 2: Handle Customer Arrivals
Add the arrival event handler:
def customer_arrival(self):
"""Handle a customer arriving at the bank."""
# Create a new customer entity
customer = simcraft.TimedEntity()
customer.record_entry(self.now)
# Customer joins the teller queue
self.tellers.enqueue(customer)
# Schedule next customer arrival
inter_arrival = self.rng.exponential(1.0 / self.arrival_rate)
self.schedule(self.customer_arrival, delay=inter_arrival)
Step 3: Handle Service Events
Add the service event handlers:
def on_service_start(self, customer):
"""Called when a customer starts being served."""
customer.record_service_start(self.now)
self.wait_times.observe(customer.waiting_time)
# Schedule service completion
service_time = self.rng.exponential(mean=2.0)
self.schedule(self.service_complete, delay=service_time, args=(customer,))
def service_complete(self, customer):
"""Handle service completion."""
customer.record_exit(self.now)
self.tellers.depart(customer)
def on_service_complete(self, customer):
"""Called after customer departs."""
self.customers_served.increment()
Step 4: Add Reporting
Add a method to report results:
def report(self):
"""Print simulation results."""
print("\n=== Bank Simulation Results ===")
print(f"Simulation time: {self.now:.1f}")
print(f"Customers served: {self.customers_served.value}")
print(f"Teller utilization: {self.tellers.stats.utilization:.1%}")
print(f"Average wait time: {self.wait_times.mean:.2f}")
print(f"Max wait time: {self.wait_times.max:.2f}")
if self.wait_times.count >= 2:
print(f"Wait time std dev: {self.wait_times.std:.2f}")
Step 5: Run the Simulation
Add a main block to run the simulation:
if __name__ == "__main__":
# Create simulation with 2 tellers, 1.5 customers/minute
sim = BankSimulation(num_tellers=2, arrival_rate=1.5)
# Run for 480 minutes (8 hours)
sim.run(until=480)
# Print results
sim.report()
Complete Code
Here’s the complete simulation:
import simcraft
class BankSimulation(simcraft.Simulation):
"""A simple bank with customers and tellers."""
def __init__(self, num_tellers: int = 2, arrival_rate: float = 1.0):
super().__init__()
self.arrival_rate = arrival_rate
# Create a server with multiple tellers
self.tellers = simcraft.Server(capacity=num_tellers)
# Statistics
self.wait_times = simcraft.Tally()
self.customers_served = simcraft.Counter()
def on_init(self):
"""Called when simulation starts."""
self.schedule(self.customer_arrival, delay=0.0)
self.tellers.on_service_start(self.on_service_start)
self.tellers.on_departure(self.on_service_complete)
def customer_arrival(self):
"""Handle a customer arriving at the bank."""
customer = simcraft.TimedEntity()
customer.record_entry(self.now)
self.tellers.enqueue(customer)
inter_arrival = self.rng.exponential(1.0 / self.arrival_rate)
self.schedule(self.customer_arrival, delay=inter_arrival)
def on_service_start(self, customer):
"""Called when a customer starts being served."""
customer.record_service_start(self.now)
self.wait_times.observe(customer.waiting_time)
service_time = self.rng.exponential(mean=2.0)
self.schedule(self.service_complete, delay=service_time, args=(customer,))
def service_complete(self, customer):
"""Handle service completion."""
customer.record_exit(self.now)
self.tellers.depart(customer)
def on_service_complete(self, customer):
"""Called after customer departs."""
self.customers_served.increment()
def report(self):
"""Print simulation results."""
print("\n=== Bank Simulation Results ===")
print(f"Simulation time: {self.now:.1f}")
print(f"Customers served: {self.customers_served.value}")
print(f"Teller utilization: {self.tellers.stats.utilization:.1%}")
print(f"Average wait time: {self.wait_times.mean:.2f}")
print(f"Max wait time: {self.wait_times.max:.2f}")
if __name__ == "__main__":
sim = BankSimulation(num_tellers=2, arrival_rate=1.5)
sim.run(until=480)
sim.report()
Running the Simulation
Save the file and run it:
python bank_simulation.py
Example output:
=== Bank Simulation Results ===
Simulation time: 480.0
Customers served: 712
Teller utilization: 74.2%
Average wait time: 1.84
Max wait time: 12.37
Experimentation
Try modifying the simulation:
Change the number of tellers: How does utilization change with 1, 2, or 3 tellers?
Vary arrival rate: What happens when customers arrive faster?
Add warmup: Use
sim.warmup(60)beforesim.run()to exclude startup transients
Next Steps
See the M/M/1 Queue Example for theoretical validation
Learn about RL Integration for optimization
Explore more Examples