Every simulation needs entities entering the system. Customers arrive. Jobs appear. Packets flow. How you model arrivals shapes your entire simulation.
The Arrival Generator Pattern
def arrivals(env, resource):
i = 0
while True:
yield env.timeout(interarrival_time())
env.process(customer(env, f"C{i}", resource))
i += 1
This pattern:
- Waits for the next arrival
- Spawns a new entity
- Repeats forever
Poisson Arrivals (The Classic)
Real-world arrivals often follow a Poisson process. The time between arrivals is exponentially distributed:
import random
def arrivals(env, resource, arrival_rate):
"""arrival_rate = average arrivals per time unit"""
i = 0
while True:
interarrival = random.expovariate(arrival_rate)
yield env.timeout(interarrival)
env.process(customer(env, f"C{i}", resource))
i += 1
# 10 arrivals per hour on average
env.process(arrivals(env, server, arrival_rate=10))
Why Poisson? Because many real processes are:
- Memoryless (past doesn’t predict future)
- Independent (arrivals don’t influence each other)
- Random but with a stable rate
Understanding expovariate
random.expovariate(rate) # Returns time until next event
rate = 10means 10 events per unit time → short intervalsrate = 0.1means 0.1 events per unit time → long intervals- Mean interval = 1/rate
Common confusion:
# Wrong: thinking in terms of mean time
random.expovariate(5) # NOT mean 5, but rate 5 (mean 0.2)
# Right: use 1/mean_time
mean_time = 5
random.expovariate(1/mean_time) # Mean interval of 5
Fixed Intervals
Sometimes arrivals are deterministic:
def arrivals(env, resource, interval):
"""Arrivals exactly every 'interval' time units"""
i = 0
while True:
yield env.timeout(interval)
env.process(customer(env, f"C{i}", resource))
i += 1
# One arrival every 5 minutes
env.process(arrivals(env, server, interval=5))
Use for scheduled processes, timed events, or as a simple baseline.
Uniform Random Intervals
When you know the range but not the distribution:
def arrivals(env, resource, min_time, max_time):
i = 0
while True:
interarrival = random.uniform(min_time, max_time)
yield env.timeout(interarrival)
env.process(customer(env, f"C{i}", resource))
i += 1
# Between 3 and 7 minutes apart
env.process(arrivals(env, server, 3, 7))
Batch Arrivals
Groups arriving together:
def batch_arrivals(env, resource, batch_rate, batch_size_range):
i = 0
while True:
yield env.timeout(random.expovariate(batch_rate))
batch_size = random.randint(*batch_size_range)
for j in range(batch_size):
env.process(customer(env, f"C{i}", resource))
i += 1
print(f"Batch of {batch_size} arrived at {env.now}")
# Batches arrive every 10 units on average, size 3-8
env.process(batch_arrivals(env, server, 1/10, (3, 8)))
Time-Varying Arrivals
Real systems have peaks and troughs:
def time_varying_rate(time):
"""Rush hour pattern"""
hour = time % 24
if 8 <= hour <= 10 or 17 <= hour <= 19:
return 20 # Peak: 20 arrivals/hour
elif 11 <= hour <= 16:
return 10 # Normal: 10/hour
else:
return 2 # Quiet: 2/hour
def arrivals(env, resource):
i = 0
while True:
current_rate = time_varying_rate(env.now)
interarrival = random.expovariate(current_rate)
yield env.timeout(interarrival)
env.process(customer(env, f"C{i}", resource))
i += 1
Schedule-Based Arrivals
Arrivals at specific times:
def scheduled_arrivals(env, resource, schedule):
"""schedule = list of arrival times"""
for i, arrival_time in enumerate(schedule):
yield env.timeout(arrival_time - env.now)
env.process(customer(env, f"C{i}", resource))
# Arrivals at times 0, 5, 7, 12, 20
env.process(scheduled_arrivals(env, server, [0, 5, 7, 12, 20]))
From Historical Data
Use real data:
import pandas as pd
# Load historical interarrival times
data = pd.read_csv('arrivals.csv')
interarrivals = data['interarrival_time'].tolist()
def replay_arrivals(env, resource, interarrivals):
i = 0
for interarrival in interarrivals:
yield env.timeout(interarrival)
env.process(customer(env, f"C{i}", resource))
i += 1
Or fit a distribution to your data:
from scipy import stats
# Fit exponential distribution
params = stats.expon.fit(data['interarrival_time'])
def fitted_arrivals(env, resource):
i = 0
while True:
interarrival = stats.expon.rvs(*params)
yield env.timeout(interarrival)
env.process(customer(env, f"C{i}", resource))
i += 1
Bounded Arrivals
Limit total arrivals:
def bounded_arrivals(env, resource, n_customers, arrival_rate):
"""Generate exactly n customers"""
for i in range(n_customers):
yield env.timeout(random.expovariate(arrival_rate))
env.process(customer(env, f"C{i}", resource))
Or time-bounded:
def time_bounded_arrivals(env, resource, arrival_rate, until):
i = 0
while env.now < until:
yield env.timeout(random.expovariate(arrival_rate))
if env.now < until:
env.process(customer(env, f"C{i}", resource))
i += 1
Multiple Arrival Streams
Different types arriving:
def vip_arrivals(env, resource):
i = 0
while True:
yield env.timeout(random.expovariate(1/30)) # Rare
env.process(vip_customer(env, f"VIP{i}", resource))
i += 1
def regular_arrivals(env, resource):
i = 0
while True:
yield env.timeout(random.expovariate(1/5)) # Common
env.process(regular_customer(env, f"Reg{i}", resource))
i += 1
env.process(vip_arrivals(env, server))
env.process(regular_arrivals(env, server))
Seeding for Reproducibility
Always seed your random generator:
random.seed(42) # Same arrivals every run
# Or use numpy for more control
import numpy as np
rng = np.random.default_rng(seed=42)
def arrivals(env, resource):
i = 0
while True:
yield env.timeout(rng.exponential(5))
env.process(customer(env, f"C{i}", resource))
i += 1
Summary
Arrival patterns:
- Poisson (exponential intervals) - most common
- Fixed - deterministic, scheduled
- Uniform - bounded randomness
- Time-varying - peaks and troughs
- Batch - groups arriving together
Choose the pattern that matches your real system. Then validate with data.

