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.80
passengersim.core 0.80

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: 2
  num_samples: 400
  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()
   ╭─────────────────────────────────────╮                                                                         
   │ licensed for jpn                    │                                                                         
   │ until 2036-05-28 15:49:32+00:00 UTC │                                                                         
   │ maximum 42000 legs in network       │                                                                         
   ╰─────────────────────────────────────╯                                                                         

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-06-12>
 * bid_price_history (256 row DataFrame)
 * cabins (5600 row DataFrame)
 * carriers (2 row DataFrame)
 * carrier_history (NoneType)
 * carrier_history2 (1400 row DataFrame)
 * forecast_accuracy (NoneType)
 * 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 (68 row DataFrame)
 * fare_class_mix (12 row DataFrame)
 * path_forecasts (NoneType)
 * leg_forecasts (NoneType)
 * edgar (NoneType)
 * 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 (335 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 59399 70000 25127 9.721783e+06 84.855714 42.302059
102 AL1 102 BOS ORD 863.753 59665 70000 25093 9.702092e+06 85.235714 42.056482
201 AL2 201 BOS ORD 863.753 59413 70000 24919 9.716049e+06 84.875714 41.941999
202 AL2 202 BOS ORD 863.753 59434 70000 25127 9.683106e+06 84.905714 42.277148
111 AL1 111 ORD LAX 1739.799 74685 84000 40413 2.193187e+07 88.910714 54.111267
112 AL1 112 ORD LAX 1739.799 74926 84000 40354 2.193041e+07 89.197619 53.858474
211 AL2 211 ORD LAX 1739.799 74851 84000 40357 2.201165e+07 89.108333 53.916447
212 AL2 212 ORD LAX 1739.799 74848 84000 40541 2.195184e+07 89.104762 54.164440

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")