[{"data":1,"prerenderedAt":556},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Funit-commitment":3},{"id":4,"title":5,"body":6,"description":537,"extension":538,"faq":539,"meta":549,"navigation":550,"noindex":551,"path":552,"seo":553,"stem":554,"__hash__":555},"content\u002Fdeveloper\u002Fguides\u002Funit-commitment.md","Solve unit commitment in Python (MINLP)",{"type":7,"value":8,"toc":531},"minimark",[9,13,37,42,45,49,73,435,440,444,467,481,485,511,522,527],[10,11,5],"h1",{"id":12},"solve-unit-commitment-in-python-minlp",[14,15,16,20,21,24,25,28,29,32,33,36],"p",{},[17,18,19],"strong",{},"Unit commitment"," is the classic power-systems planning problem: given several\ngenerating units, decide which to switch ",[17,22,23],{},"on"," and how much each should\n",[17,26,27],{},"produce"," to meet demand at minimum cost. It mixes binary on\u002Foff decisions with\na nonlinear cost curve — a ",[17,30,31],{},"mixed-integer nonlinear program (MINLP)",". With\n",[17,34,35],{},"quicopt"," it is a short Pyomo model.",[38,39,41],"h2",{"id":40},"why-its-hard","Why it's hard",[14,43,44],{},"An off unit costs nothing and produces nothing; an on unit pays a fixed start\ncost plus a fuel cost that grows with output (here, quadratically). The on\u002Foff\nchoices are discrete and the cost is nonlinear, so neither an LP nor a plain\nnonlinear solver fits — you need MINLP.",[38,46,48],{"id":47},"model-it","Model it",[14,50,51,52,56,57,60,61,64,65,68,69,72],{},"Binary ",[53,54,55],"code",{},"on[i]"," switches unit ",[53,58,59],{},"i","; continuous ",[53,62,63],{},"p[i]"," is its output, capped at zero\nunless the unit is on. Meet demand, minimize fixed + fuel cost. Modeled in\n",[17,66,67],{},"Pyomo"," (",[53,70,71],{},"pip install \"quicopt[pyomo]\"","):",[74,75,81],"pre",{"className":76,"code":77,"filename":78,"language":79,"meta":80,"style":80},"language-python shiki shiki-themes github-dark","import pyomo.environ as pyo\nfrom quicopt import Client\ncaps, fixed, a = [50.0, 60.0, 40.0], [100.0, 120.0, 80.0], [0.010, 0.008, 0.012]\ndemand = 100.0\nm = pyo.ConcreteModel()\nm.on = pyo.Var(range(3), domain=pyo.Binary)\nm.p = pyo.Var(range(3), bounds=(0, None))\nm.cap = pyo.Constraint(range(3), rule=lambda m, i: m.p[i] \u003C= caps[i] * m.on[i])\nm.dem = pyo.Constraint(expr=sum(m.p[i] for i in range(3)) >= demand)\nm.obj = pyo.Objective(expr=sum(fixed[i]*m.on[i] + a[i]*m.p[i]**2 for i in range(3)), sense=pyo.minimize)\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(m).display)\n","unit_commitment.py","python","",[53,82,83,102,116,177,188,199,231,267,307,353,419],{"__ignoreMap":80},[84,85,88,92,96,99],"span",{"class":86,"line":87},"line",1,[84,89,91],{"class":90},"snl16","import",[84,93,95],{"class":94},"s95oV"," pyomo.environ ",[84,97,98],{"class":90},"as",[84,100,101],{"class":94}," pyo\n",[84,103,105,108,111,113],{"class":86,"line":104},2,[84,106,107],{"class":90},"from",[84,109,110],{"class":94}," quicopt ",[84,112,91],{"class":90},[84,114,115],{"class":94}," Client\n",[84,117,119,122,125,128,132,135,138,140,143,146,149,151,154,156,159,161,164,166,169,171,174],{"class":86,"line":118},3,[84,120,121],{"class":94},"caps, fixed, a ",[84,123,124],{"class":90},"=",[84,126,127],{"class":94}," [",[84,129,131],{"class":130},"sDLfK","50.0",[84,133,134],{"class":94},", ",[84,136,137],{"class":130},"60.0",[84,139,134],{"class":94},[84,141,142],{"class":130},"40.0",[84,144,145],{"class":94},"], [",[84,147,148],{"class":130},"100.0",[84,150,134],{"class":94},[84,152,153],{"class":130},"120.0",[84,155,134],{"class":94},[84,157,158],{"class":130},"80.0",[84,160,145],{"class":94},[84,162,163],{"class":130},"0.010",[84,165,134],{"class":94},[84,167,168],{"class":130},"0.008",[84,170,134],{"class":94},[84,172,173],{"class":130},"0.012",[84,175,176],{"class":94},"]\n",[84,178,180,183,185],{"class":86,"line":179},4,[84,181,182],{"class":94},"demand ",[84,184,124],{"class":90},[84,186,187],{"class":130}," 100.0\n",[84,189,191,194,196],{"class":86,"line":190},5,[84,192,193],{"class":94},"m ",[84,195,124],{"class":90},[84,197,198],{"class":94}," pyo.ConcreteModel()\n",[84,200,202,205,207,210,213,216,219,222,226,228],{"class":86,"line":201},6,[84,203,204],{"class":94},"m.on ",[84,206,124],{"class":90},[84,208,209],{"class":94}," pyo.Var(",[84,211,212],{"class":130},"range",[84,214,215],{"class":94},"(",[84,217,218],{"class":130},"3",[84,220,221],{"class":94},"), ",[84,223,225],{"class":224},"s9osk","domain",[84,227,124],{"class":90},[84,229,230],{"class":94},"pyo.Binary)\n",[84,232,234,237,239,241,243,245,247,249,252,254,256,259,261,264],{"class":86,"line":233},7,[84,235,236],{"class":94},"m.p ",[84,238,124],{"class":90},[84,240,209],{"class":94},[84,242,212],{"class":130},[84,244,215],{"class":94},[84,246,218],{"class":130},[84,248,221],{"class":94},[84,250,251],{"class":224},"bounds",[84,253,124],{"class":90},[84,255,215],{"class":94},[84,257,258],{"class":130},"0",[84,260,134],{"class":94},[84,262,263],{"class":130},"None",[84,265,266],{"class":94},"))\n",[84,268,270,273,275,278,280,282,284,286,289,292,295,298,301,304],{"class":86,"line":269},8,[84,271,272],{"class":94},"m.cap ",[84,274,124],{"class":90},[84,276,277],{"class":94}," pyo.Constraint(",[84,279,212],{"class":130},[84,281,215],{"class":94},[84,283,218],{"class":130},[84,285,221],{"class":94},[84,287,288],{"class":224},"rule",[84,290,291],{"class":90},"=lambda",[84,293,294],{"class":94}," m, i: m.p[i] ",[84,296,297],{"class":90},"\u003C=",[84,299,300],{"class":94}," caps[i] ",[84,302,303],{"class":90},"*",[84,305,306],{"class":94}," m.on[i])\n",[84,308,310,313,315,317,320,322,325,328,331,334,337,340,342,344,347,350],{"class":86,"line":309},9,[84,311,312],{"class":94},"m.dem ",[84,314,124],{"class":90},[84,316,277],{"class":94},[84,318,319],{"class":224},"expr",[84,321,124],{"class":90},[84,323,324],{"class":130},"sum",[84,326,327],{"class":94},"(m.p[i] ",[84,329,330],{"class":90},"for",[84,332,333],{"class":94}," i ",[84,335,336],{"class":90},"in",[84,338,339],{"class":130}," range",[84,341,215],{"class":94},[84,343,218],{"class":130},[84,345,346],{"class":94},")) ",[84,348,349],{"class":90},">=",[84,351,352],{"class":94}," demand)\n",[84,354,356,359,361,364,366,368,370,373,375,378,381,384,386,389,392,395,398,400,402,404,406,408,411,414,416],{"class":86,"line":355},10,[84,357,358],{"class":94},"m.obj ",[84,360,124],{"class":90},[84,362,363],{"class":94}," pyo.Objective(",[84,365,319],{"class":224},[84,367,124],{"class":90},[84,369,324],{"class":130},[84,371,372],{"class":94},"(fixed[i]",[84,374,303],{"class":90},[84,376,377],{"class":94},"m.on[i] ",[84,379,380],{"class":90},"+",[84,382,383],{"class":94}," a[i]",[84,385,303],{"class":90},[84,387,388],{"class":94},"m.p[i]",[84,390,391],{"class":90},"**",[84,393,394],{"class":130},"2",[84,396,397],{"class":90}," for",[84,399,333],{"class":94},[84,401,336],{"class":90},[84,403,339],{"class":130},[84,405,215],{"class":94},[84,407,218],{"class":130},[84,409,410],{"class":94},")), ",[84,412,413],{"class":224},"sense",[84,415,124],{"class":90},[84,417,418],{"class":94},"pyo.minimize)\n",[84,420,422,425,428,432],{"class":86,"line":421},11,[84,423,424],{"class":130},"print",[84,426,427],{"class":94},"(Client(",[84,429,431],{"class":430},"sU2Wk","\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[84,433,434],{"class":94},").solve(m).display)\n",[436,437],"term-result",{":rows":438,"cmd":439},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  248.0\",\"├── x:          x1=0, x2=1, x3=1, x4=0, x5=60, x6=40  (6 variables)\",\"└── solve_time: 0.0136 s\"]","$ python unit_commitment.py",[38,441,443],{"id":442},"what-you-get","What you get",[14,445,446,447,450,451,454,455,458,459,462,463,466],{},"The least-cost plan switches on units 2 and 3 (",[53,448,449],{},"x2=1, x3=1",") at outputs ",[53,452,453],{},"60"," and\n",[53,456,457],{},"40"," to cover demand ",[53,460,461],{},"100",", for a total cost of ",[53,464,465],{},"248",". Unit 1 stays off — its\nfixed cost plus fuel would beat neither option. quicopt returns this from binary\non\u002Foff decisions and a nonlinear cost in one solve.",[14,468,469,472,473,476,477,480],{},[53,470,471],{},"status: optimal"," means quicopt ",[17,474,475],{},"proved"," this is the least-cost commitment — no\nmix of on\u002Foff decisions and outputs does better. The quadratic fuel cost is convex\non its own; what makes the problem hard is the ",[17,478,479],{},"binary"," on\u002Foff switching, which\nis why it needs a mixed-integer solver rather than a plain nonlinear one. The same\nmodel extends to more units, multiple time periods, and ramp limits by adding\nconstraints.",[38,482,484],{"id":483},"next","Next",[486,487,488,497,504],"ul",{},[489,490,491,492],"li",{},"The problem class behind this: ",[493,494,496],"a",{"href":495},"\u002Fproblems\u002Fminlp","Mixed-integer nonlinear (MINLP)",[489,498,499,500],{},"A runnable model for every supported class: ",[493,501,503],{"href":502},"\u002Fdeveloper\u002Fexamples","Examples",[489,505,506,507],{},"Set up the client and solve your first model: ",[493,508,510],{"href":509},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,512,513,516,517,521],{},[17,514,515],{},"Reference:"," A. J. Wood, B. F. Wollenberg, G. B. Sheblé, ",[518,519,520],"em",{},"Power Generation,\nOperation, and Control",", Wiley (3rd ed., 2013).",[523,524],"contact-cta",{"sub":525,"title":526},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger fleet — or a different problem?",[528,529,530],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":80,"searchDepth":104,"depth":104,"links":532},[533,534,535,536],{"id":40,"depth":104,"text":41},{"id":47,"depth":104,"text":48},{"id":442,"depth":104,"text":443},{"id":483,"depth":104,"text":484},"Decide which generating units to switch on and how much each should produce to meet demand at minimum cost in Python — unit commitment as a mixed-integer nonlinear program (MINLP), solved with quicopt.","md",[540,543,546],{"q":541,"a":542},"Why isn't this just a linear program?","Two things break linearity: the on\u002Foff decisions are binary, and the fuel cost rises nonlinearly with output. Together that is a mixed-integer nonlinear program (MINLP) — quicopt handles both in one model.",{"q":544,"a":545},"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\u002Foff, which is the standard formulation anyway.",{"q":547,"a":548},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},true,false,"\u002Fdeveloper\u002Fguides\u002Funit-commitment",{"title":5,"description":537},"developer\u002Fguides\u002Funit-commitment","dViHNW8ahH7R15gYBmBApkCPTfM8OgxPzLjE9oTJA-M",1784110685996]