Honglin Li
  • Research
  • Projects
  • Publications
  • Software
  • Teaching
  • Learn
  • Notes
  • CV
  • Journeys

From dispatch to locational prices: the linear program behind the market

Solve a day of dispatch, read each hourly price as a dual, then congest a line and watch one price split into three

Modeler lesson M1 of Energy Systems, Step by Step: write a day of power-plant dispatch as a linear program, solve it with an open-source solver in the browser, read every hourly price as the dual of a balance row, then put the plants on a three-bus network, fill a line and watch one price split into three locational marginal prices; a Python notebook rebuilds the same model.
  1. Learn
  2. M1
  • Modeler · L3
  • 40 min guided · 45 min lab · about 90 min practice
  • After lesson 11 and lesson 8 and B1 and B2 and B3
  • Completed
  1. 1Step by step
  2. 2Explain
  3. 3Check
  4. 4Sources

The idea in one picture: noon, with the Ridge–Town line full

Three buses at noon with the Ridge–Town line limited to 110 MW and full. Wind Ridge's price is 0 dollars, the Gas Hub's 40 dollars and Prairie Town's 80 dollars per MWh. 110 / 110 MWfull 101 / 200 MW 9 / 200 MW Wind Ridgewind + solar$0/MWh Gas Hubgas plants$40/MWh Prairie Townload + nuclear$80/MWh

With JavaScript on, you build this model step by step and solve it with a real solver in your browser: first a day with every plant in one place, then the same day on this network.

Demand, wind and solar follow lesson 0's real day, 3 September 2025 on the Southwest Power Pool grid (EIA-930), scaled to the town. Prairie Town, its plants, costs, network and line limits are illustrative. The solver is HiGHS (open source), loaded on your first solve (about 3.5 MB, once).

Build it in Python · notebook lab, about 45 minutes, then 90 minutes of practice

The same linear program in scipy.optimize.linprog(method="highs"); assert cells check every browser number.

  1. Download the notebook; the data are inside it.
  2. Upload it to Google Colab (File → Upload notebook), or open it locally with Python 3, NumPy and SciPy 1.9 or later.
  3. Run all cells, then try the practice questions at the end.

Notebook (.ipynb) Lesson data (JSON)

Need a refresher? Toolbox K8 (Git and reproducible notebooks).

What you just did, as diagrams

Linear program
Decisions (outputs, flows), a goal (lowest running cost) and rules (balance, sizes, ramp, network), all linear.
Dual of a balance row
The extra cost per MWh of a small change in that row's demand: the price at that bus and hour. At a corner of the model the step up and the step down differ.
Ramp prices
A slow plant links hours and produces $0 and $80 prices; the $80 matches no plant's offer.
DC power flow
Power splits over every path by the lines' reactance: two thirds on the direct line of an equal triangle.
Congestion
A full line splits one price into locational prices. Loads then pay more than generators are paid; the difference is the congestion rent, $13,200 at noon with the line at 110 MW.
Go deeper · ModelerPrices from PTDFs, and where the congestion money goes

Take Prairie Town as the reference bus. A power transfer distribution factor \(\text{PTDF}_{\ell n}\) is the share of 1 MW injected at bus \(n\), and taken out in town, that flows on line \(\ell\). In an equal triangle, an injection at the Ridge puts \(2/3\) on Ridge–Town and \(1/3\) on the path through the Gas Hub. In a lossless DC model, with \(\mu_\ell\) the value of 1 MW more limit on line \(\ell\) in that hour:

\[\text{LMP}_n = \text{LMP}_\text{Town} - \sum_\ell \text{PTDF}_{\ell n}\,\mu_\ell .\]

At noon with the line at 110 MW, \(\mu = \$120\): the Ridge gets \(80 - \tfrac{2}{3} \times 120 = \$0\) and the Gas Hub \(80 - \tfrac{1}{3} \times 120 = \$40\).

Congestion rent is what loads pay minus what generators are paid, each at its own bus’s price:

Noon, line at 110 MW $ in the hour
Loads pay: 311 MWh × $80 $24,880
Nuclear plant, in town: 100 MWh × $80 $8,000
Gas Hub: 92 MWh × $40 $3,680
Wind Ridge: 119 MWh × $0 $0
Congestion rent: loads minus generators $13,200 for the hour

In this DC model the rent also equals each full line’s \(\mu\) times its flow, \(120 \times 110\) MW. It is not the price gap between the line’s two ends times its flow (\(80 \times 110 = \$8{,}800\)).

Many organized markets use congestion revenue to fund financial transmission rights, under their own settlement and funding rules. Their prices also add a third part for losses, which a DC model leaves out (FERC Energy Primer, p. 64).

Go deeper · ModelerWhen a price is not unique

A dual is a slope. At a corner of the model the slope differs on each side, so check both sides. At 14:00 with the line at 110 MW, the gas combined-cycle plant runs at exactly its size, 150 MW:

In town at 14:00 Per MWh
Step up: one more MWh needs the peaker costs $200
Step down: one MWh less saves only $80
The solver’s dual $80, one end of that range

If the two sides differ, say which side your question is about. A mixed-integer solve (M2) does not provide these LP shadow prices; after fixing its on/off decisions, a dispatch LP gives prices conditional on them.

Go deeperWhat this model leaves out
  • Losses, voltage magnitudes and reactive power: a DC model uses only voltage-angle differences, holds voltage magnitudes fixed and is lossless. A17 estimates line heating and feeder voltage with short formulas to show when that matters, and says when an AC power flow is needed to check.
  • Outages: real dispatch keeps flows safe if any one line or plant fails (the N-1 rule).
  • Start-ups and minimum run times: those need on/off decisions, a mixed-integer program (M2).
  • Offers and price-responsive demand: here costs stand in for offers, and demand is fixed.
  • Size: real markets price thousands of locations; the algebra is the same.

Modeler's note: M3 adds investment decisions to this model, and the Modeler checkpoint CK-M asks you to formulate, solve and verify a model you have not seen. The Researcher studio R5 makes verification and validation a routine.

Check yourself

Myth or fact? “A bus price always equals the offer of some plant.”

  • Myth
  • Fact
Answer

Myth. At noon the town's $80 matches no offer: it is 2 × $40 − 1 × $0, set by the full line's physics.

Myth or fact? “When the line out of a wind area is full, the price at the wind farm can fall to zero.”

  • Myth
  • Fact
Answer

Fact. Wind Ridge spills power it cannot send, so one more MWh there is free: its price is $0.

Myth or fact? “The dual of a balance row is the price a real market must charge.”

  • Myth
  • Fact
Answer

Myth. It is a marginal value under this model's assumptions: fixed plants, costs as offers, no losses, this network and day. Markets clear real offers under many more rules.

At noon the full Ridge–Town line (110 MW) has μ = $120. If its limit rose to 111 MW for the noon interval only, the day's running cost would…

  • fall by $120
  • fall by $80
  • fall by $40
  • not change
Answer

Fall by $120. Swapping 1 MW of Gas Hub output for Ridge output adds only 2/3 − 1/3 = 1/3 MW to this line, so 1 MW more room lets 3 MW of free Ridge power replace 3 MW of $40 gas.

Which change brings the three noon prices back together?

  • Add 50 MW of solar on Wind Ridge
  • Raise the Ridge–Town limit to 140 MW
  • Raise the gas price
  • Lower the town's demand by 1 MW
Answer

Raise the limit to 140 MW: at noon the line then needs only 134 MW, so it has room and one price returns. More Ridge solar would only add to the spill.

Numbers, sources and what’s next

Numbers to remember

Number What it means Source
3 parts A locational marginal price is an energy charge, a congestion charge and a charge for losses. FERC Energy Primer (PDF), p. 64 · April 2020 edition
about 2/3 Share of U.S. electricity load served in regions whose grid operator runs an organized market with such prices. FERC, Electric Power Markets · page accessed 2 Nov 2026
2/3 and 1/3 How an equal triangle of lines splits power sent from one corner to another: direct line, and the path around. DC power flow; this lesson's network (illustrative)
$80 = 2 × $40 − 1 × $0 The town's noon price with the line full: two gas MWh in, one Ridge MWh out. This lesson's model (illustrative)

Next

M2 · On or off? Unit commitment as a mixed-integer program

Related lab: a SciPy/HiGHS notebook that rebuilds the model and asserts the browser's prices, about 45 minutes.

In the real world

Grid operators that run markets compute a price at each location from the same kind of model, with losses, security rules and many more locations.

  • Builds on: lesson 0 (the day and the plants), 8 (one price), 10 and 11 (writing and solving a linear program).
  • Bridges: B1, B2 and B3.
  • Open tools for such models at scale: PyPSA, Pyomo and JuMP.

Sources and further reading

  • FERC staff (2020). Energy Primer: A Handbook of Energy Market Basics (PDF), chapter 3: locational marginal pricing.
  • FERC. Electric Power Markets: the regions with organized markets.
  • EIA. Hourly Electric Grid Monitor (Form EIA-930): the Southwest Power Pool data behind lesson 0's day.
  • HiGHS. Documentation of the open-source solver used in the page and, through SciPy, in the notebook.

Spotted an error, or stuck? Write to hlli@ksu.edu. Corrections are made in place and dated here.

Text licensed CC BY 4.0, code MIT: teachers and students may reuse and adapt it, with credit to Honglin Li.

© 2026 Honglin Li · Kansas State University · hlli@ksu.edu

 

Last updated: November 2026 · Built with Quarto