Europa Baseline Network¶
This page reviews the configuration of the Europa network and runs a baseline simulation, with all
three carriers using the same revenue management system (E). See the
Europa network overview for a description of the carriers and their hubs.
import altair as alt
import passengersim as pax
from passengersim.config.checks import check_config
pax.versions()
passengersim 0.94
passengersim.core 0.94
cfg = pax.Config.from_yaml(pax.demo_network("europa"))
Route Maps¶
cfg.plotly_route_map("Brat", center_lon=31, center_lat=52, zoom=2.2)
cfg.plotly_route_map("Stroop", center_lon=31, center_lat=52, zoom=2.2)
cfg.plotly_route_map("Tapas", center_lon=0, center_lat=50, zoom=4.5)
For this example page, we will pre-build the flight connections, so we can do some pre-simulation checking to review our network and verify it is constructed correctly.
from passengersim.connection_builder import prebuild_connections
cfg = prebuild_connections(cfg, inplace=False)
Hub Flight Schedules¶
cfg.fig_hub_schedule("AMS")
cfg.fig_hub_schedule("FRA")
cfg.fig_hub_schedule("MAD")
Check Configuration¶
check_config(cfg)
✅ fare classes match carrier booking classes
✅ no fares without demands
✅ no markets without fares
✅ market lowest fares match configured fare prices
✅ no demands without fares
✅ no demands without paths
✅ fare restrictions: 4 unique restrictions found
- R2: 60 occurrences
- R1: 176 occurrences
- R4: 20 occurrences
- R3: 40 occurrences
✅ reference price scaling is consistent
✅ choice model restrictions: 4 unique restrictions found
- r1: {'business': 0.28, 'leisure': 0.3}
- r2: {'business': 0.4, 'leisure': 0.3}
- r3: {'business': 0.3, 'leisure': 0.38}
- r4: {'business': 0.22, 'leisure': 0.22}
✅ choice model and fare restrictions match
List of Markets¶
cfg.dataframes.markets
| orig | dest | demand_multiplier | delta_t | distance | lowest_fare | |
|---|---|---|---|---|---|---|
| 0 | KEF | MAD | 1.0 | None | 1794.496 | 220.0 |
| 1 | KEF | AMS | 1.0 | None | 1265.292 | 220.0 |
| 2 | MAD | AMS | 1.0 | None | 905.703 | 100.0 |
| 3 | CDG | AMS | 1.0 | None | 247.307 | 85.0 |
| 4 | JFK | AMS | 1.0 | None | 3631.022 | 800.0 |
| 5 | KEF | FRA | 1.0 | None | 1490.664 | 280.0 |
| 6 | MAD | FRA | 1.0 | None | 880.763 | 100.0 |
| 7 | CDG | FRA | 1.0 | None | 277.519 | 90.0 |
| 8 | AMS | FRA | 1.0 | None | 227.108 | 75.0 |
| 9 | JFK | FRA | 1.0 | None | 3842.424 | 800.0 |
| 10 | KEF | HEL | 1.0 | None | 1517.821 | 380.0 |
| 11 | MAD | HEL | 1.0 | None | 1829.061 | 250.0 |
| 12 | CDG | HEL | 1.0 | None | 1177.052 | 300.0 |
| 13 | AMS | HEL | 1.0 | None | 944.658 | 200.0 |
| 14 | FRA | HEL | 1.0 | None | 956.086 | 200.0 |
| 15 | JFK | HEL | 1.0 | None | 4102.818 | 1100.0 |
| 16 | KEF | ATH | 1.0 | None | 2608.413 | 380.0 |
| 17 | MAD | ATH | 1.0 | None | 1476.248 | 200.0 |
| 18 | CDG | ATH | 1.0 | None | 1309.374 | 250.0 |
| 19 | AMS | ATH | 1.0 | None | 1354.762 | 270.0 |
| 20 | FRA | ATH | 1.0 | None | 1128.011 | 220.0 |
| 21 | JFK | ATH | 1.0 | None | 4925.660 | 900.0 |
| 22 | KEF | WAW | 1.0 | None | 1741.344 | 380.0 |
| 23 | MAD | WAW | 1.0 | None | 1409.401 | 260.0 |
| 24 | CDG | WAW | 1.0 | None | 833.625 | 210.0 |
| 25 | AMS | WAW | 1.0 | None | 683.775 | 150.0 |
| 26 | FRA | WAW | 1.0 | None | 557.831 | 170.0 |
| 27 | JFK | WAW | 1.0 | None | 4252.119 | 950.0 |
| 28 | KEF | SIN | 1.0 | None | 7174.082 | 1400.0 |
| 29 | MAD | SIN | 1.0 | None | 7067.757 | 1300.0 |
| 30 | CDG | SIN | 1.0 | None | 6659.469 | 1400.0 |
| 31 | AMS | SIN | 1.0 | None | 6528.644 | 1100.0 |
| 32 | FRA | SIN | 1.0 | None | 6384.186 | 1100.0 |
Market Pricing Distribution¶
alt.Chart(cfg.dataframes.markets.eval("price_per_mile = lowest_fare / distance")).mark_point().encode(
x=alt.X("distance", title="Market Distance (miles)"),
y=alt.Y("price_per_mile", title="Lowest Fare Price per Mile", scale=alt.Scale(zero=False)),
tooltip=["orig", "dest", "distance", "lowest_fare", "price_per_mile"],
color="orig",
shape="dest",
)
Here we have plotted the lowest price fare per mile against the distance. We can see the cluster of lower priced markets with MAD origins (reflecting the LCC that is dominant there) as well as cluster of higher priced markets that are effectively controlled by legacy carriers flying into fortress hubs with zero or nearly zero competition. Note none of this is an outcome of the simulation, this is merely the input/setup of pricing for the network.
Carrier Service Capacity¶
We can calculate the number of seats offered by each carrier on each served route.
cfg.dataframes.legs.groupby(["carrier", "orig", "dest"])[["capacity"]].sum().reset_index()
| carrier | orig | dest | capacity | |
|---|---|---|---|---|
| 0 | Brat | AMS | FRA | 300 |
| 1 | Brat | CDG | FRA | 450 |
| 2 | Brat | FRA | ATH | 450 |
| 3 | Brat | FRA | HEL | 300 |
| 4 | Brat | FRA | SIN | 300 |
| 5 | Brat | FRA | WAW | 300 |
| 6 | Brat | JFK | FRA | 300 |
| 7 | Brat | MAD | FRA | 300 |
| 8 | Stroop | AMS | ATH | 300 |
| 9 | Stroop | AMS | FRA | 200 |
| 10 | Stroop | AMS | HEL | 270 |
| 11 | Stroop | AMS | SIN | 300 |
| 12 | Stroop | AMS | WAW | 170 |
| 13 | Stroop | CDG | AMS | 250 |
| 14 | Stroop | JFK | AMS | 300 |
| 15 | Stroop | KEF | AMS | 320 |
| 16 | Stroop | MAD | AMS | 220 |
| 17 | Tapas | KEF | MAD | 360 |
| 18 | Tapas | MAD | AMS | 240 |
| 19 | Tapas | MAD | ATH | 240 |
| 20 | Tapas | MAD | FRA | 360 |
| 21 | Tapas | MAD | HEL | 120 |
| 22 | Tapas | MAD | WAW | 360 |
Simulation Results¶
sim = pax.MultiSimulation(cfg)
out = sim.run()
╭─────────────────── Summary revenue statistics · Europa ───────────────────╮ │ Brat Stroop Tapas │ │ Run Samples Mean ~95% CI Mean ~95% CI Mean ~95% CI │ │ Europa 900 932.1K ±7,237 928.2K ±7,336 291.7K ±2,360 │ │ Post-burn-in only · Approx. 95% CI for mean │ ╰───────────────────────────────────────────────────────────────────────────╯
out.fig_carrier_revenues()
out.fig_carrier_load_factors()
out.fig_carrier_rasm()
out.fig_carrier_yields()
out.fig_fare_class_mix()
out.fig_leg_load_v_distance(carrier="Brat", beeswarm=1000)
out.fig_leg_load_v_distance(carrier="Stroop", beeswarm=1000)
out.fig_leg_load_v_distance(carrier="Tapas", beeswarm=1000)
The Tapas network is much smaller than the other carriers, both in terms of seats flown and also
distance of legs, as there are no long-haul legs for this carrier.
Market Shares¶
out.market_shares()
| carrier | Brat | Stroop | Tapas | |
|---|---|---|---|---|
| orig | dest | |||
| AMS | ATH | 30.679067 | 69.320933 | 0.000000 |
| FRA | 55.099152 | 44.900848 | 0.000000 | |
| HEL | 16.008772 | 83.991228 | 0.000000 | |
| SIN | 25.466540 | 74.533460 | 0.000000 | |
| WAW | 16.972072 | 83.027928 | 0.000000 | |
| CDG | AMS | 0.000000 | 100.000000 | 0.000000 |
| ATH | 68.816622 | 31.183378 | 0.000000 | |
| FRA | 92.156420 | 7.843580 | 0.000000 | |
| HEL | 56.462642 | 43.537358 | 0.000000 | |
| SIN | 64.801863 | 35.198137 | 0.000000 | |
| WAW | 79.223803 | 20.776197 | 0.000000 | |
| FRA | ATH | 100.000000 | 0.000000 | 0.000000 |
| HEL | 100.000000 | 0.000000 | 0.000000 | |
| SIN | 100.000000 | 0.000000 | 0.000000 | |
| WAW | 100.000000 | 0.000000 | 0.000000 | |
| JFK | AMS | 0.000000 | 100.000000 | 0.000000 |
| ATH | 58.323946 | 41.676054 | 0.000000 | |
| FRA | 76.168032 | 23.831968 | 0.000000 | |
| HEL | 51.752344 | 48.247656 | 0.000000 | |
| WAW | 52.644019 | 47.355981 | 0.000000 | |
| KEF | AMS | 0.000000 | 81.250695 | 18.749305 |
| ATH | 0.000000 | 42.266403 | 57.733597 | |
| FRA | 0.000000 | 45.738023 | 54.261977 | |
| HEL | 0.000000 | 68.014879 | 31.985121 | |
| MAD | 0.000000 | 0.000000 | 100.000000 | |
| SIN | 0.000000 | 100.000000 | 0.000000 | |
| WAW | 0.000000 | 31.917893 | 68.082107 | |
| MAD | AMS | 0.000000 | 33.662542 | 66.337458 |
| ATH | 12.440618 | 7.672209 | 79.887173 | |
| FRA | 37.618604 | 2.587385 | 59.794011 | |
| HEL | 16.600961 | 22.606935 | 60.792103 | |
| SIN | 55.062873 | 44.937127 | 0.000000 | |
| WAW | 9.480951 | 7.467462 | 83.051586 |
out.market_shares(agg="orig")
| carrier | Brat | Stroop | Tapas |
|---|---|---|---|
| orig | |||
| AMS | 34.858498 | 65.141502 | 0.000000 |
| CDG | 65.454048 | 34.545952 | 0.000000 |
| FRA | 100.000000 | 0.000000 | 0.000000 |
| JFK | 49.339247 | 50.660753 | 0.000000 |
| KEF | 0.000000 | 46.496964 | 53.503036 |
| MAD | 20.158162 | 14.775521 | 65.066317 |
out.market_shares(agg="dest")
| carrier | Brat | Stroop | Tapas |
|---|---|---|---|
| dest | |||
| AMS | 0.000000 | 63.073160 | 36.926840 |
| ATH | 44.210479 | 32.516361 | 23.273159 |
| FRA | 48.933506 | 19.446180 | 31.620314 |
| HEL | 43.679342 | 37.261557 | 19.059101 |
| MAD | 0.000000 | 0.000000 | 100.000000 |
| SIN | 50.857332 | 49.142668 | 0.000000 |
| WAW | 35.785805 | 20.952654 | 43.261541 |