[{"data":1,"prerenderedAt":572},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fexamples\u002Funit-commitment":3},{"id":4,"title":5,"body":6,"description":553,"extension":554,"faq":555,"meta":565,"navigation":566,"noindex":567,"path":568,"seo":569,"stem":570,"__hash__":571},"content\u002Fdeveloper\u002Fexamples\u002Funit-commitment.md","Solve unit commitment in Python (MIQP)",{"type":7,"value":8,"toc":547},"minimark",[9,13,37,42,50,54,78,433,438,442,465,478,500,503,507,533,543],[10,11,5],"h1",{"id":12},"solve-unit-commitment-in-python-miqp",[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 quadratic cost curve, which makes it a ",[17,30,31],{},"mixed-integer quadratic program\n(MIQP)",". With ",[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,45,49],{},"An off unit costs nothing and produces nothing; an on unit pays a fixed start\ncost plus a fuel cost that grows with the square of its output. The on\u002Foff\nchoices are discrete and the cost curves, so neither an LP nor a plain nonlinear\nsolver fits. You need a solver that takes the integrality and the curvature\ntogether, which is what MIQP means. MIQP is the quadratic case of a mixed-integer\nnonlinear program: quadratic objective, linear constraints, some variables\nbinary. Keeping the constraints linear is what lets the answer be ",[46,47,48],"em",{},"proved"," rather\nthan approached.",[38,51,53],{"id":52},"model-it","Model it",[14,55,56,57,61,62,65,66,69,70,73,74,77],{},"Binary ",[58,59,60],"code",{},"on[i]"," switches unit ",[58,63,64],{},"i","; continuous ",[58,67,68],{},"p[i]"," is its output, capped at zero\nunless the unit is on. Meet demand, minimize fixed + fuel cost. Modeled in\n",[17,71,72],{},"Pyomo"," (",[58,75,76],{},"pip install \"quicopt[pyomo]\"","):",[79,80,86],"pre",{"className":81,"code":82,"filename":83,"language":84,"meta":85,"style":85},"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().solve(m).display)\n","unit_commitment.py","python","",[58,87,88,107,121,182,193,204,236,272,312,358,424],{"__ignoreMap":85},[89,90,93,97,101,104],"span",{"class":91,"line":92},"line",1,[89,94,96],{"class":95},"snl16","import",[89,98,100],{"class":99},"s95oV"," pyomo.environ ",[89,102,103],{"class":95},"as",[89,105,106],{"class":99}," pyo\n",[89,108,110,113,116,118],{"class":91,"line":109},2,[89,111,112],{"class":95},"from",[89,114,115],{"class":99}," quicopt ",[89,117,96],{"class":95},[89,119,120],{"class":99}," Client\n",[89,122,124,127,130,133,137,140,143,145,148,151,154,156,159,161,164,166,169,171,174,176,179],{"class":91,"line":123},3,[89,125,126],{"class":99},"caps, fixed, a ",[89,128,129],{"class":95},"=",[89,131,132],{"class":99}," [",[89,134,136],{"class":135},"sDLfK","50.0",[89,138,139],{"class":99},", ",[89,141,142],{"class":135},"60.0",[89,144,139],{"class":99},[89,146,147],{"class":135},"40.0",[89,149,150],{"class":99},"], [",[89,152,153],{"class":135},"100.0",[89,155,139],{"class":99},[89,157,158],{"class":135},"120.0",[89,160,139],{"class":99},[89,162,163],{"class":135},"80.0",[89,165,150],{"class":99},[89,167,168],{"class":135},"0.010",[89,170,139],{"class":99},[89,172,173],{"class":135},"0.008",[89,175,139],{"class":99},[89,177,178],{"class":135},"0.012",[89,180,181],{"class":99},"]\n",[89,183,185,188,190],{"class":91,"line":184},4,[89,186,187],{"class":99},"demand ",[89,189,129],{"class":95},[89,191,192],{"class":135}," 100.0\n",[89,194,196,199,201],{"class":91,"line":195},5,[89,197,198],{"class":99},"m ",[89,200,129],{"class":95},[89,202,203],{"class":99}," pyo.ConcreteModel()\n",[89,205,207,210,212,215,218,221,224,227,231,233],{"class":91,"line":206},6,[89,208,209],{"class":99},"m.on ",[89,211,129],{"class":95},[89,213,214],{"class":99}," pyo.Var(",[89,216,217],{"class":135},"range",[89,219,220],{"class":99},"(",[89,222,223],{"class":135},"3",[89,225,226],{"class":99},"), ",[89,228,230],{"class":229},"s9osk","domain",[89,232,129],{"class":95},[89,234,235],{"class":99},"pyo.Binary)\n",[89,237,239,242,244,246,248,250,252,254,257,259,261,264,266,269],{"class":91,"line":238},7,[89,240,241],{"class":99},"m.p ",[89,243,129],{"class":95},[89,245,214],{"class":99},[89,247,217],{"class":135},[89,249,220],{"class":99},[89,251,223],{"class":135},[89,253,226],{"class":99},[89,255,256],{"class":229},"bounds",[89,258,129],{"class":95},[89,260,220],{"class":99},[89,262,263],{"class":135},"0",[89,265,139],{"class":99},[89,267,268],{"class":135},"None",[89,270,271],{"class":99},"))\n",[89,273,275,278,280,283,285,287,289,291,294,297,300,303,306,309],{"class":91,"line":274},8,[89,276,277],{"class":99},"m.cap ",[89,279,129],{"class":95},[89,281,282],{"class":99}," pyo.Constraint(",[89,284,217],{"class":135},[89,286,220],{"class":99},[89,288,223],{"class":135},[89,290,226],{"class":99},[89,292,293],{"class":229},"rule",[89,295,296],{"class":95},"=lambda",[89,298,299],{"class":99}," m, i: m.p[i] ",[89,301,302],{"class":95},"\u003C=",[89,304,305],{"class":99}," caps[i] ",[89,307,308],{"class":95},"*",[89,310,311],{"class":99}," m.on[i])\n",[89,313,315,318,320,322,325,327,330,333,336,339,342,345,347,349,352,355],{"class":91,"line":314},9,[89,316,317],{"class":99},"m.dem ",[89,319,129],{"class":95},[89,321,282],{"class":99},[89,323,324],{"class":229},"expr",[89,326,129],{"class":95},[89,328,329],{"class":135},"sum",[89,331,332],{"class":99},"(m.p[i] ",[89,334,335],{"class":95},"for",[89,337,338],{"class":99}," i ",[89,340,341],{"class":95},"in",[89,343,344],{"class":135}," range",[89,346,220],{"class":99},[89,348,223],{"class":135},[89,350,351],{"class":99},")) ",[89,353,354],{"class":95},">=",[89,356,357],{"class":99}," demand)\n",[89,359,361,364,366,369,371,373,375,378,380,383,386,389,391,394,397,400,403,405,407,409,411,413,416,419,421],{"class":91,"line":360},10,[89,362,363],{"class":99},"m.obj ",[89,365,129],{"class":95},[89,367,368],{"class":99}," pyo.Objective(",[89,370,324],{"class":229},[89,372,129],{"class":95},[89,374,329],{"class":135},[89,376,377],{"class":99},"(fixed[i]",[89,379,308],{"class":95},[89,381,382],{"class":99},"m.on[i] ",[89,384,385],{"class":95},"+",[89,387,388],{"class":99}," a[i]",[89,390,308],{"class":95},[89,392,393],{"class":99},"m.p[i]",[89,395,396],{"class":95},"**",[89,398,399],{"class":135},"2",[89,401,402],{"class":95}," for",[89,404,338],{"class":99},[89,406,341],{"class":95},[89,408,344],{"class":135},[89,410,220],{"class":99},[89,412,223],{"class":135},[89,414,415],{"class":99},")), ",[89,417,418],{"class":229},"sense",[89,420,129],{"class":95},[89,422,423],{"class":99},"pyo.minimize)\n",[89,425,427,430],{"class":91,"line":426},11,[89,428,429],{"class":135},"print",[89,431,432],{"class":99},"(Client().solve(m).display)\n",[434,435],"term-result",{":rows":436,"cmd":437},"[\"├── shots\",\"│   ├── 1 · primal-dual · global     248   0.18s  ◀ 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.0012 s\"]","$ python unit_commitment.py",[38,439,441],{"id":440},"what-you-get","What you get",[14,443,444,445,448,449,452,453,456,457,460,461,464],{},"The least-cost plan switches on units 2 and 3 (",[58,446,447],{},"x2=1, x3=1",") at outputs ",[58,450,451],{},"60"," and\n",[58,454,455],{},"40"," to cover demand ",[58,458,459],{},"100",", for a total cost of ",[58,462,463],{},"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,466,467,470,471,473,474,477],{},[58,468,469],{},"status: optimal"," means quicopt ",[17,472,48],{}," 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,475,476],{},"binary"," on\u002Foff switching, which\nis why it needs a mixed-integer solver rather than a plain nonlinear one.",[14,479,480,481,484,485,487,488,491,492,495,496,499],{},"The ",[58,482,483],{},"shots"," block shows how the proof arrived. quicopt runs several solvers and\nreports what each answer is worth. All three land on ",[58,486,463],{},", but only the first\ncame back ",[58,489,490],{},"primal-dual · global",", carrying the dual bound that settles the\nquestion. The other two return a ",[58,493,494],{},"primal"," point: the same plan, with nothing to\nsay about whether anything beats it. Three solvers agreeing is reassuring, and\nthe certificate is what turns it into a proof. ",[58,497,498],{},"solve_time"," is the wall clock for\nthe whole set, so it keeps running long after the winning shot finished.",[14,501,502],{},"The same model extends to more units, multiple time periods, and ramp limits by\nadding constraints.",[38,504,506],{"id":505},"next","Next",[508,509,510,519,526],"ul",{},[511,512,513,514],"li",{},"The problem class behind this: ",[515,516,518],"a",{"href":517},"\u002Fproblems\u002Fmiqp","Mixed-integer quadratic (MIQP)",[511,520,521,522],{},"A runnable model for every supported class: ",[515,523,525],{"href":524},"\u002Fdeveloper\u002Fexamples\u002Fby-class","Examples",[511,527,528,529],{},"Set up the client and solve your first model: ",[515,530,532],{"href":531},"\u002Fdeveloper\u002Fdocs\u002Fquickstart","Getting started",[14,534,535,538,539,542],{},[17,536,537],{},"Reference:"," A. J. Wood, B. F. Wollenberg, G. B. Sheblé, ",[46,540,541],{},"Power Generation,\nOperation, and Control",", Wiley (3rd ed., 2013).",[544,545,546],"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 .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":85,"searchDepth":109,"depth":109,"links":548},[549,550,551,552],{"id":40,"depth":109,"text":41},{"id":52,"depth":109,"text":53},{"id":440,"depth":109,"text":441},{"id":505,"depth":109,"text":506},"Unit commitment is the classic power-systems planning problem: given several\ngenerating units, decide which to switch on and how much each should\nproduce to meet demand at minimum cost. It mixes binary on\u002Foff decisions with\na quadratic cost curve, which makes it a mixed-integer quadratic program\n(MIQP). With quicopt it is a short Pyomo model.","md",[556,559,562],{"q":557,"a":558},"Why isn't this just a linear program?","Two things break linearity: the on\u002Foff 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.",{"q":560,"a":561},"Can I use integer (not just binary) unit counts?","Yes. Quicopt accepts general integer variables in nonlinear models too, so a group of identical units can be one integer count instead of one binary per unit. Separate binaries stay the standard formulation when the units differ.",{"q":563,"a":564},"Is quicopt free to use?","Yes, to start: sign up for a free account, install the client with pip install \"quicopt[pyomo]\" and set your API key. The free account solves models with up to 50 variables and has a limited compute allowance; no license is needed.",{},true,false,"\u002Fdeveloper\u002Fexamples\u002Funit-commitment",{"title":5,"description":553},"developer\u002Fexamples\u002Funit-commitment","8tuLQ6lC2xKG_75uiWipUEcmnS3laEboBqhuY5QivLo",1794337679434]