How do you read a formulation, and check that a model is right?
Read a dispatch model line by line, hunt five planted bugs, and check a solved model’s balance and duals
- Analyst to Modeler · L2–L3
- 30 min guided · about 45 min practice
- After B1
- Completed
Prairie Town's dispatch, 17:00 to 20:00 on 3 September 2025, as a published model would write it
- min Σt∈T Σg∈G cg pg,t Δt + Σt∈T V ut Δt
- Σg∈G pg,t + ut = dt ∀ t ∈ T (balance)
- pg,t ≤ P̄g ∀ g ∈ G, t ∈ T (sizes)
- pg,t ≤ ag,t ∀ g ∈ {wind, solar}, t ∈ T (weather)
- −R Δt ≤ pcc,t − pcc,t−1 ≤ R Δt ∀ t ∈ T (ramp; the start pcc,16 = 45 MW is given)
- pnuc,t = P̄nuc ∀ t ∈ T (nuclear runs flat); p, u ≥ 0
Symbol key and input values
| Symbol | Meaning | Unit | Value |
|---|---|---|---|
| nuc, wind, solar, cc, pk | the plants in G: nuclear, wind farm, solar farm, combined-cycle gas plant, peaker | ||
| dt | demand in hour t | MW | 17:00 349, 18:00 344, 19:00 331, 20:00 319 |
| awind,t | wind the weather allows | MW | 17:00 54, 18:00 59, 19:00 72, 20:00 89 |
| asolar,t | sun the weather allows | MW | 17:00 130, 18:00 70, 19:00 14, 20:00 0 |
| cg | running cost of plant g | $/MWh | nuc 10, wind 0, solar 0, cc 40, pk 100 |
| P̄g | size of plant g | MW | nuc 100, wind 150, solar 200, cc 150, pk 100 |
| R | how far the cc plant can move in an hour | MW per hour | 40 |
| pcc,16 | cc output at 16:00, the start: given, not chosen | MW | 45 |
| V | charge for each MWh of unserved demand | $/MWh | 10,000 |
| Δt | length of one step | h | 1 |
Its solution costs $22,600: the combined-cycle plant makes 75, 115, 145 and 130 MW, the peaker nothing, and the balance holds in all four hours.
With JavaScript on, you read it line by line, hunt five planted bugs and check the solution with a real solver.
- Demand, wind and solar: the real hourly shapes of 3 September 2025 on the Southwest Power Pool grid (EIA-930), scaled as in lesson 0.
- Start: the combined-cycle plant's 45 MW at 16:00, from lesson 0's cheapest-first plan.
- Plant sizes and costs are Prairie Town's; the $10,000/MWh charge for unserved demand is illustrative.
- Solver: HiGHS 1.15, in your browser.
What you just did, as diagrams
- Reading sequence
- Sets, parameters, variables, objective, constraints: what is counted, given, chosen, scored and ruled out.
- Indices and sums
- p with indices g and t is a table; Σ over g adds a column, Σ over t a row.
- Units
- $/MWh × MW × h = $: every term of the objective in dollars, with Δt written out.
- For all
- One line with ∀ t ∈ T stands for one row per hour.
- Verification
- Does the model do what its equations say? Units, signs, balance, duals, a hand check.
- Validation
- Is the model right enough for the question? Compare with real data and expert judgement.
Go deeper · AnalystWhy the balance dual is $0 at 17:00 and $80 at 18:00
A balance dual is the change in the cheapest total cost when one hour’s demand rises by 1 MWh.
- 19:00 and 20:00: $40. The combined-cycle plant simply makes 1 MW more.
- 18:00: $80. The plant already climbs its full 40 MW from 17:00, so 1 MW more at 18:00 needs 1 MW more at 17:00 too, where it pushes out free wind: $40 + $40 = $80, still below the $100 peaker.
- 17:00: $0. The plan already spills 10 MW of wind and sun to warm the plant up for the evening; one more MWh there just spills less.
- Linked hours. The ramp links the hours, so it links their prices. The Modeler lesson M1 reads such duals as marginal values for a whole day.
Go deeper · ModelerThe objective and the balance in Pyomo
Each line of the formulation becomes one line of code, with the sets doing the copying. Here are the objective and the balance in Pyomo, with c, d, V and dt holding the parameters; the sizes, weather, ramp and nuclear lines are left out:
from pyomo.environ import *
m = ConcreteModel()
m.T = Set(initialize=[17, 18, 19, 20])
m.G = Set(initialize=["nuc", "wind", "solar", "cc", "pk"])
m.p = Var(m.G, m.T, within=NonNegativeReals) # MW
m.u = Var(m.T, within=NonNegativeReals) # MW
m.cost = Objective(expr=sum(c[g] * m.p[g, t] * dt for g in m.G for t in m.T)
+ sum(V * m.u[t] * dt for t in m.T))
m.balance = Constraint(m.T, rule=lambda m, t:
sum(m.p[g, t] for g in m.G) + m.u[t] == d[t])Suspect 3 in code is a single balance row for hour 17, in place of the indexed one:
m.balance = Constraint(expr=sum(m.p[g, 17] for g in m.G) + m.u[17] == d[17])- Drop
* dt: the objective is then right only at one-hour steps. - The skill: reading code against the formulation, line by line, as in this lesson.
Go deeper · ResearcherValidation in published energy studies
Validating an optimization model is hard: it describes what would be cheapest, not what will happen.
| Approach | Evidence | Limit |
|---|---|---|
| Look back | For the UK’s electricity transition from 1990 to 2014, the cumulative cost of the real path deviated by 9 to 23% from the cost-optimal paths of a model, under the study’s alternative assumptions (Trutnevyte, 2016). | One country and one model; the real path was never meant to be cost-optimal. |
| Good practice | Guidance for energy-system optimization studies asks for documented, verifiable models and for uncertainty to be explored, not hidden (DeCarolis et al., 2017). | A standard to meet, not a test that passes or fails. |
The Researcher studio R5 turns both into a routine.
Check yourself
Myth or fact? “A model that solves to ‘optimal’ has passed verification.”
- Myth
- Fact
Answer
Myth. All five suspects solved to optimal. The solver checks the mathematics, not whether it says what you meant.
Myth or fact? “A balance dual can never be negative.”
- Myth
- Fact
Answer
Myth. Here all four are ≥ 0, but when output is forced above demand, more demand saves money: cut 17:00 demand to 105 MW and its dual becomes −$140. Real markets see negative prices for the same reason.
Myth or fact? “Adding up p in MW over the hours gives energy in MWh.”
- Myth
- Fact
Answer
Myth. Multiply by Δt first. With one-hour steps the number happens to be the same; at 15-minute steps the plain sum is four times too big.
A model's cost is far below your hand estimate, and the dearest plant runs first. Which check finds the bug fastest?
- The units of every parameter
- The balance in every hour
- Counting rows
- The signs of the duals
Answer
The units: a cost in $/kWh read as $/MWh makes a plant look a thousand times cheaper, as suspect 1 showed. The balance still holds.
How many rows does p(g,t) ≤ P̄(g) ∀ g ∈ G, t ∈ T stand for, with 5 plants and a whole day of 24 hours?
- 5
- 24
- 120
- 29
Answer
120: one row for each plant in each hour, 5 × 24.
Practice: nine exercises in three sets · about 39 min
With JavaScript on, each exercise runs the solver, gives hints one at a time and checks your answer.
Read a line
Say what a line means, the unit of every symbol, and how many rows it stands for.
The ramp line, word by word (worked example, about 2 min)
−R Δt ≤ pcc,t − pcc,t−1 ≤ R Δt ∀ t ∈ T
- In words. From one hour to the next, the combined-cycle plant's output changes by at most R, up or down.
- Units. p is in MW; R = 40 MW per hour times Δt = 1 h is MW too: every side in MW.
- Rows. Two limits per hour, one on each side: 4 hours give 8 rows.
- First hour. At t = 17, p(cc, 16) is not a variable but the start, 45 MW: a parameter.
How many rows would this line stand for over a whole day of 24 hours?
Answer
Two rows per hour, 2 × 24 = 48.
A storage line: the units (partly worked, about 3 min)
et = et−1 + η qt Δt − wt Δt ∀ t ∈ T
The energy in a battery at the end of hour t is what it held an hour before, plus what charging adds after losses, minus what it discharges.
- et, energy stored: MWh (given)
- Δt, the step: h (given)
Give the unit of each: qt, charging power, η, the share kept, wt, discharging power. Rows for the 4 hours of T?
Answer
q(t): MW; η: no unit; w(t): MW. MWh = MWh + (no unit × MW × h) − (MW × h), and 4 rows, one per hour.
A reserve line, from scratch (from scratch, about 5 min)
Σg∈{cc, pk} (P̄g − pg,t) ≥ rt ∀ t ∈ T
What does this line ask for?
- Every hour, the two gas plants keep at least r(t) of unused capacity in hand.
- Every hour, the two gas plants make at least r(t).
- Over the whole day, the gas plants' unused capacity adds up to at least r.
- Every hour, each gas plant keeps r(t) unused.
The unit of r(t)? Its rows over a day of 24 hours? Rows of the 24-hour model with this line added?
Answer
Every hour, the two gas plants keep at least r(t) of unused capacity in hand. Unused gas capacity in MW, one row per hour; 264 rows for the day plus 24 reserve rows: 288.
Hunt the bug
Three more colleagues, three more planted errors: run each model and let its numbers point to the line.
The penalty in the wrong unit (worked example, about 3 min)
- c = (10, 0, 0, 40, 100) $/MWh for (nuc, wind, solar, cc, pk); V = 10 $/kWh (costs)
- min Σt∈T Σg∈G cg pg,t Δt + Σt∈T V ut Δt (objective)
- Σg∈G pg,t + ut = dt ∀ t ∈ T (balance)
- pg,t ≤ P̄g ∀ g ∈ G, t ∈ T (sizes)
- pg,t ≤ ag,t ∀ g ∈ {wind, solar}, t ∈ T (weather)
- −R Δt ≤ pcc,t − pcc,t−1 ≤ R Δt ∀ t ∈ T (ramp)
- pnuc,t = P̄nuc ∀ t ∈ T (nuclear runs flat)
The solved plan:
The solved plan, MW, by plant and hour MW 17:00 18:00 19:00 20:00 nuclear 100 100 100 100 wind 54 59 72 89 solar 130 70 14 0 combined-cycle 5 0 0 0 peaker 0 0 0 0 unserved 60 115 145 130 demand 349 344 331 319 - Symptom. Optimal at $8,700. 60–145 MW unserved every hour, while the combined-cycle plant makes at most 5 MW.
- Caught by. The units of every parameter: V = 10 $/kWh is $10,000/MWh, yet the model used 10.
- Also caught by. The duals: every balance dual is $10/MWh, below the $40 of the cheapest gas plant. Unserved demand became the cheapest “plant”.
- Fix. Convert first: 10 $/kWh × 1,000 kWh per MWh = $10,000/MWh.
Which number in the solution gives the bug away first?
- 60–145 MW unserved while a gas plant idles
- Nuclear at 100 MW every hour
- Wind at 89 MW at 20:00
- No output from the peaker
Answer
60–145 MW unserved while a gas plant idles. Demand left unserved while a cheaper plant could run is the first thing to explain.
Nuclear no longer flat (partly worked, about 5 min)
Optimal at $22,500: $100 less than the right model's $22,600. Nuclear makes 90 MW at 17:00.
The solved plan, MW, by plant and hour MW 17:00 18:00 19:00 20:00 nuclear 90 100 100 100 wind 54 59 72 89 solar 130 70 14 0 combined-cycle 75 115 145 130 peaker 0 0 0 0 unserved 0 0 0 0 demand 349 344 331 319 - c = (10, 0, 0, 40, 100) $/MWh for (nuc, wind, solar, cc, pk) (costs)
- min Σt∈T Σg∈G cg pg,t Δt + Σt∈T V ut Δt (objective)
- Σg∈G pg,t + ut = dt ∀ t ∈ T (balance)
- pg,t ≤ P̄g ∀ g ∈ G, t ∈ T (sizes)
- pg,t ≤ ag,t ∀ g ∈ {wind, solar}, t ∈ T (weather)
- −R Δt ≤ pcc,t − pcc,t−1 ≤ R Δt ∀ t ∈ T (ramp)
- pnuc,t ≤ P̄nuc ∀ t ∈ T (nuclear runs flat)
Which line was changed, and which check catches it: Units of every parameter; The balance, hour by hour; Outputs against what each plant must do; Counting rows?
Answer
= became ≤ in line (5). The 17:00 balance dual moves from $0 to $10: one more MWh of demand now raises nuclear from 90 to 91 MW, at $10. Caught by: outputs against what each plant must do.
A plant counted twice (from scratch, about 7 min)
- c = (10, 0, 0, 40, 100) $/MWh for (nuc, wind, solar, cc, pk) (costs)
- min Σt∈T Σg∈G cg pg,t Δt + Σt∈T V ut Δt (objective)
- Σg∈G pg,t + pwind,t + ut = dt ∀ t ∈ T (balance)
- pg,t ≤ P̄g ∀ g ∈ G, t ∈ T (sizes)
- pg,t ≤ ag,t ∀ g ∈ {wind, solar}, t ∈ T (weather)
- −R Δt ≤ pcc,t − pcc,t−1 ≤ R Δt ∀ t ∈ T (ramp)
- pnuc,t = P̄nuc ∀ t ∈ T (nuclear runs flat)
The solved plan:
The solved plan, MW, by plant and hour MW 17:00 18:00 19:00 20:00 nuclear 100 100 100 100 wind 51.5 59 72 89 solar 130 70 14 0 combined-cycle 16 56 73 41 peaker 0 0 0 0 unserved 0 0 0 0 demand 349 344 331 319 Supply at 19:00, all five plants? Shortfall at 19:00? Which line causes it?
Answer
259 MW against 331: 72 MW short, exactly the wind output. The balance line counts wind twice, so the model believes it is balanced.
Check a solution
Before you trust a solution: the balance in an hour, the cost by hand, and the signs and sizes of the duals.
Three checks on the right solution (worked example, about 3 min)
The solved plan:
The solved plan, MW, by plant and hour MW 17:00 18:00 19:00 20:00 nuclear 100 100 100 100 wind 44 59 72 89 solar 130 70 14 0 combined-cycle 75 115 145 130 peaker 0 0 0 0 unserved 0 0 0 0 supply 349 344 331 319 demand 349 344 331 319 - Balance at 19:00. 100 + 72 + 14 + 145 + 0 = 331 MW, the demand ✓
- Cost by hand. nuclear 400 MWh × $10 = $4,000; combined-cycle 465 MWh × $40 = $18,600; total $22,600, the solver's objective ✓
- Duals. $0, $80, $40 and $40 per MWh: all ≥ 0 ✓
If the peaker made 10 MW at 19:00 in place of 10 MW of combined-cycle output, how much would the cost rise?
Answer
10 × ($100 − $40) = $600, which is why the solver leaves the peaker off.
Check the evening, 18:00 to 21:00 (partly worked, about 4 min)
The combined-cycle plant starts from 65 MW at 17:00, lesson 0's cheapest-first plan.
The solved plan, MW, by plant and hour MW 18:00 19:00 20:00 21:00 nuclear 100 100 100 100 wind 59 72 89 98 solar 70 14 0 0 combined-cycle 105 145 130 105 peaker 10 0 0 0 unserved 0 0 0 0 demand 344 331 319 303 - Balance at 18:00. 100 + 59 + 70 + 105 + 10 = 344 MW, the demand ✓
- Nuclear. 400 MWh × $10 = $4,000
- Combined-cycle. 485 MWh × $40 = $19,400
Peaker cost? Total, by hand?
Answer
Peaker $1,000. $24,400, the solver's objective too: the model's cost and a hand count agree.
Check the solar dip, 14:00 to 17:00 (from scratch, about 7 min)
The combined-cycle plant starts from 45 MW at 13:00, lesson 0's cheapest-first plan.
The solved plan, MW, by plant and hour MW 14:00 15:00 16:00 17:00 nuclear 100 100 100 100 wind 33 37 47 54 solar 112 147 161 130 combined-cycle 85 60 45 65 peaker 10 0 0 0 unserved 0 0 0 0 demand 340 344 353 349 Supply at 14:00? Cost by hand, all four hours? Your guess for the 14:00 balance dual?
Answer
Balanced (340 MW), $15,200 by hand, the solver's objective, and a $100 dual: the peaker is the marginal plant at 14:00. The solver's balance duals: 14:00 $100, 15:00 $40, 16:00 $40 and 17:00 $40 per MWh.
The models are this lesson's dispatch of 3 September 2025, solved by HiGHS in your browser (a precomputed answer if it cannot load). The 65 MW and 45 MW starts are lesson 0's cheapest-first plan at 17:00 and 13:00; the storage and reserve lines are illustrative.
Checks, sources and what’s next
Which check catches which bug
| Bug | What the solver says | Caught by |
|---|---|---|
| unit | optimal, with the dearest plant running first | units of every parameter |
| sign | optimal, with demand unmet in every hour | balance, hour by hour |
| index | optimal, balanced at 17:00 only | counting rows; balance |
| double | optimal, cost far above a hand calculation | a hand-calculated cost |
| lunch | optimal, wind above what the wind allowed | outputs against their limits |
Next
Sources and further reading
- EIA. Hourly Electric Grid Monitor (Form EIA-930): the Southwest Power Pool data behind Prairie Town's day, 2025.
- DeCarolis, J. et al. (2017). Formalizing best practice for energy system optimization modelling. Applied Energy 194.
- Trutnevyte, E. (2016). Does cost optimization approximate the real-world energy transition? Energy 106.
- Pyomo, JuMP and HiGHS: the modelling languages and the solver of the Modeler tier.
- First time with a linear program? Bridge B1, from algebra to a linear program.
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.