Basic Simulation

In this example, we will demonstrate some of the basics of PassengerSim. We’ll interface with the tool through the passengersim Python interface, which is imported into Python in the same manner as any other Python library.

import passengersim as pax

pax.versions()
passengersim 0.93
passengersim.core 0.93

Using PassengerSim generally involves three steps: configuring a simulation, running it, and analyzing the results. The first and last of these steps rely primarily on the open-source passengersim code that runs entirely in Python, while running a simulation relies on the passengersim.core library, which is the proprietary licensed code.

To get started, we will load a PassengerSim configuration from YAML input files. These files are structured plain text inputs that define everything needed to run a simulation: carriers, flight legs, passenger choice models, simulation controls, and more. The demo YAML file shown below shows how one of these YAML input files might be structured, with “include” statements that point to files containing much of the fundamental network configuration and a handful of specific settings can supplement or replace settings from the included files.

from passengersim.utils.codeview import show_file

show_file(pax.demo_network("3MKT/DEMO"))
include:
  - 01-base.yaml
  - 02-buyup.yaml
  - 03-ap.yaml
  - 14-heur.yaml

simulation_controls:
  demand_multiplier: 1.0
  num_trials: 3
  num_samples: 300
  burn_samples: 50
  random_seed: 314

carriers:
  AL1:
    rm_system: E
  AL2:
    rm_system: E

db: null

outputs:
  reports: []

We can load the YAML files into a Config object, which will both handle loading the raw content and validating that it includes everything necessary to prepare a PassengerSim simulation.

cfg = pax.Config.from_yaml(pax.demo_network("3MKT/DEMO"))

After loading, it’s possible to modify the Config object in Python to change aspects of a simulation. For this example, we won’t change anything here, and just create the Simulation object from the existing Config. We can choose to create a regular Simulation which will run all trials and samples sequentially in a single process, or a MultiSimulation which will run each trial in parallel, with each trial running in a seperate subprocess. The later option is generally much faster on multi-core computers, but detailed simulation model development or debugging can usually be done more conveniently in the single process workflow.

Since our demo is configured to run two trials, we’ll run a MultiSimulation.

sim = pax.MultiSimulation(cfg)

Running the simulation is as simple as calling the run command, which runs the simulation and returns a summary output object.

summary = sim.run()

By default, PassengerSim will generate a summary output that includes a wide variety of aggregated summary output data, which can be reviewed or visualized using an included suite of analysis tools.

summary
<passengersim.summaries.SimulationTables created on 2026-10-13>
 * bid_price_history (384 row DataFrame)
 * cabins (6000 row DataFrame)
 * carriers (2 row DataFrame)
 * carrier_history (NoneType)
 * carrier_history2 (1500 row DataFrame)
 * forecast_accuracy (NoneType)
 * spill (12 row DataFrame)
 * cp_segmentation (12 row DataFrame)
 * demand_to_come_summary (34 row DataFrame)
 * demand_to_come (NoneType)
 * demands (6 row DataFrame)
 * demand_history (NoneType)
 * displacement_history (102 row DataFrame)
 * fare_class_mix (12 row DataFrame)
 * path_forecasts (NoneType)
 * leg_forecasts (NoneType)
 * edgar (NoneType)
 * leg_bid_price_history (512 row DataFrame)
 * legbuckets (48 row DataFrame)
 * legs (8 row DataFrame)
 * leg_detail (NoneType)
 * local_and_flow_yields (NoneType)
 * pathclasses (72 row DataFrame)
 * path_legs (16 row DataFrame)
 * paths (12 row DataFrame)
 * segmentation_by_timeframe (502 row DataFrame)
 * segmentation_detail (NoneType)
<*>

The raw data of the summary tables shown can be accessed via direct attribute access on the SimulationTables object. For example, we can see the summary data collected on the legs like this:

summary.legs
carrier flt_no orig dest distance gt_sold gt_capacity gt_sold_local gt_revenue avg_load_factor avg_local
leg_id
101 AL1 101 BOS ORD 863.753 63374 75000 26354 1.031126e+07 84.498667 41.584877
102 AL1 102 BOS ORD 863.753 63691 75000 26498 1.030775e+07 84.921333 41.603994
201 AL2 201 BOS ORD 863.753 63329 75000 26407 1.034801e+07 84.438667 41.698116
202 AL2 202 BOS ORD 863.753 63470 75000 26490 1.032119e+07 84.626667 41.736253
111 AL1 111 ORD LAX 1739.799 80699 90000 43679 2.361579e+07 89.665556 54.125826
112 AL1 112 ORD LAX 1739.799 80771 90000 43578 2.358700e+07 89.745556 53.952532
211 AL2 211 ORD LAX 1739.799 80474 90000 43552 2.368296e+07 89.415556 54.119343
212 AL2 212 ORD LAX 1739.799 80539 90000 43559 2.363938e+07 89.487778 54.084357

In addition to accessing the raw data, PassengerSim also has a built-in collection of visualization tools for reviewing the data. These tools include aggregate summary figures, e.g. some figures showing carrier revenue, load factors, or RASM.

summary.fig_carrier_revenues()
summary.fig_carrier_load_factors()
summary.fig_carrier_rasm()

We can also examine more detailed slices of simulation results. One commonly used visualization show the fare class mix across all bookings on each carrier.

summary.fig_fare_class_mix()

We can also dive deeper into this fare class data, to look not just at how many bookings are in each fare class, but also when those bookings occured and what passenger segments they came from. The figure below clearly shows that the lower fare classes are booked almost exclusively by leisure travlers, and primarily in the early time frames.

summary.fig_bookings_by_timeframe(by_class=True, by_carrier="AL1")

We can also do a deeper dive into the passengers on an individual leg, to look at the breakdown of their path origins and destinations, and the fare classes sold on that particular leg.

summary.fig_select_leg_analysis(101)

A different perspective on the results can be seen in this figure of carrier revenue results, which are plotted sample-by-sample. Here we can see how carrier do against each other: if AL1 is having a good day, is AL2 also having a similarly good day?

summary.fig_carrier_head_to_head_revenue("AL1", "AL2")