Skip to main content
Back to module
Optimization & Operations Research · Uncertainty

Discrete-event simulation

Reading29mLesson 2 of 3 in this module

Simulate a single-server service line with random arrivals and check the results against queueing theory.

Why it matters

Queueing formulas need tidy assumptions. Real service lines have lunch rushes, two kinds of customers, a server who takes breaks and a machine that breaks down. When the maths gets messy, OR analysts simulate: they build a small model of the system in code, run it thousands of times with random inputs, and measure what happens. Discrete-event simulation (DES) is used to design hospitals, factories, airports and warehouses, and to test a schedule or layout before anyone spends money on it.

The core idea

A discrete-event simulation jumps from one event to the next instead of ticking through every second. Nothing in the model changes between events.

  • Entities: the things that flow (customers, patients, parts).
  • Resources: what entities compete for (servers, beds, machines).
  • Events: moments when the state changes (an arrival, a service start, a departure).
  • State: what you track (queue length, which servers are busy).
  • Clock: the current simulated time.
  • Event list: future events sorted by time. The simulation repeatedly pops the earliest event, advances the clock to it, updates the state, and schedules any new events it causes.

A one-server line has two event types. An arrival joins the queue (or starts service if the server is free) and schedules the next arrival. A departure frees the server and starts the next person in line.

A one-server line in a few lines of Python

For one server, first come first served, you do not even need an explicit event list. Each customer starts service when they arrive or when the server becomes free, whichever is later:

def simulate_line(arrivals, services):
    out = []
    server_free_at = 0.0
    for arrive, service in zip(arrivals, services):
        start = max(arrive, server_free_at)
        finish = start + service
        out.append((start, finish, start - arrive))   # wait = start - arrival
        server_free_at = finish
    return out

Check it by hand. Customers arrive at times 0, 1, 2 and 5 and need 2, 2, 1 and 1 minutes:

customerarrivesstartsfinisheswaits
10020
21241
32452
45560

Hand-checking a tiny case like this is verification: making sure the code does what you meant.

Adding randomness

Random inputs come from probability distributions. For an M/M/1 coffee kiosk with 1 arrival per minute and 2 services per minute:

import random

rng = random.Random(42)            # fixed seed: the same run every time
t = 0.0
arrivals, services = [], []
for _ in range(50000):
    t += rng.expovariate(1.0)      # time to the next arrival
    arrivals.append(t)
    services.append(rng.expovariate(2.0))

Theory says Wq = rho / (mu - lambda) = 0.5 / 1 = 0.5 minutes and half of all customers wait. A long simulated run lands close to both numbers. Comparing a simulation with a case you can solve exactly is how you build trust before you add the messy details that the formulas cannot handle: a rush hour, a second barista from 8 to 10, or customers who leave when the line is longer than six.

Always fix the seed while developing, so results are reproducible and bugs can be found. Then change the seed to get independent runs.

Running experiments properly

  • Warm-up. A simulation that starts empty is not typical. Discard the first stretch of output (the warm-up period) or start from a realistic state.
  • Replications. One run is one random story. Run many independent replications (different seeds) and report the average with a confidence interval, for example mean +/- 1.96 * sd / sqrt(n).
  • Same random numbers for comparisons. When comparing two designs, give both the same seeds (common random numbers), so differences come from the design and not from luck.
  • Validation. Compare the model's output with real data (average waits last month). Verification asks "is the code right?"; validation asks "is the model right?"

Tools

In Python, SimPy is a popular open-source DES library. Processes are generator functions, and resources handle the queueing for you:

import simpy

def customer(env, counter, service_time, waits):
    arrive = env.now
    with counter.request() as req:
        yield req                          # wait for the server
        waits.append(env.now - arrive)
        yield env.timeout(service_time)    # being served

env = simpy.Environment()
counter = simpy.Resource(env, capacity=1)

Commercial tools such as Arena, AnyLogic, FlexSim and Simio add drag-and-drop modelling and animation, which helps a lot when you present to managers.

Common mistakes

  • Trusting one run. Report several replications and a confidence interval.
  • No warm-up. Starting empty makes waits look shorter than they really are.
  • Unverified code. Always test a tiny hand-checked case and a case with a known formula.
  • Wrong rates. expovariate(rate) takes a rate (per minute), not a mean time.
  • Modelling every detail on day one. Start simple, validate, then add detail.

Quick recap

  • DES jumps between events: arrivals, service starts, departures.
  • For one FIFO server, start = max(arrival, server free), wait = start - arrival.
  • Fix seeds while developing; use warm-up, replications and confidence intervals for results.
  • Verify against hand cases and formulas; validate against real data.

Try it

In the Do tab, simulate a service line and check it against the M/M/1 formula.

Also in this lesson: