This is a static, read-only snapshot — sliders and other controls will not respond.
Back to notebook list
·
Run it yourself
%23%20%2F%2F%2F%20script%0A%23%20%5Btool.marimo.opengraph%5D%0A%23%20title%20%3D%20%22Unit%20Commitment%22%0A%23%20description%20%3D%20%22Indicator%20Constraints%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-unitcommitment.svg%22%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.24.0%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20xpress%20as%20xp%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%0A%20%20%20%20return%20mo%2C%20np%2C%20pd%2C%20plt%2C%20xp%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20**Solving%20an%20electricity%20generation%20problem%20using%20indicator%20constraints**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20This%20example%20shows%20how%20to%20model%20and%20solve%20an%20electricity%20generation%20problem%20typically%20found%20in%20power%20markets%20(see%20%5BGarver%20(1963)%5D(https%3A%2F%2Fieeexplore.ieee.org%2Fdocument%2F4501405))%2C%20showcasing%20the%20use%20of%20indicator%20constraints%20to%20model%20change%20state%20constraints%20when%20generators%20are%20turned%20on%2Foff.%0A%0A%20%20%20%20%26copy%3B%20Copyright%202025-2026%20Fair%20Isaac%20Corporation.%20The%20use%20of%20this%20example%20is%20subject%20to%20%5Blegal%20and%20license%20requirements%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%23legal-and-license-requirements).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Install%20the%20necessary%20packages%0A%20%20%20%20%23%20'%25pip%20install%20-q%20xpress%20numpy%20pandas%20matplotlib'%20command%20supported%20automatically%20in%20marimo%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Problem%20description%0A%0A%20%20%20%20Four%20types%20of%20power%20generators%20are%20available%20to%20meet%20daily%20electricity%20demand%20and%20a%20security%20reserve%20above%20the%20estimated%20demand.%20Each%20type%20of%20generator%20has%20a%20set%20minimum%20and%20maximum%20power%20output%2C%20a%20start-up%20cost%2C%20an%20hourly%20cost%20at%20minimum%20output%2C%20and%20a%20cost%20per%20MW%20above%20minimum%20output.%20A%20generator%20can%20only%20be%20started%20or%20stopped%20at%20the%20beginning%20of%20a%20time%20period.%0A%0A%20%20%20%20We%20first%20solve%20the%20**basic%20model**%2C%20where%20generators%20can%20freely%20switch%20ON%2FOFF%20between%20consecutive%20periods.%20We%20then%20add%20the%20requirement%20that%20once%20a%20generator%20is%20switched%20ON%2FOFF%20it%20must%20remain%20in%20that%20state%20for%20at%20least%20a%20minimum%20number%20of%20periods%2C%20modeled%20with%20**indicator%20constraints**%2C%20and%20compare%20the%20extra%20cost%20this%20introduces%20against%20the%20basic%20model.%0A%0A%20%20%20%20Use%20the%20**Demand%20scale%20factor**%20control%20below%20to%20scale%20the%20whole%20demand%20curve%20up%20or%20down.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20formulation%0A%0A%20%20%20%20Binary%20variables%20%24work_%7Bu%2Ct%7D%24%2C%20%24start_%7Bu%2Ct%7D%24%2C%20%24stop_%7Bu%2Ct%7D%24%20track%20whether%20unit%20%24u%24%20is%20working%2C%20starting%2C%20or%20stopping%20in%20period%20%24t%24%3B%20continuous%20variable%20%24padd_%7Bu%2Ct%7D%24%20is%20the%20output%20above%20the%20unit's%20minimum.%0A%0A%20%20%20%20%24%24%5Cmin%20%5Csum_%7Bu%2Ct%7D%20COST%5E%7Bstart%7D_u%20%5Ccdot%20start_%7Bu%2Ct%7D%20%2B%20LEN_t%20%5Ccdot%20(COST%5E%7Bmin%7D_u%20%5Ccdot%20work_%7Bu%2Ct%7D%20%2B%20COST%5E%7Badd%7D_u%20%5Ccdot%20padd_%7Bu%2Ct%7D)%20%2B%20PEN%20%5Csum_%7Bu%2Ct%7D%20stop_%7Bu%2Ct%7D%24%24%0A%0A%20%20%20%20Subject%20to%2C%20for%20every%20unit%20%24u%24%20and%20period%20%24t%24%20(with%20%24n%24%20the%20previous%20period)%3A%0A%0A%20%20%20%20*%20Start%2Fstop%20tracking%3A%20%24start_%7Bu%2Ct%7D%20%5Cgeq%20work_%7Bu%2Ct%7D%20-%20work_%7Bu%2Cn%7D%24%2C%20%24start_%7Bu%2Ct%7D%20%5Cleq%20work_%7Bu%2Ct%7D%24%2C%20%24stop_%7Bu%2Ct%7D%20%5Cgeq%20work_%7Bu%2Cn%7D%20-%20work_%7Bu%2Ct%7D%24%2C%20%24stop_%7Bu%2Ct%7D%20%5Cleq%201%20-%20work_%7Bu%2Ct%7D%24%0A%20%20%20%20*%20Output%20above%20minimum%20limited%20by%20capacity%3A%20%24padd_%7Bu%2Ct%7D%20%5Cleq%20(P%5E%7Bmax%7D_u%20-%20P%5E%7Bmin%7D_u)%20%5Ccdot%20work_%7Bu%2Ct%7D%24%0A%20%20%20%20*%20Demand%20satisfied%3A%20%24%5Csum_u%20P%5E%7Bmin%7D_u%20%5Ccdot%20work_%7Bu%2Ct%7D%20%2B%20padd_%7Bu%2Ct%7D%20%5Cgeq%20DEM_t%24%0A%20%20%20%20*%20Security%20reserve%3A%20%24%5Csum_u%20P%5E%7Bmax%7D_u%20%5Ccdot%20work_%7Bu%2Ct%7D%20%5Cgeq%20(1%20%2B%20reserve)%20%5Ccdot%20DEM_t%24%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20parameters%0A%0A%20%20%20%20**You%20can%20adjust%20the%20parameters%20below%20to%20change%20the%20problem%20instance%20and%20re-solve%20the%20model%20automatically.**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20demand_scale_slider%20%3D%20mo.ui.slider(0.7%2C%201.3%2C%20value%3D1.0%2C%20step%3D0.05%2C%20label%3D%22Demand%20scale%20factor%22%2C%20show_value%3DTrue)%0A%20%20%20%20reserve_slider%20%3D%20mo.ui.slider(0.0%2C%200.4%2C%20value%3D0.2%2C%20step%3D0.05%2C%20label%3D%22Security%20reserve%20(fraction%20above%20demand)%22%2C%20show_value%3DTrue)%0A%20%20%20%20penalty_slider%20%3D%20mo.ui.slider(0.0%2C%205.0%2C%20value%3D0.1%2C%20step%3D0.1%2C%20label%3D%22Stop%20penalty%22%2C%20show_value%3DTrue)%0A%20%20%20%20mo.vstack(%5Bdemand_scale_slider%2C%20reserve_slider%2C%20penalty_slider%5D)%0A%20%20%20%20return%20demand_scale_slider%2C%20penalty_slider%2C%20reserve_slider%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Data%20preparation%20and%20analysis%0A%0A%20%20%20%20We%20define%20the%20time%20periods%2C%20demand%2C%20and%20generator%20characteristics.%20The%20**Demand%20scale%20factor**%20control%20above%20rescales%20the%20whole%20demand%20curve%20to%20explore%20how%20the%20optimal%20schedule%20adapts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(demand_scale_slider%2C%20np)%3A%0A%20%20%20%20%23%20Time%20periods%0A%20%20%20%20LEN%20%3D%20%5B6%2C%203%2C%203%2C%202%2C%204%2C%204%2C%202%5D%0A%20%20%20%20DEM%20%3D%20list(np.array(%5B12000%2C%2032000%2C%2025000%2C%2036000%2C%2025000%2C%2030000%2C%2018000%5D)%20*%20demand_scale_slider.value)%0A%0A%20%20%20%20%23%20Power%20plants%0A%20%20%20%20PMIN%20%3D%20%5B750%2C%201000%2C%201200%2C%201800%5D%20%20%20%20%20%20%23%20minimum%20output%20(MW)%20per%20generator%20type%0A%20%20%20%20PMAX%20%3D%20%5B1750%2C%201500%2C%202000%2C%203500%5D%20%20%20%20%20%23%20maximum%20output%20(MW)%20per%20generator%20type%0A%20%20%20%20CSTART%20%3D%20%5B5000%2C%201600%2C%202400%2C%201200%5D%20%20%20%23%20start-up%20cost%20per%20generator%20type%0A%20%20%20%20CMIN%20%3D%20%5B2250%2C%201800%2C%203750%2C%204800%5D%20%20%20%20%20%23%20hourly%20cost%20of%20operating%20generator%20type%20at%20minimum%20output%20(see%20PMIN)%0A%20%20%20%20CADD%20%3D%20%5B2.7%2C%202.2%2C%201.8%2C%203.8%5D%20%20%20%20%20%20%20%20%20%23%20cost%2Fhour%2FMW%20of%20prod.%20above%20min.%20level%20per%20generator%20type%0A%20%20%20%20AVAIL%20%3D%20%5B10%2C%204%2C%208%2C%203%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20number%20of%20units%20per%20type%0A%0A%20%20%20%20NT%20%3D%207%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20number%20of%20time%20periods%0A%20%20%20%20PERIODS%20%3D%20range(NT)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20set%20of%20time%20periods%0A%20%20%20%20TYPES%20%3D%20range(4)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20power%20generator%20types%0A%20%20%20%20UNITS%20%3D%20range(sum(AVAIL))%20%20%20%20%20%20%20%20%20%20%20%23%20power%20generation%20units%0A%20%20%20%20TYPE%20%3D%20%5Bi%20for%20i%20in%20TYPES%20for%20p%20in%20range(AVAIL%5Bi%5D)%5D%20%20%20%20%20%20%23%20associating%20units%20with%20types%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20CADD%2C%0A%20%20%20%20%20%20%20%20CMIN%2C%0A%20%20%20%20%20%20%20%20CSTART%2C%0A%20%20%20%20%20%20%20%20DEM%2C%0A%20%20%20%20%20%20%20%20LEN%2C%0A%20%20%20%20%20%20%20%20NT%2C%0A%20%20%20%20%20%20%20%20PERIODS%2C%0A%20%20%20%20%20%20%20%20PMAX%2C%0A%20%20%20%20%20%20%20%20PMIN%2C%0A%20%20%20%20%20%20%20%20TYPE%2C%0A%20%20%20%20%20%20%20%20TYPES%2C%0A%20%20%20%20%20%20%20%20UNITS%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20LEN%2C%20NT%2C%20mo%2C%20pd)%3A%0A%20%20%20%20%23%20Build%20a%20display%20table%20with%20the%20period%20boundaries%2C%20length%20and%20demand%0A%20%20%20%20ct%20%3D%200%0A%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20for%20period%20in%20range(NT)%3A%0A%20%20%20%20%20%20%20%20rows.append(%7B%22Period%22%3A%20f%22%7Bct%7Dh-%7Bct%2BLEN%5Bperiod%5D%7Dh%22%2C%20%22Length%20(h)%22%3A%20LEN%5Bperiod%5D%2C%20%22Demand%20(MW)%22%3A%20round(DEM%5Bperiod%5D)%7D)%0A%20%20%20%20%20%20%20%20ct%20%2B%3D%20LEN%5Bperiod%5D%0A%0A%20%20%20%20mo.ui.table(pd.DataFrame(rows)%2C%20selection%3DNone)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NT%2C%20PMAX%2C%20PMIN%2C%20TYPE%2C%20TYPES%2C%20UNITS%2C%20np%2C%20plt)%3A%0A%20%20%20%20def%20compute_outputs(p%2C%20work%2C%20padd)%3A%0A%20%20%20%20%20%20%20%20%23%20Get%20the%20total%20power%20output%20per%20generator%20type%20per%20planning%20period%0A%20%20%20%20%20%20%20%20outputs%20%3D%20%5B%5B0.0%20for%20_%20in%20range(NT)%5D%20for%20_%20in%20TYPES%5D%0A%20%20%20%20%20%20%20%20reserve_series%20%3D%20%5B0.0%20for%20_%20in%20range(NT)%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20TYPES%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20t%20in%20range(NT)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20u%20in%20UNITS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20TYPE%5Bu%5D%20%3D%3D%20i%20and%20p.getSolution(work%5Bu%2C%20t%5D)%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20power_output%20%3D%20p.getSolution(PMIN%5BTYPE%5Bu%5D%5D%20*%20work%5Bu%2C%20t%5D%20%2B%20padd%5Bu%2C%20t%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20outputs%5Bi%5D%5Bt%5D%20%2B%3D%20power_output%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reserve_series%5Bt%5D%20%2B%3D%20PMAX%5BTYPE%5Bu%5D%5D%20-%20power_output%0A%20%20%20%20%20%20%20%20return%20outputs%2C%20reserve_series%0A%0A%20%20%20%20def%20plot_outputs(outputs%2C%20reserve_series%2C%20DEM%2C%20title)%3A%0A%20%20%20%20%20%20%20%20%23%20Labels%20for%20the%20unit%20types%0A%20%20%20%20%20%20%20%20labels%20%3D%20%5Bf%22Unit%20type%20%7Bi%20%2B%201%7D%22%20for%20i%20in%20TYPES%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20a%20stacked%20bar%20chart%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(9%2C%205))%0A%0A%20%20%20%20%20%20%20%20%23%20Plot%20output%20for%20each%20unit%20type%20per%20planning%20period%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(len(outputs))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.bar(range(1%2C%20NT%20%2B%201)%2C%20outputs%5Bi%5D%2C%20label%3Dlabels%5Bi%5D%2C%20bottom%3Dnp.sum(outputs%5B%3Ai%5D%2C%20axis%3D0))%0A%0A%20%20%20%20%20%20%20%20%23%20Plot%20the%20total%20reserve%20per%20planning%20period%0A%20%20%20%20%20%20%20%20ax.bar(range(1%2C%20NT%20%2B%201)%2C%20reserve_series%2C%20label%3D%22Total%20reserve%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bottom%3Dnp.sum(outputs%2C%20axis%3D0)%2C%20fill%3DFalse%2C%20edgecolor%3D%22black%22%2C%20linestyle%3D%22--%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Plot%20the%20demand%20data%20as%20a%20line%0A%20%20%20%20%20%20%20%20ax.plot(range(1%2C%20NT%20%2B%201)%2C%20DEM%2C%20color%3D%22black%22%2C%20marker%3D%22o%22%2C%20label%3D%22Demand%20(MW)%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Label%20and%20size%20the%20axes%0A%20%20%20%20%20%20%20%20ax.set_xlabel(%22Planning%20period%22%2C%20fontsize%3D12)%0A%20%20%20%20%20%20%20%20ax.set_ylabel(%22Total%20output%20(MW)%22%2C%20fontsize%3D12)%0A%20%20%20%20%20%20%20%20ax.set_title(title%2C%20fontsize%3D13)%0A%0A%20%20%20%20%20%20%20%20%23%20Add%20a%20legend%0A%20%20%20%20%20%20%20%20ax.legend(loc%3D%22upper%20left%22%2C%20bbox_to_anchor%3D(1%2C%201)%2C%20fontsize%3D10)%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20compute_outputs%2C%20plot_outputs%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20implementation%20and%20solution%0A%0A%20%20%20%20Constraints%20are%20added%20directly%20to%20the%20problem%20using%20generator%20expressions%20over%20%60(unit%2C%20period)%60%20pairs.%20The%20daily%20cost%20and%20stop%20penalty%20are%20tracked%20as%20separate%20expressions%20so%20they%20can%20be%20reported%20individually%20alongside%20the%20total%20objective%20value.%20We%20start%20with%20the%20**basic%20model**%2C%20without%20minimum%20up%2Fdown%20time%20constraints.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20CADD%2C%0A%20%20%20%20CMIN%2C%0A%20%20%20%20CSTART%2C%0A%20%20%20%20DEM%2C%0A%20%20%20%20LEN%2C%0A%20%20%20%20NT%2C%0A%20%20%20%20PERIODS%2C%0A%20%20%20%20PMAX%2C%0A%20%20%20%20PMIN%2C%0A%20%20%20%20TYPE%2C%0A%20%20%20%20UNITS%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20penalty_slider%2C%0A%20%20%20%20reserve_slider%2C%0A%20%20%20%20xp%2C%0A)%3A%0A%20%20%20%20PEN%20%3D%20penalty_slider.value%20%20%20%20%20%20%20%20%20%20%20%20%23%20penalty%20associated%20with%20stopping%20units%0A%20%20%20%20RESERVE%20%3D%201.0%20%2B%20reserve_slider.value%0A%0A%20%20%20%20%23%20Create%20problem%0A%20%20%20%20p%20%3D%20xp.problem(%22Unit%20commitment%22)%0A%0A%20%20%20%20%23%20Create%20decision%20variables%0A%20%20%20%20start%20%3D%20p.addVariables(UNITS%2C%20PERIODS%2C%20vartype%3Dxp.binary%2C%20name%3D%22start%22)%0A%20%20%20%20stop%20%3D%20p.addVariables(UNITS%2C%20PERIODS%2C%20vartype%3Dxp.binary%2C%20name%3D%22stop%22)%0A%20%20%20%20work%20%3D%20p.addVariables(UNITS%2C%20PERIODS%2C%20vartype%3Dxp.binary%2C%20name%3D%22work%22)%0A%20%20%20%20padd%20%3D%20p.addVariables(UNITS%2C%20PERIODS%2C%20name%3D%22padd%22)%0A%0A%20%20%20%20%23%20If%20generator%20starts%20in%20period%0A%20%20%20%20p.addConstraint(start%5Bu%2C%20t%5D%20%3E%3D%20work%5Bu%2C%20t%5D%20-%20work%5Bu%2C%20(NT%20%2B%20t%20-%201)%20%25%20NT%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%20%20%20%20p.addConstraint(start%5Bu%2C%20t%5D%20%3C%3D%20work%5Bu%2C%20t%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%0A%20%20%20%20%23%20If%20generator%20stops%20before%20period%0A%20%20%20%20p.addConstraint(stop%5Bu%2C%20t%5D%20%3E%3D%20work%5Bu%2C%20(NT%20%2B%20t%20-%201)%20%25%20NT%5D%20-%20work%5Bu%2C%20t%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%20%20%20%20p.addConstraint(stop%5Bu%2C%20t%5D%20%3C%3D%201%20-%20work%5Bu%2C%20t%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%0A%20%20%20%20%23%20Limit%20on%20power%20production%20above%20minimum%20level%0A%20%20%20%20p.addConstraint(padd%5Bu%2C%20t%5D%20%3C%3D%20(PMAX%5BTYPE%5Bu%5D%5D%20-%20PMIN%5BTYPE%5Bu%5D%5D)%20*%20work%5Bu%2C%20t%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%0A%20%20%20%20%23%20Satisfy%20demands%0A%20%20%20%20p.addConstraint(xp.Sum(PMIN%5BTYPE%5Bu%5D%5D%20*%20work%5Bu%2C%20t%5D%20%2B%20padd%5Bu%2C%20t%5D%20for%20u%20in%20UNITS)%20%3E%3D%20DEM%5Bt%5D%20for%20t%20in%20PERIODS)%0A%0A%20%20%20%20%23%20Security%20reserve%0A%20%20%20%20p.addConstraint(xp.Sum(PMAX%5BTYPE%5Bu%5D%5D%20*%20work%5Bu%2C%20t%5D%20for%20u%20in%20UNITS)%20%3E%3D%20RESERVE%20*%20DEM%5Bt%5D%20for%20t%20in%20PERIODS)%0A%0A%20%20%20%20%23%20Create%20and%20add%20the%20objective%20function%20of%20the%20problem%20(compute%20'daily%20cost'%20and%20'penalty'%20separately)%0A%20%20%20%20Cost%20%3D%20xp.Sum(CSTART%5BTYPE%5Bu%5D%5D%20*%20start%5Bu%2C%20t%5D%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20LEN%5Bt%5D%20*%20(CMIN%5BTYPE%5Bu%5D%5D%20*%20work%5Bu%2C%20t%5D%20%2B%20CADD%5BTYPE%5Bu%5D%5D%20*%20padd%5Bu%2C%20t%5D)%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%20%20%20%20Penalty%20%3D%20PEN%20*%20xp.Sum(stop%5Bu%2C%20t%5D%20for%20u%20in%20UNITS%20for%20t%20in%20PERIODS)%0A%20%20%20%20p.setObjective(Cost%20%2B%20Penalty)%0A%0A%20%20%20%20%23%20Optimize%20the%20problem%20and%20print%20the%20daily%20cost%2C%20penalty%20and%20total%20objective%20value%0A%20%20%20%20p.controls.outputlog%20%3D%200%0A%20%20%20%20p.optimize()%0A%0A%20%20%20%20basic_cost%20%3D%20p.getSolution(Cost)%0A%20%20%20%20basic_penalty%20%3D%20p.getSolution(Penalty)%0A%20%20%20%20basic_objval%20%3D%20p.attributes.objval%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20Cost%2C%0A%20%20%20%20%20%20%20%20Penalty%2C%0A%20%20%20%20%20%20%20%20basic_cost%2C%0A%20%20%20%20%20%20%20%20basic_objval%2C%0A%20%20%20%20%20%20%20%20basic_penalty%2C%0A%20%20%20%20%20%20%20%20p%2C%0A%20%20%20%20%20%20%20%20padd%2C%0A%20%20%20%20%20%20%20%20start%2C%0A%20%20%20%20%20%20%20%20stop%2C%0A%20%20%20%20%20%20%20%20work%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(basic_cost%2C%20basic_objval%2C%20basic_penalty%2C%20mo)%3A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Daily%20cost%3A**%20%7Bbasic_cost%3A%2C.2f%7D%0A%0A%20%20%20%20**Stop%20penalty%3A**%20%7Bbasic_penalty%3A%2C.2f%7D%0A%0A%20%20%20%20**Objective%20value%3A**%20%7Bbasic_objval%3A%2C.2f%7D%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20compute_outputs%2C%20p%2C%20padd%2C%20plot_outputs%2C%20work)%3A%0A%20%20%20%20_outputs%2C%20_reserve%20%3D%20compute_outputs(p%2C%20work%2C%20padd)%0A%20%20%20%20plot_outputs(_outputs%2C%20_reserve%2C%20DEM%2C%20%22Basic%20model%20(no%20minimum%20up%2Fdown%20time)%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Adding%20indicator%20constraints%0A%0A%20%20%20%20In%20real-world%20applications%2C%20generators%20must%20often%20remain%20for%20some%20time%20in%20a%20certain%20state%20after%20they%20have%20been%20switched%20to%20that%20state%2C%20i.e.%20if%20a%20generator%20is%20turned%20ON%2FOFF%2C%20it%20must%20remain%20in%20that%20state%20for%20at%20least%20%24ON%5E%7Bmin%7D_s%24%2F%24OFF%5E%7Bmin%7D_s%24%20periods%2C%20respectively.%20Such%20state-change%20constraints%20can%20be%20formulated%20with%20the%20help%20of%20so-called%20**indicator%20constraints**%2C%20whose%20enforcement%20depends%20on%20the%20value%20of%20a%20binary%20%22indicator%22%20variable.%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%26%20%5Chbox%7BCan%20only%20switch%20OFF%20at%20least%20%24ON%5E%7Bmin%7D_s%24%20periods%20after%20it%20has%20been%20turned%20ON%3A%7D%20%5C%5C%0A%20%20%20%20%26%20%5Cqquad%20%5Csum_%7Bj%3Dt%2B1%7D%5E%7Bt%2BON%5E%7Bmin%7D_s-1%7D%20stop_%7Bu%2C(j%20%5Cbmod%20NT)%7D%20%5Cleq%200%2C%20%5Cqquad%20%5Cforall%20u%2C%20%5Cforall%20t%3A%20start_%7Bu%2Ct%7D%20%3D%201%20%5C%5C%0A%20%20%20%20%26%20%5Chbox%7BCan%20only%20switch%20ON%20at%20least%20%24OFF%5E%7Bmin%7D_s%24%20periods%20after%20it%20has%20been%20turned%20OFF%3A%7D%20%5C%5C%0A%20%20%20%20%26%20%5Cqquad%20%5Csum_%7Bj%3Dt%2B1%7D%5E%7Bt%2BOFF%5E%7Bmin%7D_s-1%7D%20start_%7Bu%2C(j%20%5Cbmod%20NT)%7D%20%5Cleq%200%2C%20%5Cqquad%20%5Cforall%20u%2C%20%5Cforall%20t%3A%20stop_%7Bu%2Ct%7D%20%3D%201%20%5C%5C%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20Indicator%20constraints%20are%20added%20with%20%5Bproblem.addIndicator()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2FchModeling.html%3Fscroll%3DsecModelingIndicator)%20to%20the%20**same%20problem**%20used%20for%20the%20basic%20model%20above.%20Comparing%20the%20outcome%20with%20the%20basic%20model%2C%20there%20is%20usually%20an%20extra%20cost%20from%20introducing%20these%20constraints%2C%20since%20units%20are%20no%20longer%20allowed%20to%20flip%20ON%2FOFF%20within%20a%20short%20window.%20The%20indicators%20also%20make%20the%20problem%20harder%20(and%20slower)%20to%20solve.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NT%2C%20PERIODS%2C%20TYPE%2C%20UNITS%2C%20mo%2C%20p%2C%20start%2C%20stop%2C%20xp)%3A%0A%20%20%20%20%23%20Minimum%20time%0A%20%20%20%20ONMIN%20%3D%20%5B3%2C%203%2C%203%2C%203%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20minimum%20time%20intervals%20a%20generator%20type%20must%20be%20ON%20once%20switched%20to%20that%20state%0A%20%20%20%20DWMIN%20%3D%20%5B3%2C%203%2C%203%2C%203%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20minimum%20time%20intervals%20a%20generator%20type%20must%20be%20OFF%20once%20switched%20to%20that%20state%0A%0A%20%20%20%20%23%20Indicator%20constraints%0A%20%20%20%20for%20u%20in%20UNITS%3A%0A%20%20%20%20%20%20%20%20for%20t%20in%20PERIODS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Can%20only%20switch%20off%20at%20least%20ONMIN%20periods%20later%0A%20%20%20%20%20%20%20%20%20%20%20%20p.addIndicator(start%5Bu%2C%20t%5D%20%3D%3D%201%2C%20xp.Sum(stop%5Bu%2C%20j%20%25%20NT%5D%20for%20j%20in%20range(t%20%2B%201%2C%20t%20%2B%20ONMIN%5BTYPE%5Bu%5D%5D))%20%3C%3D%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Can%20only%20switch%20on%20at%20least%20DWMIN%20periods%20later%0A%20%20%20%20%20%20%20%20%20%20%20%20p.addIndicator(stop%5Bu%2C%20t%5D%20%3D%3D%201%2C%20xp.Sum(start%5Bu%2C%20j%20%25%20NT%5D%20for%20j%20in%20range(t%20%2B%201%2C%20t%20%2B%20DWMIN%5BTYPE%5Bu%5D%5D))%20%3C%3D%200)%0A%0A%20%20%20%20%23%20Re-optimize%20the%20problem%20and%20print%20the%20daily%20cost%2C%20penalty%20and%20total%20objective%20value%0A%20%20%20%20p.controls.outputlog%20%3D%200%0A%20%20%20%20p.optimize()%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Cost%2C%20Penalty%2C%20basic_objval%2C%20mo%2C%20p)%3A%0A%20%20%20%20ind_cost%20%3D%20p.getSolution(Cost)%0A%20%20%20%20ind_penalty%20%3D%20p.getSolution(Penalty)%0A%20%20%20%20ind_objval%20%3D%20p.attributes.objval%0A%20%20%20%20delta%20%3D%20ind_objval%20-%20basic_objval%0A%20%20%20%20pct%20%3D%20100%20*%20delta%20%2F%20basic_objval%20if%20basic_objval%20else%200.0%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Daily%20cost%3A**%20%7Bind_cost%3A%2C.2f%7D%0A%0A%20%20%20%20**Stop%20penalty%3A**%20%7Bind_penalty%3A%2C.2f%7D%0A%0A%20%20%20%20**Objective%20value%3A**%20%7Bind_objval%3A%2C.2f%7D%0A%0A%20%20%20%20**Extra%20cost%20from%20enforcing%20minimum%20up%2Fdown%20time%3A**%20%7Bdelta%3A%2C.2f%7D%20(%7Bpct%3A.2f%7D%25%20higher%20than%20the%20basic%20model)%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM%2C%20compute_outputs%2C%20p%2C%20padd%2C%20plot_outputs%2C%20work)%3A%0A%20%20%20%20_outputs%2C%20_reserve%20%3D%20compute_outputs(p%2C%20work%2C%20padd)%0A%20%20%20%20plot_outputs(_outputs%2C%20_reserve%2C%20DEM%2C%20%22With%20minimum%20up%2Fdown%20time%20(indicator%20constraints)%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
8e2e45bcec82759215b4725c5bffc162