All articles

Airport Simulation with SimPy: From Check-In to Takeoff

Published

Airports are complex systems under constant pressure. Passengers flow through multiple touchpoints. Delays cascade. Simulation helps you see where things go wrong before they do.

The Airport Model

Components:

  • Passengers - Arrive for flights
  • Check-in - Desks and kiosks
  • Security - Screening queues
  • Gates - Boarding areas
  • Aircraft - Arrivals and departures

Basic Passenger Flow

import simpy
import random

class Airport:
    def __init__(self, env, config):
        self.env = env
        self.check_in_desks = simpy.Resource(env, capacity=config['check_in_desks'])
        self.security_lanes = simpy.Resource(env, capacity=config['security_lanes'])
        self.gates = simpy.Resource(env, capacity=config['gates'])
        self.stats = []

    def passenger(self, passenger_id, flight_time):
        arrival = self.env.now
        record = {'id': passenger_id, 'arrival': arrival, 'flight': flight_time}

        # Check-in
        with self.check_in_desks.request() as req:
            yield req
            yield self.env.timeout(random.uniform(2, 5))
        record['checked_in'] = self.env.now

        # Security
        with self.security_lanes.request() as req:
            yield req
            yield self.env.timeout(random.uniform(3, 10))
        record['cleared_security'] = self.env.now

        # Wait at gate (until boarding)
        boarding_time = flight_time - 30  # Board 30 mins before
        if self.env.now < boarding_time:
            yield self.env.timeout(boarding_time - self.env.now)

        # Board
        record['boarded'] = self.env.now
        record['made_flight'] = self.env.now < flight_time

        self.stats.append(record)

def passenger_arrivals(env, airport, flight_schedule):
    """Generate passengers for scheduled flights."""
    passenger_id = 0

    for flight_time, num_passengers in flight_schedule:
        # Passengers arrive 1-3 hours before flight
        for _ in range(num_passengers):
            arrival_offset = random.uniform(60, 180)
            arrival_time = flight_time - arrival_offset

            if arrival_time > env.now:
                yield env.timeout(arrival_time - env.now)

            env.process(airport.passenger(passenger_id, flight_time))
            passenger_id += 1

# Run
env = simpy.Environment()
airport = Airport(env, {'check_in_desks': 10, 'security_lanes': 4, 'gates': 20})

# Flight schedule: (departure_time, passengers)
flights = [(60, 150), (120, 200), (180, 180), (240, 220)]
env.process(passenger_arrivals(env, airport, flights))
env.run(until=300)

Security with Different Lanes

class SecurityCheckpoint:
    def __init__(self, env, regular_lanes, priority_lanes):
        self.env = env
        self.regular = simpy.Resource(env, capacity=regular_lanes)
        self.priority = simpy.Resource(env, capacity=priority_lanes)

    def screen_passenger(self, passenger_type):
        if passenger_type in ['business', 'frequent_flyer']:
            lane = self.priority
            process_time = random.uniform(2, 5)
        else:
            lane = self.regular
            process_time = random.uniform(4, 12)

        with lane.request() as req:
            yield req
            yield self.env.timeout(process_time)

Aircraft Turnaround

class Aircraft:
    def __init__(self, env, flight_id, aircraft_type):
        self.env = env
        self.flight_id = flight_id
        self.type = aircraft_type

    def turnaround(self, gate, ground_crew, fuel_truck, catering):
        """Complete turnaround between arrival and departure."""
        # Request gate
        with gate.request() as gate_req:
            yield gate_req

            # Parallel activities
            disembark = self.env.process(self.disembark())
            clean = self.env.process(self.clean())

            yield disembark
            yield clean

            # Sequential activities requiring resources
            with ground_crew.request() as crew:
                yield crew
                yield self.env.timeout(random.uniform(10, 20))  # Baggage

            with fuel_truck.request() as fuel:
                yield fuel
                yield self.env.timeout(random.uniform(15, 25))

            with catering.request() as cater:
                yield cater
                yield self.env.timeout(random.uniform(10, 15))

            # Board
            yield from self.board()

    def disembark(self):
        passengers = random.randint(100, 180)
        yield self.env.timeout(passengers * 0.1)

    def clean(self):
        yield self.env.timeout(random.uniform(15, 25))

    def board(self):
        passengers = random.randint(100, 180)
        yield self.env.timeout(passengers * 0.15)

Baggage Handling

class BaggageSystem:
    def __init__(self, env, num_carousels, num_handlers):
        self.env = env
        self.carousels = simpy.Resource(env, capacity=num_carousels)
        self.handlers = simpy.Resource(env, capacity=num_handlers)
        self.bags_processed = 0

    def process_flight_bags(self, flight_id, num_bags):
        """Process bags from check-in to aircraft."""
        bags_loaded = 0

        with self.carousels.request() as carousel:
            yield carousel

            while bags_loaded < num_bags:
                # Handler picks up bags
                with self.handlers.request() as handler:
                    yield handler
                    batch = min(5, num_bags - bags_loaded)
                    yield self.env.timeout(batch * 0.5)
                    bags_loaded += batch

        self.bags_processed += num_bags

Complete Airport Simulation

import simpy
import random
import numpy as np

class AirportSimulation:
    def __init__(self, env, config):
        self.env = env
        self.config = config

        # Resources
        self.check_in = simpy.Resource(env, capacity=config['check_in_desks'])
        self.kiosks = simpy.Resource(env, capacity=config['kiosks'])
        self.bag_drop = simpy.Resource(env, capacity=config['bag_drops'])
        self.security = simpy.Resource(env, capacity=config['security_lanes'])
        self.passport_control = simpy.Resource(env, capacity=config['passport_booths'])
        self.gates = simpy.Resource(env, capacity=config['gates'])

        # Stats
        self.passenger_stats = []
        self.missed_flights = 0

    def passenger(self, pax_id, flight_time, has_bags, is_international):
        arrival = self.env.now
        record = {
            'id': pax_id,
            'arrival': arrival,
            'flight_time': flight_time
        }

        # Check-in (desk or kiosk)
        if random.random() < 0.7:  # 70% use kiosk
            with self.kiosks.request() as req:
                yield req
                yield self.env.timeout(random.uniform(1, 3))
        else:
            with self.check_in.request() as req:
                yield req
                yield self.env.timeout(random.uniform(3, 7))

        # Bag drop if needed
        if has_bags:
            with self.bag_drop.request() as req:
                yield req
                yield self.env.timeout(random.uniform(2, 5))

        record['check_in_complete'] = self.env.now

        # Security
        with self.security.request() as req:
            yield req
            yield self.env.timeout(random.triangular(3, 15, 6))

        record['security_complete'] = self.env.now

        # Passport control for international
        if is_international:
            with self.passport_control.request() as req:
                yield req
                yield self.env.timeout(random.uniform(1, 3))

        record['airside'] = self.env.now

        # Check if made it in time for boarding
        boarding_time = flight_time - 30
        if self.env.now > boarding_time:
            self.missed_flights += 1
            record['made_flight'] = False
        else:
            record['made_flight'] = True
            # Wait for boarding
            if self.env.now < boarding_time:
                yield self.env.timeout(boarding_time - self.env.now)

        record['total_time'] = self.env.now - arrival
        self.passenger_stats.append(record)

    def flight_passengers(self, flight_time, num_pax, is_international):
        """Generate passengers for a flight."""
        for i in range(num_pax):
            # Arrival distribution: 60-180 mins before flight
            advance = random.triangular(60, 180, 120)
            arrival_time = max(0, flight_time - advance)

            # Wait until arrival time
            if arrival_time > self.env.now:
                yield self.env.timeout(arrival_time - self.env.now)

            has_bags = random.random() < 0.7
            self.env.process(self.passenger(
                f"{flight_time}_{i}",
                flight_time,
                has_bags,
                is_international
            ))

            # Small gap between passenger arrivals
            yield self.env.timeout(random.uniform(0.1, 0.5))

    def run(self, flight_schedule):
        """Run simulation with flight schedule."""
        for flight_time, num_pax, is_intl in flight_schedule:
            self.env.process(self.flight_passengers(flight_time, num_pax, is_intl))

        # Run until last flight + buffer
        last_flight = max(f[0] for f in flight_schedule)
        self.env.run(until=last_flight + 60)

    def report(self):
        pax = self.passenger_stats
        made_it = [p for p in pax if p['made_flight']]

        print("\n=== Airport Simulation Report ===")
        print(f"Total passengers: {len(pax)}")
        print(f"Made flight: {len(made_it)} ({len(made_it)/len(pax):.1%})")
        print(f"Missed flight: {self.missed_flights}")

        if made_it:
            total_times = [p['total_time'] for p in made_it]
            security_times = [p['security_complete'] - p['check_in_complete']
                            for p in made_it if 'security_complete' in p]

            print(f"\nTotal journey time (check-in to gate):")
            print(f"  Mean: {np.mean(total_times):.1f} min")
            print(f"  90th pct: {np.percentile(total_times, 90):.1f} min")

            print(f"\nSecurity wait + process:")
            print(f"  Mean: {np.mean(security_times):.1f} min")

# Config
config = {
    'check_in_desks': 15,
    'kiosks': 20,
    'bag_drops': 10,
    'security_lanes': 8,
    'passport_booths': 6,
    'gates': 30
}

# Flight schedule: (departure_time, passengers, is_international)
schedule = [
    (60, 180, True),
    (90, 150, False),
    (120, 200, True),
    (150, 160, False),
    (180, 220, True),
    (210, 190, False),
    (240, 175, True),
]

random.seed(42)
env = simpy.Environment()
sim = AirportSimulation(env, config)
sim.run(schedule)
sim.report()

Summary

Airport simulation captures:

  • Multi-stage passenger journey
  • Parallel and sequential processes
  • Time-critical deadlines
  • Resource contention at bottlenecks
  • Flight schedule dependencies

Every airport is a system. Simulate to optimise.

Next Steps

Ready to build a model of your own?

The free SimPy Handbook covers the foundations; the Simulation Bootcamp covers everything else, from first principles to models a business will act on.