Model a city’s energy system: an energy hub with grid limits and screening fairness outputs
Write Prairie City’s energy hub as a linear program with a ceiling on each substation, read its duals, let it choose under a CO2 cap, and keep the household estimates apart
The lesson in one table: Prairie City's districts, run as one linear program (2025 dollars, illustrative)
| Plan | Yearly cost against 2025 | CO₂ of the districts (a year) | Substation upgrades | Burden, homes below 80% of area median income (AMI; screening) |
|---|---|---|---|---|
| 2025 as it is | $0.0M | 1,921,977 t | 0 MW | 6.1% |
| Electrify heat and cars | −$65.8M | 1,527,234 t | 91 MW | 5.4% |
| Electrify, managed charging, 40 MW of batteries | −$60.5M | 1,527,266 t | 48 MW | 5.5% |
| The LP's choice, CO₂ ≤ 80% of 2025 | −$86.2M | 1,537,582 t | 5 MW | 5.1% |
Precomputed with HiGHS on 6 November 2026. Cost includes the cars' fuel and charging, not their prices; equipment and upgrades as yearly payments.
With JavaScript on, you run each plan with a real solver in your browser, read the substations' duals, let the LP choose under a CO₂ cap, and change who adopts first in the household table. Prairie City is fictional; its numbers are illustrative.
- Illustrative: Prairie City, its three districts, one substation each and its household groups (prairie-city.json); retrofit, district-heat and upgrade costs are set for teaching, fixed here at their defaults (the inputs table lists them; the City Planner changes them).
- Based on public data: Kansas's 2025 prices (EIA), Wichita's weather normals (NOAA), SPP's 2025 hourly shapes (EIA-930), eGRID's 2023 SPP North CO₂ rate (EPA), DOE LEAD's income bands; heat pumps and rooftop solar from EIA, batteries from NREL, 2025 dollars.
- Time: eight representative days of 2025, weighted to 365 days; a cold design day and the summer peak day kept. Weather: NOAA's normals on SPP's 2025 days (synthetic).
- Solver: HiGHS (open source), loaded on your first run (about 3.5 MB, once).
Build it in Python · notebook lab, about 60 minutes, then 120 minutes of practice
The engine's hub with scipy.optimize.linprog and HiGHS.
- The LP: balances, substation rows and their duals, managed charging, batteries, the heat network.
- Choosing: the building shares as decisions under a CO₂ cap.
- After the LP: the household layer.
What it checks: assert cells match the browser's busiest hours, upgrades, costs, CO₂, the duals and the LP's chosen shares.
- Download the notebook and the two data files; put them in one folder.
- Upload all three to Google Colab (File → Upload notebook, then the folder icon), or open the notebook locally with Python 3, NumPy and SciPy 1.9 or later.
- Run all cells, then try the practice tasks at the end.
Notebook (.ipynb) Prairie City (JSON) Engine settings (JSON)
What you just did, as diagrams
- The hub
- Each district-hour, electricity bought through the substation, rooftop sun and batteries meet the base load, heating and car charging.
- Building shares
- Heat is split into shares by envelope and heating system, so heating power is linear in the decisions.
- The substation's ceiling
- Each hour's purchase stays under the limit plus any upgrade; its dual is the value of one more MW of room in that hour.
- The cap's dual
- When the LP chooses under a CO₂ cap, the cap's dual is the cost of the last tonne cut, under this model's assumptions.
- Kept apart
- Bills and energy burden by household group come from an accounting layer after the LP, with a stated rule for who adopts first.
Go deeper · ModelerThe hub in symbols
Every hour lasts \(\Delta t = 1\) h, so a MW held for an hour is a MWh. Days \(k\) (8), hours \(h\) (24), districts \(d\) (3).
| Symbol | What it is | Unit | Decided or given |
|---|---|---|---|
| \(imp_{dkh}\) | bought through district \(d\)’s substation | MW | decided, \(\ge 0\) |
| \(pv_{dkh}\), \(ev_{dkh}\) | rooftop solar used; car charging (managed) | MW | decided |
| \(c_{dkh}\), \(o_{dkh}\), \(q_{dkh}\) | battery in, out, stored | MW, MW, MWh | decided |
| \(y_{d,e,s}\) | share of district \(d\)’s heat in envelope \(e\) (0 as built, 1 retrofitted) with system \(s\) | share | given in a run; decided in step 6 |
| \(up_d\) | upgrade of \(d\)’s substation | MW | decided |
| \(w_k\) | days day \(k\) stands for (they add to 365) | days | given |
| \(p_d\), \(a\) | \(d\)’s 2025 electricity price; the upgrade’s yearly cost | $/MWh; $ per MW a year | given |
| \(B_{dkh}\), \(Q_{dkh}\) | base load; heat needed | MW; MW of heat | given (prairie-city.json) |
| \(\sigma\), \(COP_{kh}\) | heat a retrofit saves; heat pump’s heat per unit of power | share; MWh/MWh | given |
| \(L_d\), \(S_{dkh}\), \(E_{dk}\) | substation limit; rooftop output; the day’s charging energy | MW; MW; MWh | given |
| \(pl_{kh}\) | the heat network’s plant power (core only): its heat pump’s heat \(\div\) 3 | MW | decided |
| \(hp_{kh}\), \(boil_{kh}\), \(si_{kh}\), \(so_{kh}\) | the network’s heat pump, gas boiler (assumed to exist: fuel only), store in and out | MW of heat | decided |
\[\min\; \sum_k w_k \sum_h \sum_d p_d\, imp_{dkh} \;+\; a \sum_d up_d \;+\; \text{fuel}(y) \;[+\; \text{equipment}(y, \text{sizes})]\]
Each district-hour (the balance; its dual is the marginal cost of one more MWh there):
\[imp + pv + o - c - ev - \sum_{e,s} Q_{dkh}\,(1 - \sigma e)\; y_{d,e,s}\;\kappa_s - [d = \text{core}]\, pl_{kh} = B_{dkh}, \qquad \kappa_{res} = 1,\; \kappa_{hp} = \frac{1}{COP_{kh}},\; \kappa_{fuel} = \kappa_{dh} = 0\]
The heat network in the core (its own heat balance, with 10% lost in the pipes and a store that loses 0.5% an hour):
\[hp_{kh} + boil_{kh} + so_{kh} - si_{kh} = \frac{1}{0.9}\sum_{e} Q_{\text{core},kh}\,(1 - \sigma e)\; y_{\text{core},e,dh}, \qquad pl_{kh} = \frac{hp_{kh}}{3}\]
The substation (its dual: what one more MW of room is worth that hour), the sun, the battery and the day’s charging:
\[imp_{dkh} - up_d \le L_d, \qquad pv_{dkh} \le S_{dkh}, \qquad q_{dkh} = q_{dk,h-1} + 0.85\, c_{dkh} - o_{dkh},\; q_{dk,-1} = q_{dk,23}, \qquad \sum_h ev_{dkh} = E_{dk}\]
When the LP chooses (step 6): \(\sum_{e,s} y_{d,e,s} = 1\), furnaces and resistance no more than in 2025, retrofits and district heat up to their maxima, and \(\text{CO}_2(imp, y) \le cap\). Shares sit inside the LP in percent (0 to 100), which keeps HiGHS’s numbers close in size.
Go deeper · ModelerWhen two answers cost the same: the tie rule
With one price all day, many schedules tie. Small stated costs, left out of every reported number, pick one:
| Tie cost | Size |
|---|---|
| Each district's busiest hour | $20 a year per MW |
| The three districts' busiest hour together | $10 per MW |
| Out of a battery | $1 per MWh |
| Out of the heat store | $0.50 per MWh |
| Managed charging | $0.10 per MWh × the hour's share of SPP's peak demand |
- The LP minimises the cost plus these small costs: on a few plans it pays under $30 a year, in the engine's test, to keep a busiest hour that is under its limit lower.
- The engine's test finds the lowest and highest value of each busiest hour, each upgrade and the energy bought over every plan within $1 of the best cost: they agree.
- The tie costs also enter the balance duals, by a few dollars at most on the busiest hours: read them as "about".
- The substation duals summed over the year are checked as cost slopes: the cost moves by the same amount when the limit moves 0.1 MW up or down.
Go deeper · ResearcherWhere this model stops
- One ceiling per substation stands for a whole distribution network; real planning checks each feeder's power flow and voltage (A17).
- Storage resets every representative day: no carrying heat or charge between days, and no full-year check here.
- Many plans cost nearly the same; methods that map near-optimal plans show the choice is wider than one optimum.
Check yourself
Electrifying heat only adds yearly kWh; the grid sees a small rise.
- Myth
- Fact
Answer
Myth. The limit binds in the coldest night's hours, when heat pumps' COP is lowest and every home heats at once; the yearly total hides that hour.
Where the LP buys an upgrade, the substation's duals summed over the year equal the upgrade's yearly cost per MW.
- Myth
- Fact
Answer
Fact. One more MW of limit saves one MW of upgrade; the test checks it as a cost slope both ways.
The LP tells you how a plan changes each household group's energy burden.
- Myth
- Fact
Answer
Myth. The LP gives the city's cost, CO₂ and peaks; who pays comes from an accounting layer with its own stated assumptions.
Numbers, sources and what’s next
Inputs the model uses
| Input | Value | Source |
|---|---|---|
| Substation limits: core · suburbs · fringe | 250 · 230 · 60 MW | prairie-city.json: each district's 2025 busiest hour × 1.15, rounded up to the next 10 MW (17%, 20% and 25% above it; teaching) |
| Electricity, Kansas 2025: homes · businesses | 14.44¢ · 11.19¢ per kWh | Electric Power Annual, Table 2.10: Average Price of Electricity to Ultimate Customers by End-Use Sector, by State, 2025 and 2024 (EIA) |
| Gas, Kansas 2025: homes · businesses | $14.81 · $11.90 per thousand cubic feet | Natural Gas Prices, Kansas (annual) (EIA) |
| Grid CO₂ | 862 lb per MWh, plus 5% losses | EPA eGRID2023, SPP North |
| Heat pump COP, an illustrative curve | 2.4 at −15 °C; 3.2 at 0 °C; 1 below −23 °C | anchored to DOE's cold-climate challenge target, 2.4 at −15 °C (U.S. Department of Energy, 24 Sep 2021); the rest is the course's curve (lesson F3), with resistance backup assumed below −23 °C |
| Heat pump, per home | $7,355, 15 years | EIA AEO2026, residential (2022 dollars, converted) |
| Rooftop solar | $4,738 per kW, 25 years | EIA AEO2026, residential (2022 dollars, converted) |
| Battery, 4 hours | $1,371 per kW, 15 years | NREL 2025 update (2024 dollars, converted) |
| Retrofit: cost a home · heat saved · life | $9,000 · 18% · 30 years | set for teaching (illustrative); fixed here at the defaults: change them in the City Planner or the notebook |
| Heat network: per home-equivalent · plant heat pump · heat store | $12,000 (40 years) · $1,200 per kW of heat, COP 3 (25 years) · $3,000 per MWh (30 years) | set for teaching (illustrative); 10% of the heat lost in the pipes; the gas boiler (90%) is assumed to exist already: its fuel is counted, not its construction |
| Substation upgrade | $800 per kW, 40 years | set for teaching (illustrative); fixed here at the defaults: change them in the City Planner or the notebook |
| Managed charging | any hours of the same day, at most a sixth of the day's charge in one hour | set for teaching: cars plugged in at home or work whenever needed (an upper bound on what managed charging can do) |
| Yearly payments | 4% real, over each item's life | lesson 13's rate (NLR ATB 2025) |
Keep going
Built on and used in
Sources and further reading
- Geidl and others (2007). Energy hubs for the future. IEEE Power and Energy Magazine 5(1), 24–30.
- EIA. Electric Power Annual, Table 2.10: average price of electricity by state and sector, 2025.
- EPA. eGRID summary data, eGRID2023 (revision 2).
- DOE. Low-Income Energy Affordability Data (LEAD) Tool.
- EIA. Assumptions to the Annual Energy Outlook 2026: Residential Demand Module, Tables 2–3.
- Cole, Ramasamy and Turan (2025). Cost Projections for Utility-Scale Battery Storage: 2025 Update. NREL/TP-6A40-93281.
- Tonn and others (2014). Weatherization Works: Summary of Findings from the Retrospective Evaluation of the U.S. Department of Energy's Weatherization Assistance Program. ORNL/TM-2014/338, Oak Ridge National Laboratory (pp. xiv and 12).
- HiGHS. Documentation of the open-source solver in your browser and in the notebook (through SciPy's
linprog).
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.