Simple FCFS¶
This is a very simple network, with only 3 markets, used to demonstrate some features of PassengerSim.
import passengersim as pax
pax.versions()
passengersim 0.57.dev4+g8e386693f passengersim.core 0.57.dev0+g85ef573c.d20251014
This example uses network/01-base.yaml configuration file.
Within a Jupyter notebook, we can directly initialize a PassengerSim Simulation
instance from this file using the from_yaml class constructor:
sim = pax.Simulation.from_yaml("network/01-base.yaml")
/var/folders/l1/yt63lf3n60b1dc25d_y2d1q80000gn/T/ipykernel_39966/2459294051.py:1: UserWarning: Restriction 'R2' found in fares but not used in any choice model
sim = pax.Simulation.from_yaml("network/01-base.yaml")
/var/folders/l1/yt63lf3n60b1dc25d_y2d1q80000gn/T/ipykernel_39966/2459294051.py:1: UserWarning: Restriction 'R1' found in fares but not used in any choice model
sim = pax.Simulation.from_yaml("network/01-base.yaml")
/var/folders/l1/yt63lf3n60b1dc25d_y2d1q80000gn/T/ipykernel_39966/2459294051.py:1: UserWarning: Restriction 'R3' found in fares but not used in any choice model
sim = pax.Simulation.from_yaml("network/01-base.yaml")
Running the simulation is as simple as calling the run command, which runs the simulation and returns a summary output object.
summary = sim.run()
Task Completed after 6.10 seconds
The contents of the final summary is controlled by Config.outputs.reports, which allows the user to add reports for more detail,
or drop some unneccessary reports to improve runtime (sometimes substantially).
sim.config.outputs.reports
{'bookings_by_timeframe',
'carrier_history',
'demand_to_come',
'fare_class_mix',
'leg_forecasts',
'load_factors',
'total_demand'}
For this example, several default reports are included, which allows us to access a number of pre-packaged visualizations for the results.
summary.fig_carrier_revenues()
summary.fig_carrier_load_factors()
summary.fig_carrier_mileage()
summary.fig_fare_class_mix()
All demand is in the lowest fare class, because the simulation has no restrictions against customers simply buying the least expensive fare.
summary.fig_bookings_by_timeframe()
We are not limited to the pre-packaged visualizations. The various summary tables available in the summary
object are all just regular pandas DataFrames, so we can use all the usual Python and Pandas tools for analysis.
For example, the demand_to_come table summarizes the total demand to come at each timeframe for every simulation sample.
summary.demand_to_come
| days_prior | 63 | 56 | 49 | 42 | 35 | 31 | 28 | 24 | 21 | 17 | 14 | 10 | 7 | 5 | 3 | 1 | 0 | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| iteration | trial | sample | segment | orig | dest | |||||||||||||||||
| 0 | 0 | 100 | business | BOS | LAX | 65 | 61 | 57 | 55 | 51 | 51 | 49 | 48 | 42 | 39 | 39 | 31 | 25 | 19 | 14 | 4 | 0 |
| ORD | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||||
| ORD | LAX | 68 | 63 | 59 | 59 | 54 | 52 | 50 | 48 | 47 | 44 | 36 | 32 | 25 | 20 | 16 | 4 | 0 | ||||
| leisure | BOS | LAX | 71 | 58 | 56 | 49 | 43 | 39 | 35 | 30 | 28 | 21 | 16 | 12 | 7 | 7 | 4 | 1 | 0 | |||
| ORD | 49 | 43 | 39 | 34 | 32 | 31 | 27 | 24 | 19 | 15 | 13 | 11 | 11 | 10 | 6 | 1 | 0 | |||||
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ||
| 499 | business | BOS | ORD | 83 | 77 | 75 | 72 | 70 | 66 | 65 | 62 | 59 | 56 | 55 | 45 | 36 | 27 | 21 | 2 | 0 | ||
| ORD | LAX | 155 | 141 | 134 | 131 | 120 | 115 | 114 | 111 | 106 | 99 | 91 | 81 | 62 | 46 | 32 | 12 | 0 | ||||
| leisure | BOS | LAX | 215 | 182 | 165 | 148 | 126 | 113 | 109 | 89 | 84 | 64 | 51 | 42 | 35 | 35 | 20 | 7 | 0 | |||
| ORD | 133 | 111 | 105 | 91 | 76 | 65 | 58 | 49 | 47 | 33 | 26 | 18 | 18 | 15 | 6 | 2 | 0 | |||||
| ORD | LAX | 198 | 165 | 146 | 132 | 114 | 102 | 95 | 74 | 57 | 51 | 43 | 32 | 24 | 21 | 11 | 4 | 0 |
2400 rows × 17 columns
With this data, we can do whatever analysis we like. Here we'll compute the correlation between total demands (from the beginning of the booking curve at DCP 63 all the way to the end) of different passenger types in different markets.
summary.demand_to_come[63].unstack(["segment", "orig", "dest"]).corr()
| segment | business | leisure | ||||||
|---|---|---|---|---|---|---|---|---|
| orig | BOS | ORD | BOS | ORD | ||||
| dest | LAX | ORD | LAX | LAX | ORD | LAX | ||
| segment | orig | dest | ||||||
| business | BOS | LAX | 1.000000 | 0.083927 | 0.231989 | 0.337721 | 0.109801 | 0.136725 |
| ORD | 0.083927 | 1.000000 | 0.132940 | 0.117638 | 0.371579 | 0.111497 | ||
| ORD | LAX | 0.231989 | 0.132940 | 1.000000 | 0.190033 | 0.217089 | 0.352117 | |
| leisure | BOS | LAX | 0.337721 | 0.117638 | 0.190033 | 1.000000 | 0.156787 | 0.140052 |
| ORD | 0.109801 | 0.371579 | 0.217089 | 0.156787 | 1.000000 | 0.147393 | ||
| ORD | LAX | 0.136725 | 0.111497 | 0.352117 | 0.140052 | 0.147393 | 1.000000 | |
The summary object also has a command to dump all the summary tables to an Excel workbook, if you prefer to analyze the results there instead of in Jupyter.
summary.to_xlsx("outputs/3mkt-01.xlsx")
Comparing against Targets¶
In addition to summary reports for a single run, we can also use PassengerSim's contrast package to compare simulation runs to each other, or against exogenously defined target results.
import targets
target = targets.load(1, sim.config)
from passengersim import contrast
comps = contrast.Contrast(
{
"simulation": summary,
"target": target,
}
)
comps.fig_carrier_revenues()
comps.fig_bookings_by_timeframe(by_carrier="AL1")
comps.fig_segmentation_by_timeframe("bookings", by_carrier=False, by_class=True)
We can look at carrier forecasts of demand on individual legs.
comps.fig_leg_forecasts(by_leg_id=111, of=["mu", "sigma"])
We can compare the mean and standard deviation of demand to come.
comps.fig_demand_to_come("mean") | comps.fig_demand_to_come("std")
We can even take arbitrary functions that apply pandas tools, and have them run automatically against multiple summary objects. For example, we can look at the variance-covariance matrix of aggregate demand by passenger type, and compare those matrices for both the simulation and the target.
comps.apply(lambda s: s.aggregate_demand_history(by_segment=True).unstack("segment").cov())
| segment | business | leisure | |
|---|---|---|---|
| source | segment | ||
| simulation | business | 3665.319273 | 2224.550439 |
| leisure | 2224.550439 | 5590.625157 | |
| target | business | 3249.598992 | 1955.419868 |
| leisure | 1955.419868 | 5359.555328 |