Grégoire Maillard

Why airlines stopped controlling seats one flight at a time, and started pricing a seat by the whole journey it is part of.

Network bid prices

At a hub, one seat on a long-haul flight can go to a local passenger or to a passenger connecting from a smaller city, who also needs a seat on the feeder flight. Leg-based revenue management decides flight by flight and fare class by fare class, so it can't tell the two apart. Origin-destination (O&D) control gives every flight a bid price, the value of its last seat across the whole network, and accepts a booking only if the fare covers the bid prices of every flight it uses. Below, a linear program works out those bid prices for a small hub at Montréal, then one stream of booking requests is run through three kinds of control. This is the hands-on companion to RM 101, chapter 06.

The network, fares, demand and capacities are illustrative assumptions, not any airline's data.

Loading the network and the LP solver…

How it works

The network

An invented airline with a hub at Montréal (YUL): two feeder flights in from Québec City (YQB) and Ottawa (YOW), two long-haul flights out to Paris (CDG) and London (LHR), one direction and one departure day, economy cabin only. That gives eight itineraries: four local (one flight) and four connecting (a feeder plus a long-haul flight), each sold in three fare classes, Y (flexible), M (standard) and Q (discount). Fares, forecasts and capacities come from bidprice.json and are illustrative assumptions.

The linear program and the bid prices

Let j be a product (an itinerary in a class) with fare fj and expected demand dj, and cl the seats on flight l. The deterministic LP (Williamson, 1992) is: maximise Σj fj xj subject to Σj uses l xj ≤ cl for every flight and 0 ≤ xj ≤ dj. The bid price πl of flight l is the dual value of its seat constraint: how much the optimal revenue would rise with one more seat on it. A request for product j is accepted if fj ≥ Σl in j πl and a seat is left on every flight it uses. The sum of the bid prices is the displacement cost of the booking, the revenue it is expected to push out of the network. The LP is solved in your browser by GLPK, compiled to WebAssembly by glpk.js 5.0.0, which returns the duals of the simplex solution directly.

The booking stream

Each product's demand for one departure is a Poisson draw whose mean is itself random (a gamma factor with a 25% coefficient of variation), so the variance is d + (0.25 d)2. Each request then gets a booking time between 0 (sales open) and 1 (departure) from a beta distribution that depends on the class: Beta(2, 5) for Q, Beta(3, 3) for M and Beta(5, 2) for Y, so discount requests come mostly early and flexible ones mostly late, with overlap. Requests are processed in time order. The generator is seeded, so the page opens on the same stream every time; "New booking stream" changes the seed.

The three controls

Both controls re-optimise at the same points: at the first request and then every N requests. The expected demand still to come for product j at time t is dj · (1 − Fk(t)), where Fk is the beta distribution of its class. Perfect hindsight is the same LP solved once on the demand that actually turned up, the most any control could have earned from that stream. Its solution is always in whole passengers because every itinerary uses at most one feeder and one long-haul flight, which makes the constraint matrix totally unimodular.

Sources

Limitations

This is a personal project and isn't affiliated with any airline.