Modelling Arrivals in SimPy: From Poisson to Custom Patterns

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: 1. Waits for the next arrival 2. Spawns a new entity 3. 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

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.

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