Solve unit commitment in Python (MIQP)
Unit commitment is the classic power-systems planning problem: given several generating units, decide which to switch on and how much each should produce to meet demand at minimum cost. It mixes binary on/off decisions with a quadratic cost curve, which makes it a mixed-integer quadratic program (MIQP). With quicopt it is a short Pyomo model.
Why it's hard
An off unit costs nothing and produces nothing; an on unit pays a fixed start cost plus a fuel cost that grows with the square of its output. The on/off choices are discrete and the cost curves, so neither an LP nor a plain nonlinear solver fits. You need a solver that takes the integrality and the curvature together, which is what MIQP means. MIQP is the quadratic case of a mixed-integer nonlinear program: quadratic objective, linear constraints, some variables binary. Keeping the constraints linear is what lets the answer be proved rather than approached.
Model it
Binary on[i] switches unit i; continuous p[i] is its output, capped at zero
unless the unit is on. Meet demand, minimize fixed + fuel cost. Modeled in
Pyomo (pip install "quicopt[pyomo]"):
import pyomo.environ as pyo
from quicopt import Client
caps, fixed, a = [50.0, 60.0, 40.0], [100.0, 120.0, 80.0], [0.010, 0.008, 0.012]
demand = 100.0
m = pyo.ConcreteModel()
m.on = pyo.Var(range(3), domain=pyo.Binary)
m.p = pyo.Var(range(3), bounds=(0, None))
m.cap = pyo.Constraint(range(3), rule=lambda m, i: m.p[i] <= caps[i] * m.on[i])
m.dem = pyo.Constraint(expr=sum(m.p[i] for i in range(3)) >= demand)
m.obj = pyo.Objective(expr=sum(fixed[i]*m.on[i] + a[i]*m.p[i]**2 for i in range(3)), sense=pyo.minimize)
print(Client().solve(m).display)
├── shots │ ├── 1 · primal-dual · global 248 0.14s ◀ best │ ├── 2 · primal 248.0 2.0s │ └── 3 · primal 248.0 2.0s ├── status: optimal ├── feasible: true ├── objective: 248.0 ├── x: x1=0, x2=1, x3=1, x4=0, x5=60, x6=40 (6 variables) └── solve_time: 2.0008 s
What you get
The least-cost plan switches on units 2 and 3 (x2=1, x3=1) at outputs 60 and
40 to cover demand 100, for a total cost of 248. Unit 1 stays off — its
fixed cost plus fuel would beat neither option. quicopt returns this from binary
on/off decisions and a nonlinear cost in one solve.
status: optimal means quicopt proved this is the least-cost commitment — no
mix of on/off decisions and outputs does better. The quadratic fuel cost is convex
on its own; what makes the problem hard is the binary on/off switching, which
is why it needs a mixed-integer solver rather than a plain nonlinear one.
The shots block shows how the proof arrived. quicopt runs several solvers and
reports what each answer is worth. All three land on 248, but only the first
came back primal-dual · global, carrying the dual bound that settles the
question. The other two return a primal point: the same plan, with nothing to
say about whether anything beats it. Three solvers agreeing is reassuring, and
the certificate is what turns it into a proof. solve_time is the wall clock for
the whole set, so it keeps running long after the winning shot finished at
0.14 s.
The same model extends to more units, multiple time periods, and ramp limits by adding constraints.
Next
- The problem class behind this: Mixed-integer quadratic (MIQP)
- A runnable model for every supported class: Examples
- Set up the client and solve your first model: Getting started
Reference: A. J. Wood, B. F. Wollenberg, G. B. Sheblé, Power Generation, Operation, and Control, Wiley (3rd ed., 2013).
A bigger fleet — or a different problem?
Tell us what you're optimizing. We'll help you model it and point you at the right approach.
Frequently asked questions
Why isn't this just a linear program?
Two things break linearity: the on/off decisions are binary, and the fuel cost grows with the square of the output. Together that is a mixed-integer quadratic program (MIQP) — the quadratic case of a mixed-integer nonlinear program, and quicopt handles both parts in one model.
Can I use integer (not just binary) unit counts?
On the free tier, nonlinear models accept binary integers only; general integers return a 422. Model each unit as its own binary on/off, which is the standard formulation anyway.
Is quicopt free to use?
Yes — pip install quicopt and your first call sets up a free key, no license.