Summary Outputs¶
When you run a simulation in PassengerSim, you will get a
SimulationTables object. This objects embeds a variety
of summary information from the run.
import passengersim as pax
pax.versions()
passengersim 0.93
passengersim.core 0.93
cfg = pax.Config.from_yaml(pax.demo_network("3MKT/08-untrunc-em"))
cfg.simulation_controls.num_samples = 300
cfg.simulation_controls.num_trials = 2
sim = pax.Simulation(cfg)
summary = sim.run()
Task Completed after 2.77 seconds
The simple repr of this object has a bit of information about what data is in there. You can view
this in a Jupyter notebook by putting the object as the last line of a cell (or just by itself):
summary
<passengersim.summaries.SimulationTables created on 2026-10-13>
* bid_price_history (256 row DataFrame)
* cabins (4000 row DataFrame)
* carriers (2 row DataFrame)
* carrier_history2 (1000 row DataFrame)
* forecast_accuracy (NoneType)
* spill (12 row DataFrame)
* cp_segmentation (12 row DataFrame)
* demand_to_come_summary (34 row DataFrame)
* demands (6 row DataFrame)
* demand_history (NoneType)
* displacement_history (68 row DataFrame)
* fare_class_mix (12 row DataFrame)
* leg_bid_price_history (512 row DataFrame)
* legbuckets (48 row DataFrame)
* legs (8 row DataFrame)
* pathclasses (72 row DataFrame)
* path_legs (16 row DataFrame)
* paths (12 row DataFrame)
* segmentation_by_timeframe (336 row DataFrame)
* carrier_history (500 row DataFrame)
* demand_to_come (3000 row DataFrame)
* leg_forecasts (816 row DataFrame)
<*>
We can see here there are a variety of tables stored as pandas DataFrames. We can access the raw
values of any of these dataframes directly in Python as an attribute on the
SimulationTables object.
summary.carriers
| control | avg_rev | avg_sold | truncation_rule | avg_leg_lf | asm | rpm | ancillary_rev | avg_local_leg_pax | avg_total_leg_pax | cp_sold | cp_revenue | rm_system | avg_price | yield | rasm | sys_lf | local_pct_leg_pax | local_pct_bookings | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| carrier | |||||||||||||||||||
| AL1 | leg | 90393.65 | 284.432 | 3 | 86.369167 | 590302.36 | 516618.181268 | 0.0 | 187.732 | 381.132 | 0.0 | 0.0 | E | 317.804080 | 0.174972 | 0.153131 | 87.517553 | 49.256426 | 66.002419 |
| AL2 | leg | 89970.05 | 284.478 | 3 | 86.394917 | 590302.36 | 516235.905594 | 0.0 | 187.798 | 381.158 | 0.0 | 0.0 | E | 316.263648 | 0.174281 | 0.152414 | 87.452794 | 49.270381 | 66.014947 |
Metadata¶
There is also some metadata stored on the summary, which can be accessed via the
metadata method.
summary.metadata()
{'time.created': '2026-10-13T04:27:23.074864+00:00',
'machine.system': 'Darwin',
'machine.release': '25.6.0',
'machine.version': 'Darwin Kernel Version 25.6.0: Fri Jul 31 19:19:08 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6050',
'machine.machine': 'arm64',
'machine.processor': 'arm',
'machine.architecture': ('64bit', ''),
'machine.node': 'Driftless.local',
'machine.platform': 'macOS-26.6.2-arm64-arm-64bit',
'machine.python_version': '3.12.13',
'machine.cpu_count': 18,
'version.passengersim': '0.93',
'version.passengersim_core': '0.93',
'outputs.disk_filename': PosixPath('/Users/jpn/Git/pax/documentation/user-guide/passengersim-output.20261012-232720.pxsim')}
You can access the metadata for a specific key by passing that key as an argument to the method.
summary.metadata("time")
{'created': '2026-10-13T04:27:23.074864+00:00'}
assert "created" in summary.metadata("time").keys()
assert "time.created" in summary.metadata().keys()
Configuration Used¶
The summary also includes the complete configuration used for this simulation run, to make it easy for users to see how the results were generated, and to replicate them if needed.
summary.config
passengersim.Config:
tags:
scenario: '3MKT'
simulation_controls: <simulation controls with 2 trials, 300 samples, 50 burn>
db: <database config, engine=sqlite, filename=:memory:>
outputs: <outputs config>
rm_systems:
blf_curves:
frat5_curves: <dict of 0 frat5_curves>
load_factor_curves:
todd_curves:
choice_models: <dict of 2 choice_models>
carriers:
AL1: <carrier config, rm_system=E>
AL2: <carrier config, rm_system=E>
places: <dict of 3 places>
circuity_rules: []
classes: ['Y0', 'Y1', 'Y2', 'Y3', 'Y4', 'Y5']
dcps: [63, 56, 49, 42, 35, 31, 28, 24, 21, 17, 14, 10, 7, 5, 3, 1]
booking_curves: <dict of 2 booking_curves>
legs: <list of 8 legs>
demands: <list of 6 demands>
fares: <list of 36 fares>
paths: <list of 0 paths>
markets: [Market(orig='BOS', dest='ORD', demand_multiplier=1.0, delta_t=None), Market(orig='ORD', dest='LAX', demand_multiplier=1.0, delta_t=None), Market(orig='BOS', dest='LAX', demand_multiplier=1.0, delta_t=None)]
other_controls:
snapshot_filters: []
dwm_tolerance: []
Saving and Restoring¶
Running a PassengerSim simulation on a practical network can take some time, so it is desirable to
save your results after a run. This allows you to come back to the analyze those results later, or
compare against other future scenario permutations. Results can be saved using the
SummaryTables.to_file method.
# ensure the saved-outputs directory starts off empty
import shutil
shutil.rmtree("saved-outputs", ignore_errors=True)
summary.to_file("saved-outputs/summary")
PosixPath('saved-outputs/summary.20261012-232723.pxsim')
The method will automatically add the appropriate file extension and write the file to disk. By default, it will also add a timestamp, so that you will not overwrite existing similar files.
from passengersim.utils.show_dir import display_directory_contents
display_directory_contents("saved-outputs")
saved-outputs/
summary.20261012-232723.pxsim
Restoring from this file can be done, surprisingly enough, using the
SimulationTables.from_file classmethod. You
can call this function with the same filename as to_file, and it will load the file with the most
recent timestamp if there is one or more matching file(s) with various timestamps. To load a
specific file that may or may not be the most recent, you can call this method with the complete
actual filename, including the timestamp and “.pxsim” suffix.
resummary = pax.SimulationTables.from_file("saved-outputs/summary")
resummary
<passengersim.summaries.SimulationTables created on 2026-10-13>
* bid_price_history (available in file storage)
* cabins (available in file storage)
* carrier_history (available in file storage)
* carrier_history2 (available in file storage)
* carriers (available in file storage)
* cp_segmentation (available in file storage)
* demand_history (available in file storage)
* demand_to_come (available in file storage)
* demand_to_come_summary (available in file storage)
* demands (available in file storage)
* displacement_history (available in file storage)
* fare_class_mix (available in file storage)
* forecast_accuracy (available in file storage)
* leg_bid_price_history (available in file storage)
* leg_forecasts (available in file storage)
* legbuckets (available in file storage)
* legs (available in file storage)
* path_legs (available in file storage)
* pathclasses (available in file storage)
* paths (available in file storage)
* segmentation_by_timeframe (available in file storage)
* spill (available in file storage)
<*>
When opening the file, only the most basic metadata is actually loaded by the
from_file method, and the rest is identified
as available on demand from storage. You can confirm which file is used as the storage, as that is
added to the metadata at load time:
resummary.metadata("store")
{'filename': 'saved-outputs/summary.20261012-232723.pxsim'}
Accessing data will load just that table from the file. This includes accessing a table explicitly (by calling for it), or implicitly (e.g. by creating a figure using the data). The loading process happens transparently – you don’t need to specifically load anything, it will just happen automatically as needed.
resummary.carriers
| control | avg_rev | avg_sold | truncation_rule | avg_leg_lf | asm | rpm | ancillary_rev | avg_local_leg_pax | avg_total_leg_pax | cp_sold | cp_revenue | rm_system | avg_price | yield | rasm | sys_lf | local_pct_leg_pax | local_pct_bookings | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| carrier | |||||||||||||||||||
| AL1 | leg | 90393.65 | 284.432 | 3 | 86.369167 | 590302.36 | 516618.181268 | 0.0 | 187.732 | 381.132 | 0.0 | 0.0 | E | 317.804080 | 0.174972 | 0.153131 | 87.517553 | 49.256426 | 66.002419 |
| AL2 | leg | 89970.05 | 284.478 | 3 | 86.394917 | 590302.36 | 516235.905594 | 0.0 | 187.798 | 381.158 | 0.0 | 0.0 | E | 316.263648 | 0.174281 | 0.152414 | 87.452794 | 49.270381 | 66.014947 |
resummary.fig_fare_class_mix()
We can see in the objects repr that the carriers and fare_class_mix tables have been loaded, but
the rest are still only in the storage file.
resummary
<passengersim.summaries.SimulationTables created on 2026-10-13>
* carriers (2 row DataFrame)
* fare_class_mix (12 row DataFrame)
* bid_price_history (available in file storage)
* cabins (available in file storage)
* carrier_history (available in file storage)
* carrier_history2 (available in file storage)
* cp_segmentation (available in file storage)
* demand_history (available in file storage)
* demand_to_come (available in file storage)
* demand_to_come_summary (available in file storage)
* demands (available in file storage)
* displacement_history (available in file storage)
* forecast_accuracy (available in file storage)
* leg_bid_price_history (available in file storage)
* leg_forecasts (available in file storage)
* legbuckets (available in file storage)
* legs (available in file storage)
* path_legs (available in file storage)
* pathclasses (available in file storage)
* paths (available in file storage)
* segmentation_by_timeframe (available in file storage)
* spill (available in file storage)
<*>
In addition to the summary data tables, the complete configuration used is also still available, in case it is needed.
resummary.config
/Users/jpn/Git/pax/.venv/lib/python3.12/site-packages/pydantic/main.py:732: UserWarning: Only 500 usable samples remain after burning 50 of 300 samples across 2 trials; at least 750 usable samples are recommended to ensure meaningful results.
return cls.__pydantic_validator__.validate_python(
passengersim.Config:
tags:
scenario: '3MKT'
simulation_controls: <simulation controls with 2 trials, 300 samples, 50 burn>
db: <database config, engine=sqlite, filename=:memory:>
outputs: <outputs config>
rm_systems:
blf_curves:
frat5_curves: <dict of 0 frat5_curves>
load_factor_curves:
todd_curves:
choice_models: <dict of 2 choice_models>
carriers:
AL1: <carrier config, rm_system=E>
AL2: <carrier config, rm_system=E>
places: <dict of 3 places>
circuity_rules: []
classes: ['Y0', 'Y1', 'Y2', 'Y3', 'Y4', 'Y5']
dcps: [63, 56, 49, 42, 35, 31, 28, 24, 21, 17, 14, 10, 7, 5, 3, 1]
booking_curves: <dict of 2 booking_curves>
legs: <list of 8 legs>
demands: <list of 6 demands>
fares: <list of 36 fares>
paths: <list of 0 paths>
markets: [Market(orig='BOS', dest='ORD', demand_multiplier=1.0, delta_t=None), Market(orig='ORD', dest='LAX', demand_multiplier=1.0, delta_t=None), Market(orig='BOS', dest='LAX', demand_multiplier=1.0, delta_t=None)]
other_controls:
snapshot_filters: []
dwm_tolerance: []
Pickling¶
In addition to the pxsim file format, PassengerSim also supports pickle serialization. This file
format loses some of the niceties of the pxsim format, such as lazy data loading. But it is a
widely adopted standard format for Python serialization which is used across many Python libraries.
If the LZ4 library is available, it will also use that for compression, which can result in a
marginally smaller filesize than the pxsim format.
summary.to_pickle("saved-outputs/summary")
display_directory_contents("saved-outputs")
saved-outputs/
summary.20261012-232723.pxsim
summary.20261012-232723.pkl.lz4
Restoring from this pickle file can be done, surprisingly enough, using the from_pickle method.
You can call this method with the same filename as to_pickle, and it will load the file with the
most recent timestamp if there is one or more matching file(s) with various timestamps. To load a
specific pickle file that may or may not be the most recent, you can call this method with the
complete actual filename, including the timestamp and “.pkl” or “.pkl.lz4” suffix.
resummary2 = pax.SimulationTables.from_pickle("saved-outputs/summary")
resummary2
<passengersim.summaries.SimulationTables created on 2026-10-13>
* bid_price_history (256 row DataFrame)
* cabins (4000 row DataFrame)
* carriers (2 row DataFrame)
* carrier_history2 (1000 row DataFrame)
* forecast_accuracy (NoneType)
* spill (12 row DataFrame)
* cp_segmentation (12 row DataFrame)
* demand_to_come_summary (34 row DataFrame)
* demands (6 row DataFrame)
* demand_history (NoneType)
* displacement_history (68 row DataFrame)
* fare_class_mix (12 row DataFrame)
* leg_bid_price_history (512 row DataFrame)
* legbuckets (48 row DataFrame)
* legs (8 row DataFrame)
* pathclasses (72 row DataFrame)
* path_legs (16 row DataFrame)
* paths (12 row DataFrame)
* segmentation_by_timeframe (336 row DataFrame)
* carrier_history (500 row DataFrame)
* demand_to_come (3000 row DataFrame)
* leg_forecasts (816 row DataFrame)
<*>
You may note the summary that is reloaded from the pickle file is fully populated, with every table showing as a DataFrame instead of “available from store”. Since the pickle format does not include lazy data access, loading anything from a pickle requires loading everything from the pickle, which may take some time for larger simulations.
Large PXSIM outputs and compatibility¶
New PXSIM exports use chunked storage, allowing a result to exceed SQLite’s single-value size limit. Existing PXSIM files remain readable without conversion. Saving a new copy uses the new format, which requires a current reader; older PassengerSim releases cannot read it.
Loading remains lazy by summary table, but accessing a table still needs enough memory for the complete restored object. For schema, compression, validation, and compatibility details, see PXSIM storage format.
To protect an archive from explicit data removal, open it read-only:
summary = pax.SimulationTables.from_file("results.pxsim", read_only=True)
Loading an archive restores its summaries, configuration, and metadata. Detailed simulation data stays in a separate SQLite file and is not opened automatically. Close the result, or use a context manager, to release the archive handle. See summary archives and caches for keeping or deleting detailed data independently of saved summaries.
Only load trusted PXSIM files: they contain Python pickle payloads. Integrity checks detect corruption but do not authenticate the source.
Simulation size defaults and migration¶
New configurations that omit simulation counts now use 3 trials, 350 samples per trial, and 50 burn samples per trial. That means 1,050 simulated samples and 900 usable samples across the trials. Previously, the defaults were 1 trial, 600 samples, and 100 burn samples, producing 500 usable samples. This changes runtime, resource use, and statistical results for scripts that rely on defaults. It is a simulation configuration change, independent of the PXSIM storage format.
Explicit counts in existing configurations, including configurations restored from PXSIM files, remain unchanged. To retain the earlier run size, specify:
simulation_controls:
num_trials: 1
num_samples: 600
burn_samples: 100
PassengerSim recommends at least 50 burn samples per trial and 750 usable samples across trials; the earlier defaults therefore produce a usable-sample warning. See Counting Simulations for the distinction between trials, samples, and burn periods.