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%22MoselPy%3A%20Scheduling%22%0A%23%20description%20%3D%20%22Stadium%2C%20flowshop%2C%20jobshop%2C%20sequencing%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-scheduling.svg%22%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.25.2%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20return%20(mo%2C)%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**FICO%26reg%3B%20Xpress%20MoselPy%20-%20Chapter%20B%3A%20Scheduling%20and%20Sequencing**%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***moselpy_scheduling_sequencing.py***%20-%20Scheduling%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20stadium%20construction%2C%20flow%20shop%2C%20job%20shop%2C%20sequencing%2C%20paint%20production%2C%20and%20assembly%20line%20balancing%2C%20solved%20with%20MoselPy.%0A%0A%20%20%20%20%26copy%3B%20Copyright%202026%20Fair%20Isaac%20Corporation.%20The%20use%20of%20this%20example%20is%20subject%20to%20%5Blegal%20and%20license%20requirements%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fxpress-community%23legal-and-license-requirements).%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%20The%20cell%20below%20imports%20the%20necessary%20libraries%20and%20set%20%60MODELS%60%20and%20%60DATA%60%2C%20the%20path%20variables%20pointing%20to%20the%20Mosel%20model%20files%20and%20data%20files%20used%20in%20this%20notebook.%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%20%23%20On%20Linux%2C%20preload%20the%20Xpress%20solver%20library%20so%20the%20Mosel%20optimizer%20module%0A%20%20%20%20%23%20(mmxprs)%20can%20resolve%20it%20at%20runtime.%0A%20%20%20%20import%20sys%2C%20os%2C%20glob%2C%20ctypes%2C%20xpresslibs%0A%20%20%20%20if%20sys.platform%20%3D%3D%20%22linux%22%3A%0A%20%20%20%20%20%20%20%20for%20_lib%20in%20glob.glob(os.path.join(xpresslibs.solver_libs_dir%2C%20%22libxprs.so*%22))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ctypes.CDLL(_lib%2C%20mode%3Dctypes.RTLD_GLOBAL)%0A%0A%20%20%20%20import%20moselpy%20as%20mp%0A%20%20%20%20from%20moselpy%20import%20ExecStatus%0A%20%20%20%20from%20io%20import%20StringIO%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.patches%20as%20mpatches%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20pandas%20as%20pd%0A%0A%20%20%20%20_root%20%3D%20mo.notebook_dir()%20%2F%20%22public%22%20%2F%20%22moselpy%22%0A%20%20%20%20MODELS%20%3D%20str(_root%20%2F%20%22models%22%20%2F%20%22scheduling_sequencing%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22scheduling_sequencing%22)%0A%0A%20%20%20%20print(f%22MoselPy%20%7Bmp.version%7D%20%20%7C%20%20Mosel%20%7Bmp.mosel_version%7D%20%20%7C%20%20Xpress%20%7Bmp.xpress_version%7D%22)%0A%20%20%20%20mp.config.set_default_stream(mp.StreamType.OUTPUT%2C%20%22null%3A%22)%0A%20%20%20%20return%20DATA%2C%20MODELS%2C%20StringIO%2C%20mp%2C%20mpatches%2C%20np%2C%20pd%2C%20plt%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%20Scheduling%20problems%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%20notebook%20covers%20six%20LP%20and%20MIP%20scheduling%20examples%20from%20Chapter%20B%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20range%20from%20classical%20project%20scheduling%20(CPM)%2C%20through%20machine%20scheduling%20(flow%20shop%2C%20job%20shop)%2C%20to%20sequencing%20and%20assignment%20problems.%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Fscheduling_sequencing%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Fscheduling_sequencing%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Fscheduling_sequencing)%0A%0A%20%20%20%20%3E%20**Requirements%3A**%20%60pip%20install%20moselpy%20mosellibs%20xpresslibs%60%20-%20%60mosellibs%60%20provides%20the%20Mosel%20runtime%20and%20%60xpresslibs%60%20the%20Xpress%20solver%20libraries.%20No%20local%20Xpress%20installation%20is%20needed%20to%20run%20these%20notebooks%2C%20as%20long%20as%20the%20packages%20with%20libraries%20have%20been%20installed.%0A%0A%20%20%20%20%3E%20A%20local%20FICO%26reg%3B%20Xpress%20installation%20is%20recommended%20if%20you%20want%20access%20to%20the%20full%20set%20of%20examples%2C%20user%20guides%20and%20documentation%2C%20solver%20libraries%2C%20and%20Xpress%20Workbench.%20Obtain%20one%20via%20the%20%5BFICO%26reg%3B%20Xpress%20community%20license%5D(https%3A%2F%2Fwww.fico.com%2Fen%2Ffico-xpress-community-license)%20page.%20If%20a%20local%20installation%20is%20present%2C%20set%20%60XPRESSDIR%60%20before%20starting%20marimo%20to%20use%20it%20instead%20of%20the%20%60mosellibs%60%20and%20%60xpresslibs%60%20packages.%0A%0A%20%20%20%20%3E%20**MoselPy%20documentation%3A**%20%5BMoselPy%20User%20Guide%20and%20Reference%20Manual%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fmosel%2FMoselPy%2Fdhtml)%0A%0A%20%20%20%20%3E%20**Mosel%20examples%20on%20the%20web%3A**%20%5Bexamples.xpress.fico.com%5D(https%3A%2F%2Fexamples.xpress.fico.com%2Fexample.pl%3Fid%3Dmosel_app_2)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**Model%20introspection**%3A%20B-1%20demonstrates%20%60model.name%60%2C%20%60model.version%60%2C%20and%20%60model.size%60%20immediately%20after%20%60load_model()%60%2C%20before%20any%20%60run()%60%20call.%0A%20%20%20%20-%20**DataFrame%20as%202D%20input**%3A%20B-2%20passes%20the%20job%20duration%20matrix%20as%20a%20%60pd.DataFrame%60%20via%20%60input_data%60%3B%20results%20retrieved%20with%20%60find_identifier(use_pandas%3DTrue)%60.%0A%20%20%20%20-%20**Iterative%20solving**%3A%20B-4%20loads%20the%20sequencing%20model%20once%20and%20runs%20three%20times%20with%20%60OBJ%3D1%60%2C%20%60OBJ%3D2%60%2C%20%60OBJ%3D3%60%20via%20%60exec_params%60%3B%20results%20collected%20into%20a%20%60pd.DataFrame%60.%0A%20%20%20%20-%20**Output%20capture%20to%20StringIO**%3A%20B-5%20redirects%20Mosel's%20%60writeln%60%20output%20to%20a%20Python%20string%20via%20%60moselpy%3Aout%60.%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%20B-1%20Stadium%20construction%20(LP)%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*Applications%20Book%20Chapter%20B%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60b1stadium_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb1stadium_p.mos)%2C%20%5B%60b1stadium2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb1stadium2_p.mos)%0A%0A%20%20%20%20A%20town%20plans%20to%20build%20a%20stadium%20with%2018%20tasks%20(plus%20one%20fictitious%20end%20task).%20Tasks%20have%20precedence%20relations%20defined%20by%20an%20adjacency%20matrix%20%60ARC%60.%20The%20project%20is%20solved%20in%20two%20phases%3A%0A%0A%20%20%20%201.%20**Phase%201%20-%20minimum%20duration%20(LP)%3A**%20find%20the%20earliest%20completion%20time%20by%20minimizing%20%60start(N)%60.%0A%20%20%20%202.%20**Phase%202%20-%20project%20crashing%20(LP)%3A**%20given%20the%20phase-1%20duration%2C%20maximize%20profit%20by%20paying%20to%20crash%20(accelerate)%20individual%20tasks.%20Each%20task%20has%20a%20maximum%20reduction%20%60MAXW%60%20and%20a%20cost%20%60COST%60%3B%20the%20builder%20earns%20%60BONUS%60%20per%20week%20of%20early%20completion.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BTASKS%7D%20%3D%201..19%24%20(task%2019%20is%20the%20fictitious%20end)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bstart%7D_i%20%5Cgeq%200%24%20(week%20task%20%24i%24%20starts)%3B%20%24%5Cmathit%7Badvance%7D_i%20%5Cgeq%200%24%20(weeks%20task%20%24i%24%20is%20accelerated).%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7BPhase%201%3A%7D%5Cquad%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Cmathit%7Bstart%7D_%7B19%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Cmathit%7Bstart%7D_j%20-%20%5Cmathit%7Bstart%7D_i%20%5Cgeq%20%5Ctext%7BDUR%7D_i%20%5Cquad%20%5Cforall%5C%2C(i%2Cj)%20%5Cin%20%5Ctext%7BARC%7D%20%5C%5C%5B6pt%5D%0A%20%20%20%20%5Ctext%7BPhase%202%3A%7D%5Cquad%20%5Ctext%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Ctext%7BBONUS%7D%20%5Ccdot%20%5Cmathit%7Badvance%7D_%7B19%7D%20-%20%5Csum_%7Bi%3D1%7D%5E%7B18%7D%20%5Ctext%7BCOST%7D_i%20%5Ccdot%20%5Cmathit%7Badvance%7D_i%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Cmathit%7Bstart%7D_j%20-%20%5Cmathit%7Bstart%7D_i%20%2B%20%5Cmathit%7Badvance%7D_i%20%5Cgeq%20%5Ctext%7BDUR%7D_i%20%5Cquad%20%5Cforall%5C%2C(i%2Cj)%20%5Cin%20%5Ctext%7BARC%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_%7B19%7D%20%2B%20%5Cmathit%7Badvance%7D_%7B19%7D%20%3D%20%5Ctext%7Bobj1%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Badvance%7D_i%20%5Cleq%20%5Ctext%7BMAXW%7D_i%20%5Cquad%20%5Cforall%5C%2C%20i%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%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%20Define%20all%20input%20data%20in%20Python.%20%60ARC%60%20is%20passed%20as%20a%20dict%20of%20%60(i%2Cj)%60%20keys%20(non-zero%20entries%20of%20the%20adjacency%20matrix)%3B%20%60DUR%60%2C%20%60COST%60%2C%20and%20%60MAXW%60%20are%201-indexed%20dicts.%20The%20model%20has%20two%20%60initializations%20from%60%20blocks%20but%20with%20%60DATASOURCE%3D%22moselpy%3A%22%60%20both%20blocks%20read%20from%20the%20same%20%60input_data%60%20dict.%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%20N%20%3D%2019%0A%20%20%20%20DUR%20%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B2%2C16%2C9%2C8%2C10%2C6%2C2%2C2%2C9%2C5%2C3%2C2%2C1%2C7%2C4%2C3%2C9%2C1%2C0%5D)%7D%0A%20%20%20%20COST%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B0%2C30%2C26%2C12%2C17%2C15%2C8%2C0%2C42%2C21%2C18%2C0%2C0%2C22%2C12%2C6%2C16%2C0%2C0%5D)%7D%0A%20%20%20%20MAXW%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B0%2C3%2C1%2C2%2C2%2C1%2C1%2C0%2C2%2C1%2C1%2C0%2C0%2C2%2C2%2C1%2C3%2C0%2C0%5D)%7D%0A%20%20%20%20BONUS%20%3D%2030%0A%20%20%20%20ARC%20%3D%20%7B(1%2C2)%3A1%2C(2%2C3)%3A1%2C(2%2C4)%3A1%2C(2%2C14)%3A1%2C(3%2C5)%3A1%2C(4%2C6)%3A1%2C(4%2C7)%3A1%2C(4%2C9)%3A1%2C(4%2C10)%3A1%2C(4%2C15)%3A1%2C%0A%20%20%20%20%20%20%20%20%20%20%20(5%2C6)%3A1%2C(6%2C8)%3A1%2C(6%2C9)%3A1%2C(6%2C11)%3A1%2C(7%2C13)%3A1%2C(8%2C16)%3A1%2C(9%2C12)%3A1%2C(10%2C19)%3A1%2C(11%2C16)%3A1%2C%0A%20%20%20%20%20%20%20%20%20%20%20(12%2C17)%3A1%2C(13%2C19)%3A1%2C(14%2C15)%3A1%2C(14%2C16)%3A1%2C(15%2C19)%3A1%2C(16%2C19)%3A1%2C(17%2C18)%3A1%2C(18%2C19)%3A1%7D%0A%20%20%20%20return%20ARC%2C%20BONUS%2C%20COST%2C%20DUR%2C%20MAXW%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%20Task%20durations%20and%20crashing%20costs%20as%20input%20data%3A%20each%20bar%20shows%20the%20base%20duration%3B%20tasks%20eligible%20for%20crashing%20are%20annotated%20with%20their%20cost%20per%20week%20saved.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(COST%2C%20DUR%2C%20MAXW%2C%20mo%2C%20mpatches%2C%20plt)%3A%0A%20%20%20%20task_names_viz%20%3D%20%5B'Site%20prep'%2C%20'Drainage'%2C%20'Foundations'%2C%20'Ground%20works'%2C%20'Concrete'%2C%20'Roofing'%2C%20'External%20works'%2C%20'Formwork'%2C%20'Walls'%2C%20'Visitors%20building'%2C%20'Offices'%2C%20'Stands'%2C%20'Fire%20escape'%2C%20'Electrical'%2C%20'Mains'%2C%20'Fittings'%2C%20'Finishing'%2C%20'Commissioning'%5D%0A%20%20%20%20DUR_list%20%3D%20%5BDUR%5Bi%5D%20for%20i%20in%20range(1%2C%2019)%5D%0A%20%20%20%20COST_list%20%3D%20%5BCOST%5Bi%5D%20for%20i%20in%20range(1%2C%2019)%5D%0A%20%20%20%20MAXW_list%20%3D%20%5BMAXW%5Bi%5D%20for%20i%20in%20range(1%2C%2019)%5D%0A%20%20%20%20bar_colors%20%3D%20%5B'%231565C0'%20if%20w%20%3E%200%20else%20'%2390CAF9'%20for%20w%20in%20MAXW_list%5D%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(10%2C%206))%0A%20%20%20%20_bars%20%3D%20_ax.barh(range(18)%2C%20DUR_list%2C%20color%3Dbar_colors%2C%20edgecolor%3D'white'%2C%20height%3D0.7)%0A%20%20%20%20for%20_i%2C%20(_d%2C%20c%2C%20w)%20in%20enumerate(zip(DUR_list%2C%20COST_list%2C%20MAXW_list))%3A%0A%20%20%20%20%20%20%20%20if%20w%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_d%20%2B%200.3%2C%20_i%2C%20f'-%7Bw%7Dw%20%40%7Bc%7D'%2C%20va%3D'center'%2C%20fontsize%3D7.5%2C%20color%3D'%23B71C1C')%0A%20%20%20%20_ax.set_yticks(range(18))%0A%20%20%20%20_ax.set_yticklabels(task_names_viz%2C%20fontsize%3D8)%0A%20%20%20%20_ax.set_xlabel('Duration%20(weeks)')%0A%20%20%20%20_ax.set_title('B-1%20Stadium%20-%20task%20durations%20and%20crashing%20options')%0A%20%20%20%20blue%20%3D%20mpatches.Patch(color%3D'%2390CAF9'%2C%20label%3D'Fixed%20duration')%0A%20%20%20%20dark%20%3D%20mpatches.Patch(color%3D'%231565C0'%2C%20label%3D'Crashable%20task')%0A%20%20%20%20_ax.legend(handles%3D%5Bblue%2C%20dark%5D%2C%20loc%3D'lower%20right')%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20mo.output.append(plt.gcf())%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%20Compile%20and%20load%20%60b1stadium_p.mos%60%2C%20then%20run%20it.%20After%20%60load_model()%60%2C%20%60model.name%60%2C%20%60model.version%60%2C%20and%20%60model.size%60%20are%20available%20for%20model%20introspection%20before%20any%20%60run()%60%20call.%20The%20model%20runs%20both%20phases%20internally%20and%20returns%20%60obj1%60%20(phase-1%20duration)%2C%20%60obj_val%60%20(phase-2%20profit)%2C%20and%20the%20start%20and%20advance%20arrays.%20%60sol_start%60%20is%20a%202D%20array%20indexed%20by%20%60(run%2C%20task)%60%3A%20run%201%20captures%20the%20phase-1%20schedule%20(minimum%20duration)%3B%20run%202%20captures%20the%20phase-2%20schedule%20after%20crashing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ARC%2C%20BONUS%2C%20COST%2C%20DUR%2C%20MAXW%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb1stadium_p.mos'%2C%20f'%7BMODELS%7D%2Fb1stadium_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fb1stadium_p.bim')%0A%20%20%20%20print(f%22Model%20name%3A%20%20%20%20'%7B_model.name%7D'%22)%0A%20%20%20%20print(f'Model%20version%3A%20%7B_model.version%7D')%0A%20%20%20%20print(f'Compiled%20size%3A%20%7B_model.size%3A%2C%7D%20bytes')%0A%20%20%20%20output%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DUR'%3A%20DUR%2C%20'COST'%3A%20COST%2C%20'MAXW'%3A%20MAXW%2C%20'BONUS'%3A%20BONUS%2C%20'ARC'%3A%20ARC%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj1%20%3D%20output%5B'obj1'%5D%0A%20%20%20%20%20%20%20%20obj_val%20%3D%20output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_start%20%3D%20output%5B'sol_start'%5D%0A%20%20%20%20%20%20%20%20sol_advance%20%3D%20output%5B'sol_advance'%5D%0A%20%20%20%20%20%20%20%20print(f'%5CnPhase%201%20-%20minimum%20project%20duration%3A%20%7Bobj1%3A.0f%7D%20weeks')%0A%20%20%20%20%20%20%20%20print(f'Phase%202%20-%20maximum%20builder%20profit%3A%20%20%20%7Bobj_val%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20_task_names%20%3D%20%5B'Site%20prep'%2C%20'Drainage'%2C%20'Foundations'%2C%20'Ground%20works'%2C%20'Concrete'%2C%20'Roofing'%2C%20'External%20works'%2C%20'Formwork'%2C%20'Walls'%2C%20'Visitors%20building'%2C%20'Offices'%2C%20'Stands'%2C%20'Fire%20escape'%2C%20'Electrical'%2C%20'Mains'%2C%20'Fittings'%2C%20'Finishing'%2C%20'Commissioning'%5D%0A%20%20%20%20%20%20%20%20print(f'%7B'Task'%3A%3C20%7D%20%7B'Start%20P1'%3A%3E8%7D%20%7B'Start%20P2'%3A%3E8%7D%20%7B'Advance'%3A%3E8%7D')%20%20%23%20dict%3A%20(run%2C%20task)%20-%3E%20start%20week%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(1%2C%2019)%3A%20%20%23%20dict%3A%20task%20-%3E%20weeks%20advanced%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%3D%20_task_names%5B_i%20-%201%5D%20if%20_i%20%3C%3D%20len(_task_names)%20else%20f'Task%20%7B_i%7D'%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7Bname%3A%3C18%7D%20%7Bsol_start%5B1%2C%20_i%5D%3A%3E8.0f%7D%20%7Bsol_start%5B2%2C%20_i%5D%3A%3E8.0f%7D%20%7Bsol_advance%5B_i%5D%3A%3E8.1f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20return%20obj1%2C%20obj_val%2C%20sol_advance%2C%20sol_start%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%20Gantt%20chart%20showing%20task%20start%20times%20and%20durations.%20Tasks%20with%20non-zero%20advance%20(crashing)%20are%20highlighted%20in%20dark%20blue%3B%20the%20red%20overlay%20shows%20the%20weeks%20saved.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20mpatches%2C%20obj1%2C%20obj_val%2C%20plt%2C%20sol_advance%2C%20sol_start)%3A%0A%20%20%20%20if%20'sol_advance'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_DUR_viz%20%3D%20%5B2%2C%2016%2C%209%2C%208%2C%2010%2C%206%2C%202%2C%202%2C%209%2C%205%2C%203%2C%202%2C%201%2C%207%2C%204%2C%203%2C%209%2C%201%5D%0A%20%20%20%20%20%20%20%20_task_names%20%3D%20%5B'Site%20prep'%2C%20'Drainage'%2C%20'Foundations'%2C%20'Ground%20works'%2C%20'Concrete'%2C%20'Roofing'%2C%20'External%20works'%2C%20'Formwork'%2C%20'Walls'%2C%20'Visitors%20building'%2C%20'Offices'%2C%20'Stands'%2C%20'Fire%20escape'%2C%20'Electrical'%2C%20'Mains'%2C%20'Fittings'%2C%20'Finishing'%2C%20'Commissioning'%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(12%2C%207))%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%5B'%231565C0'%20if%20sol_advance%5Bi%5D%20%3E%200.01%20else%20'%2390CAF9'%20for%20i%20in%20range(1%2C%2019)%5D%0A%20%20%20%20%20%20%20%20for%20_i%20in%20range(1%2C%2019)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(_i%20-%201%2C%20_DUR_viz%5B_i%20-%201%5D%2C%20left%3Dsol_start%5B2%2C%20_i%5D%2C%20color%3D_colors%5B_i%20-%201%5D%2C%20edgecolor%3D'white'%2C%20height%3D0.7)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sol_advance%5B_i%5D%20%3E%200.01%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(_i%20-%201%2C%20sol_advance%5B_i%5D%2C%20left%3Dsol_start%5B2%2C%20_i%5D%20%2B%20_DUR_viz%5B_i%20-%201%5D%20-%20sol_advance%5B_i%5D%2C%20color%3D'%23E53935'%2C%20edgecolor%3D'white'%2C%20height%3D0.7%2C%20alpha%3D0.7)%0A%20%20%20%20%20%20%20%20_ax.set_yticks(range(18))%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(_task_names%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Weeks')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'B-1%20Stadium%20construction%20-%20duration%20%7Bobj1%3A.0f%7D%20weeks%2C%20crashing%20profit%20%7Bobj_val%3A.0f%7D')%0A%20%20%20%20%20%20%20%20blue_patch%20%3D%20mpatches.Patch(color%3D'%2390CAF9'%2C%20label%3D'Normal%20duration')%0A%20%20%20%20%20%20%20%20dark_patch%20%3D%20mpatches.Patch(color%3D'%231565C0'%2C%20label%3D'Task%20with%20crashing')%0A%20%20%20%20%20%20%20%20red_patch%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Weeks%20saved'%2C%20alpha%3D0.7)%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bblue_patch%2C%20dark_patch%2C%20red_patch%5D%2C%20loc%3D'lower%20right')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20Gantt%20chart')%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%23%20Set-based%20formulation%0A%0A%20%20%20%20%60b1stadium2_p.mos%60%20encodes%20the%20same%20problem%20using%20%60SUCC%3A%20array(TASKS)%20of%20set%20of%20integer%60%20instead%20of%20a%20dynamic%202D%20adjacency%20matrix.%20The%20precedence%20loop%20becomes%20%60forall(i%20in%20TASKS%2C%20j%20in%20SUCC(i))%60%20rather%20than%20iterating%20over%20all%20%60(i%2Cj)%60%20pairs%20with%20%60exists(ARC(i%2Cj))%60.%20Both%20formulations%20solve%20to%20the%20same%20two-phase%20result.%0A%0A%20%20%20%20%3E%20**Note%3A**%20%60b1stadium2_p%60%20reads%20input%20from%20a%20%60.dat%60%20file%20rather%20than%20Python%20%60input_data%60%20because%20%60SUCC%3A%20array(TASKS)%20of%20set%20of%20integer%60%20is%20not%20supported%20by%20the%20%60moselpy%3A%60%20driver%20for%20Python%20input.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DATA%2C%20MODELS%2C%20mp%2C%20obj1%2C%20obj_val)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb1stadium2_p.mos'%2C%20f'%7BMODELS%7D%2Fb1stadium2_p.bim')%0A%20%20%20%20_out2%20%3D%20None%0A%20%20%20%20with%20mp.load_model(f'%7BMODELS%7D%2Fb1stadium2_p.bim')%20as%20model2%3A%0A%20%20%20%20%20%20%20%20_out2%20%3D%20model2.run(exec_params%3D%7B'DATASOURCE'%3A%20f'%7BDATA%7D%2Fb1stadium2_p.dat'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20%20%20%20%20sol2_ok%20%3D%20model2.prob_status.is_solution_available%0A%20%20%20%20if%20sol2_ok%3A%0A%20%20%20%20%20%20%20%20print(f'%5Barray-based%5D%20%20Phase%201%20duration%3A%20%7Bobj1%3A.0f%7D%20w%20%20%7C%20%20Phase%202%20profit%3A%20%7Bobj_val%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'%5Bset-based%5D%20%20%20%20Phase%201%20duration%3A%20%7B_out2%5B'obj1'%5D%3A.0f%7D%20w%20%20%7C%20%20Phase%202%20profit%3A%20%7B_out2%5B'obj_val'%5D%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print('Results%20match%3A'%2C%20abs(_out2%5B'obj_val'%5D%20-%20obj_val)%20%3C%201e-06)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20found%20for%20b1stadium2_p')%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%20B-2%20Flow%20shop%20(MIP)%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*Applications%20Book%20Chapter%20B%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60b2flowshop_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb2flowshop_p.mos)%0A%0A%20%20%20%20A%20workshop%20produces%20metal%20pipes%20on%20three%20machines.%20Each%20of%20six%20jobs%20must%20pass%20through%20all%20three%20machines%20in%20order%20(machine%201%2C%20then%202%2C%20then%203).%20The%20goal%20is%20to%20find%20the%20optimal%20job%20sequence%20(ranking)%20to%20minimize%20the%20total%20waiting%20time%20on%20the%20last%20machine.%0A%0A%20%20%20%20Binary%20variable%20%24%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%24%20if%20job%20%24j%24%20is%20processed%20%24k%24-th.%20Continuous%20variable%20%24%5Cmathit%7Bstart%7D_%7Bm%2Ck%7D%24%20gives%20the%20start%20time%20of%20the%20%24k%24-th%20ranked%20job%20on%20machine%20%24m%24.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BMACH%7D%20%3D%201..3%24%2C%20%24%5Ctext%7BJOBS%7D%20%3D%201..6%24%2C%20%24%5Ctext%7BRANKS%7D%20%3D%201..6%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Bstart%7D_%7Bm%2Ck%7D%20%5Cgeq%200%24%3B%20%24%5Cmathit%7Bempty%7D_%7Bm%2Ck%7D%20%5Cgeq%200%24%3B%20%24%5Cmathit%7Bwtime%7D_%7Bm%2Ck%7D%20%5Cgeq%200%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bm%3D1%7D%5E%7B2%7D%5Csum_%7Bj%7D%20%5Ctext%7BDUR%7D_%7Bm%2Cj%7D%20%5Ccdot%20%5Cmathit%7Brank%7D_%7Bj%2C1%7D%20%2B%20%5Csum_%7Bk%3D1%7D%5E%7B5%7D%20%5Cmathit%7Bempty%7D_%7B3%2Ck%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_j%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20k%20%5Cquad%20%5Ctext%7B(one%20job%20per%20rank)%7D%20%5C%5C%0A%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%26%20%5Csum_k%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20j%20%5Cquad%20%5Ctext%7B(one%20rank%20per%20job)%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bempty%7D_%7Bm%2Ck%7D%20%2B%20%5Csum_j%20%5Ctext%7BDUR%7D_%7Bm%2Cj%7D%5Cmathit%7Brank%7D_%7Bj%2Ck%2B1%7D%20%2B%20%5Cmathit%7Bwtime%7D_%7Bm%2Ck%2B1%7D%20%3D%20%5Cmathit%7Bwtime%7D_%7Bm%2Ck%7D%20%2B%20%5Csum_j%20%5Ctext%7BDUR%7D_%7Bm%2B1%2Cj%7D%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%2B%20%5Cmathit%7Bempty%7D_%7Bm%2B1%2Ck%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20The%20duration%20matrix%20%60DUR%60%20(3%20machines%20x%206%20jobs)%20is%20passed%20from%20Python%20as%20a%20%60pd.DataFrame%60.%20Both%20%60DATASOURCE%60%20and%20%60OUTFILE%60%20are%20set%20to%20%60%22moselpy%3A%22%60%20so%20that%20input%20data%20flows%20in%20and%20results%20flow%20back%20through%20Python%20in%20the%20same%20call.%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%20Define%20the%203-by-6%20duration%20matrix%20as%20a%20%60pd.DataFrame%60.%20Rows%20are%20machines%2C%20columns%20are%20jobs.%20Passing%20a%20DataFrame%20directly%20as%20%60input_data%60%20is%20a%20convenient%20way%20to%20supply%202D%20data%20-%20MoselPy%20maps%20the%20row%2Fcolumn%20indices%20to%20the%20Mosel%202D%20array.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(pd)%3A%0A%20%20%20%20dur_data%20%3D%20%5B%5B3%2C%206%2C%203%2C%205%2C%205%2C%207%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B5%2C%204%2C%202%2C%204%2C%204%2C%205%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B5%2C%202%2C%204%2C%206%2C%203%2C%206%5D%5D%0A%20%20%20%20dur_df%20%3D%20pd.DataFrame(dur_data%2C%20index%3D%5B1%2C%202%2C%203%5D%2C%20columns%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%5D)%0A%20%20%20%20dur_df.index.name%20%20%20%3D%20%22Machine%22%0A%20%20%20%20dur_df.columns.name%20%3D%20%22Job%22%0A%0A%20%20%20%20print(%22Duration%20matrix%20(machine%20x%20job)%3A%22)%0A%20%20%20%20print(dur_df.to_string())%0A%20%20%20%20return%20dur_data%2C%20dur_df%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%20Compile%20%60b2flowshop_p.mos%60%20with%20the%20%60%22g%22%60%20debug%20flag%20so%20that%20all%20model%20identifiers%20-%20not%20just%20%60public%60%20ones%20-%20are%20accessible%20via%20%60find_identifier()%60.%20Pass%20%60DATASOURCE%3D%22moselpy%3A%22%60%20and%20%60OUTFILE%3D%22moselpy%3A%22%60%20so%20results%20are%20returned%20in%20the%20output%20dict.%0A%0A%20%20%20%20After%20the%20run%2C%20%60model.find_identifier(%22sol_start%22%2C%20use_pandas%3DTrue)%60%20returns%20a%20MultiIndex%20Series%20keyed%20by%20%60(machine%2C%20rank)%60.%20Calling%20%60.unstack(level%3D0)%60%20pivots%20the%20machine%20index%20into%20columns%2C%20producing%20a%20rank-by-machine%20DataFrame%20of%20start%20times.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20dur_df%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb2flowshop_p.mos'%2C%20f'%7BMODELS%7D%2Fb2flowshop_p.bim'%2C%20'g')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fb2flowshop_p.bim')%0A%20%20%20%20output_1%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DUR'%3A%20dur_df%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_1%20%3D%20output_1%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20waiting%20time%3A%20%7Bobj_val_1%3A.0f%7D')%0A%20%20%20%20%20%20%20%20sol_start_df%20%3D%20_model.find_identifier('sol_start'%2C%20use_pandas%3DTrue).unstack(level%3D0)%0A%20%20%20%20%20%20%20%20sol_start_df.columns%20%3D%20%5Bf'Machine%20%7Bm%7D'%20for%20m%20in%20sol_start_df.columns%5D%0A%20%20%20%20%20%20%20%20sol_start_df.index%20%3D%20%5Bf'Rank%20%7Bk%7D'%20for%20k%20in%20sol_start_df.index%5D%0A%20%20%20%20%20%20%20%20print(f'%5CnStart%20times%20(rank%20x%20machine)%3A')%0A%20%20%20%20%20%20%20%20print(sol_start_df.round(1).to_string())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20return%20obj_val_1%2C%20output_1%2C%20sol_start_df%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%20Gantt%20chart%20with%20machines%20as%20rows.%20Each%20colored%20block%20represents%20one%20job%20(colored%20by%20job%20index)%20starting%20at%20%60sol_start%5Bmachine%2C%20rank%5D%60%20with%20width%20%60DUR%5Bmachine%2C%20job%5D%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dur_data%2C%20mo%2C%20mpatches%2C%20obj_val_1%2C%20output_1%2C%20plt%2C%20sol_start_df)%3A%0A%20%20%20%20if%20'sol_start_df'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20NM%2C%20NJ%20%3D%20(3%2C%206)%0A%20%20%20%20%20%20%20%20job_colors%20%3D%20%5B'%231E88E5'%2C%20'%23FB8C00'%2C%20'%2343A047'%2C%20'%23E53935'%2C%20'%238E24AA'%2C%20'%2300ACC1'%5D%0A%20%20%20%20%20%20%20%20_sol_rank%20%3D%20output_1%5B'sol_rank'%5D%0A%20%20%20%20%20%20%20%20job_order%20%3D%20%5Bround(sum((j%20*%20_sol_rank.get((j%2C%20k)%2C%200)%20for%20j%20in%20range(1%2C%20NJ%20%2B%201))))%20for%20k%20in%20range(1%2C%20NJ%20%2B%201)%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(NM%2C%201%2C%20figsize%3D(12%2C%205)%2C%20sharex%3DTrue)%0A%20%20%20%20%20%20%20%20for%20m_idx%2C%20_m%20in%20enumerate(range(1%2C%20NM%20%2B%201))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5Bm_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20k_idx%2C%20k%20in%20enumerate(range(1%2C%20NJ%20%2B%201))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20job%20%3D%20job_order%5Bk_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_s%20%3D%20sol_start_df.iloc%5Bk_idx%2C%20m_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_d%20%3D%20dur_data%5B_m%20-%201%5D%5Bjob%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(0%2C%20_d%2C%20left%3D_s%2C%20color%3Djob_colors%5Bjob%20-%201%5D%2C%20edgecolor%3D'white'%2C%20height%3D0.5)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_s%20%2B%20_d%20%2F%202%2C%200%2C%20str(job)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20color%3D'white'%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_yticks(%5B0%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Machine%20%7B_m%7D'%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_xlim(0%2C%20None)%0A%20%20%20%20%20%20%20%20_axes%5B-1%5D.set_xlabel('Time%20units')%0A%20%20%20%20%20%20%20%20_handles%20%3D%20%5Bmpatches.Patch(color%3Djob_colors%5Bj%20-%201%5D%2C%20label%3Df'Job%20%7Bj%7D')%20for%20j%20in%20range(1%2C%20NJ%20%2B%201)%5D%0A%20%20%20%20%20%20%20%20_axes%5B0%5D.legend(handles%3D_handles%2C%20loc%3D'upper%20right'%2C%20ncol%3DNJ%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_axes%5B0%5D.set_title(f'B-2%20Flow%20shop%20-%20total%20waiting%20time%20%7Bobj_val_1%3A.0f%7D')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20flow%20shop%20Gantt%20chart')%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%20B-3%20Job%20shop%20(MIP)%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*Applications%20Book%20Chapter%20B%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60b3jobshop_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb3jobshop_p.mos)%2C%20%5B%60b3jobshop2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb3jobshop2_p.mos)%2C%20%5B%60b3jobshop3_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb3jobshop3_p.mos)%0A%0A%20%20%20%20Three%20types%20of%20wallpaper%20(jobs)%20are%20processed%20on%20three%20machines%20(printing%20colors).%20Each%20job%20visits%20machines%20in%20a%20job-specific%20order.%20Jobs%20that%20share%20a%20machine%20cannot%20overlap%20-%20these%20are%20the%20disjunctive%20constraints.%20All%20three%20formulations%20minimize%20the%20same%20makespan%20(%60finish%60).%0A%0A%20%20%20%20-%20%60b3jobshop_p.mos%60%3A%208%20operations%2C%20precedence%20graph%20%60ARC%60%20and%20disjunctions%20%60DISJ%60%20as%20dynamic%20arrays.%0A%20%20%20%20-%20%60b3jobshop2_p.mos%60%3A%20generic%20formulation%2C%20%60DUR%60%20indexed%20by%20%60(machine%2C%20job)%60%2C%20explicit%20%60NUMT%60%2F%60SEQ%60%2F%60NUMD%60%2F%60DISJ%60%20arrays.%0A%20%20%20%20-%20%60b3jobshop3_p.mos%60%3A%20compact%20formulation%20using%20%60array%20of%20list%60%20for%20job%20sequences%20and%20%60array%20of%20set%60%20for%20disjunctive%20groups.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BJOBS%7D%20%3D%201..8%24%20(operations)%2C%20machines%20implicit%20in%20precedence%2Fdisjunction%20data.%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bstart%7D_j%20%5Cgeq%200%24%20(start%20time%20of%20operation%20%24j%24)%3B%20%24%5Cmathit%7Bfinish%7D%20%5Cgeq%200%24%20(makespan)%3B%20%24y_d%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(disjunction%20direction).%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Cmathit%7Bfinish%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Cmathit%7Bfinish%7D%20%5Cgeq%20%5Cmathit%7Bstart%7D_j%20%2B%20%5Ctext%7BDUR%7D_j%20%5Cquad%20%5Cforall%5C%2C%20j%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_i%20%2B%20%5Ctext%7BDUR%7D_i%20%5Cleq%20%5Cmathit%7Bstart%7D_j%20%5Cquad%20%5Cforall%5C%2C(i%2Cj)%20%5Cin%20%5Ctext%7BARC%7D%20%5Cquad%20%5Ctext%7B(precedence)%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_i%20%2B%20%5Ctext%7BDUR%7D_i%20%5Cleq%20%5Cmathit%7Bstart%7D_j%20%2B%20M%20%5Ccdot%20y_d%20%5Cquad%20%5Cforall%5C%2C(i%2Cj)%20%5Cin%20%5Ctext%7BDISJ%7D%20%5Cquad%20%5Ctext%7B(disjunction)%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_j%20%2B%20%5Ctext%7BDUR%7D_j%20%5Cleq%20%5Cmathit%7Bstart%7D_i%20%2B%20M%20%5Ccdot%20(1-y_d)%20%5Cquad%20%5Cforall%5C%2C(i%2Cj)%20%5Cin%20%5Ctext%7BDISJ%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%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%20Define%20input%20data%20for%20%60b3jobshop_p%60%20and%20%60b3jobshop2_p%60%20in%20Python.%20Both%20models%20are%20passed%20via%20%60DATASOURCE%3D%22moselpy%3A%22%60%20and%20%60input_data%60.%20%60b3jobshop3_p%60%20reads%20from%20a%20%60.dat%60%20file%20-%20see%20the%20note%20below.%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%20b3jobshop_p%3A%208%20operations%2C%20precedence%20graph%20ARC%2C%20disjunctions%20DISJ%0A%20%20%20%20DUR_J%20%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B45%2C20%2C12%2C10%2C17%2C10%2C34%2C28%5D)%7D%0A%20%20%20%20ARC_J%20%20%3D%20%7B(1%2C6)%3A1%2C(4%2C2)%3A1%2C(2%2C7)%3A1%2C(8%2C3)%3A1%2C(3%2C5)%3A1%7D%0A%20%20%20%20DISJ_J%20%3D%20%7B(1%2C2)%3A1%2C(1%2C3)%3A1%2C(2%2C3)%3A1%2C(4%2C5)%3A1%2C(6%2C7)%3A1%2C(6%2C8)%3A1%2C(7%2C8)%3A1%7D%0A%0A%20%20%20%20%23%20b3jobshop2_p%3A%20generic%20formulation%20with%20DUR(machine%2Cjob)%2C%20sequence%20and%20disjunction%20arrays%0A%20%20%20%20DUR2%20%20%3D%20%7B(1%2C1)%3A45%2C(1%2C2)%3A20%2C(1%2C3)%3A12%2C(2%2C2)%3A10%2C(2%2C3)%3A17%2C(3%2C1)%3A10%2C(3%2C2)%3A34%2C(3%2C3)%3A28%7D%0A%20%20%20%20NUMT%20%20%3D%20%7B1%3A2%2C%202%3A3%2C%203%3A3%7D%0A%20%20%20%20SEQ2%20%20%3D%20%7B(1%2C1)%3A1%2C(1%2C2)%3A3%2C(2%2C1)%3A2%2C(2%2C2)%3A1%2C(2%2C3)%3A3%2C(3%2C1)%3A3%2C(3%2C2)%3A1%2C(3%2C3)%3A2%7D%0A%20%20%20%20NUMD%20%20%3D%20%7B1%3A3%2C%202%3A2%2C%203%3A3%7D%0A%20%20%20%20DISJ2%20%3D%20%7B(1%2C1)%3A1%2C(1%2C2)%3A2%2C(1%2C3)%3A3%2C(2%2C1)%3A2%2C(2%2C2)%3A3%2C(3%2C1)%3A1%2C(3%2C2)%3A2%2C(3%2C3)%3A3%7D%0A%20%20%20%20return%20ARC_J%2C%20DISJ2%2C%20DISJ_J%2C%20DUR2%2C%20DUR_J%2C%20NUMD%2C%20NUMT%2C%20SEQ2%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%20Run%20all%20three%20formulations%20and%20compare%20their%20makespan%20values.%20Since%20these%20are%20equivalent%20models%20for%20the%20same%20problem%2C%20they%20must%20all%20return%20the%20same%20optimal%20objective.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ARC_J%2C%0A%20%20%20%20DATA%2C%0A%20%20%20%20DISJ2%2C%0A%20%20%20%20DISJ_J%2C%0A%20%20%20%20DUR2%2C%0A%20%20%20%20DUR_J%2C%0A%20%20%20%20MODELS%2C%0A%20%20%20%20NUMD%2C%0A%20%20%20%20NUMT%2C%0A%20%20%20%20SEQ2%2C%0A%20%20%20%20mp%2C%0A%20%20%20%20pd%2C%0A)%3A%0A%20%20%20%20outputs_b3%20%3D%20%7B%7D%0A%20%20%20%20results_b3%20%3D%20%5B%5D%0A%20%20%20%20_model%2C%20out1%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fb3jobshop_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DUR'%3A%20DUR_J%2C%20'ARC'%3A%20ARC_J%2C%20'DISJ'%3A%20DISJ_J%7D)%0A%20%20%20%20%23%20b3jobshop_p%20-%20data%20from%20Python%0A%20%20%20%20outputs_b3%5B'b3jobshop_p'%5D%20%3D%20out1%0A%20%20%20%20results_b3.append(%7B'model'%3A%20'b3jobshop_p'%2C%20'makespan'%3A%20out1%5B'obj_val'%5D%7D)%0A%20%20%20%20_model%2C%20_out2%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fb3jobshop2_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DUR'%3A%20DUR2%2C%20'NUMT'%3A%20NUMT%2C%20'SEQ'%3A%20SEQ2%2C%20'NUMD'%3A%20NUMD%2C%20'DISJ'%3A%20DISJ2%7D)%0A%20%20%20%20outputs_b3%5B'b3jobshop2_p'%5D%20%3D%20_out2%0A%20%20%20%20results_b3.append(%7B'model'%3A%20'b3jobshop2_p'%2C%20'makespan'%3A%20_out2%5B'obj_val'%5D%7D)%0A%20%20%20%20_model%2C%20out3%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fb3jobshop3_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20f'%7BDATA%7D%2Fb3jobshop3_p.dat'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20outputs_b3%5B'b3jobshop3_p'%5D%20%3D%20out3%0A%20%20%20%20results_b3.append(%7B'model'%3A%20'b3jobshop3_p'%2C%20'makespan'%3A%20out3%5B'obj_val'%5D%7D)%0A%20%20%20%20%23%20b3jobshop2_p%20-%20data%20from%20Python%0A%20%20%20%20df_b3%20%3D%20pd.DataFrame(results_b3).set_index('model')%0A%20%20%20%20print('Job%20shop%20formulation%20comparison%3A')%0A%20%20%20%20%23%20b3jobshop3_p%20-%20reads%20from%20.dat%20file%20because%20SEQ%3A%20array%20of%20list%20of%20integer%20and%0A%20%20%20%20%23%20DISJ%3A%20array%20of%20set%20of%20integer%20are%20not%20supported%20by%20the%20moselpy%3A%20driver%20for%20Python%20input%0A%20%20%20%20print(df_b3.to_string())%0A%20%20%20%20return%20(outputs_b3%2C)%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%20Gantt%20chart%20built%20from%20%60b3jobshop_p%60%20results.%20The%208%20operations%20are%20grouped%20by%20paper%20type%20(ops%201-2%20for%20paper%201%2C%203-5%20for%20paper%202%2C%206-8%20for%20paper%203).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20mpatches%2C%20outputs_b3%2C%20plt)%3A%0A%20%20%20%20if%20'obj_val'%20in%20outputs_b3.get('b3jobshop_p'%2C%20%7B%7D)%3A%0A%20%20%20%20%20%20%20%20sol_start_b3%20%3D%20outputs_b3%5B'b3jobshop_p'%5D%5B'sol_start'%5D%20%20%23%20dict%3A%20job%20(1..8)%20-%3E%20start%20time%0A%20%20%20%20%20%20%20%20makespan_b3%20%3D%20outputs_b3%5B'b3jobshop_p'%5D%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20DUR_b3%20%3D%20%5B45%2C%2020%2C%2012%2C%2010%2C%2017%2C%2010%2C%2034%2C%2028%5D%0A%20%20%20%20%20%20%20%20job_paper%20%3D%20%5B1%2C%201%2C%202%2C%202%2C%202%2C%203%2C%203%2C%203%5D%0A%20%20%20%20%20%20%20%20paper_colors%20%3D%20%5B'%231E88E5'%2C%20'%23FB8C00'%2C%20'%2343A047'%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(12%2C%204))%0A%20%20%20%20%20%20%20%20for%20j_idx%2C%20_j%20in%20enumerate(range(1%2C%209))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_s%20%3D%20sol_start_b3%5B_j%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_d%20%3D%20DUR_b3%5Bj_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20paper%20%3D%20job_paper%5Bj_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(j_idx%2C%20_d%2C%20left%3D_s%2C%20color%3Dpaper_colors%5Bpaper%20-%201%5D%2C%20edgecolor%3D'white'%2C%20height%3D0.7)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_s%20%2B%20_d%20%2F%202%2C%20j_idx%2C%20str(_j)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D9%2C%20color%3D'white'%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20_ax.set_yticks(range(8))%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Op%20%7Bj%7D'%20for%20j%20in%20range(1%2C%209)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Time%20units')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'B-3%20Job%20shop%20-%20makespan%20%7Bmakespan_b3%3A.0f%7D')%0A%20%20%20%20%20%20%20%20_handles%20%3D%20%5Bmpatches.Patch(color%3Dpaper_colors%5Bp%20-%201%5D%2C%20label%3Df'Paper%20%7Bp%7D')%20for%20p%20in%20range(1%2C%204)%5D%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D_handles%2C%20loc%3D'lower%20right')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20for%20b3jobshop_p%20-%20skipping%20Gantt%20chart')%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%20B-4%20Sequencing%20(MIP)%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*Applications%20Book%20Chapter%20B%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60b4seq_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb4seq_p.mos)%0A%0A%20%20%20%20Seven%20jobs%20must%20be%20sequenced%20on%20a%20single%20bottleneck%20machine.%20Each%20job%20has%20a%20release%20date%20%60REL%60%2C%20duration%20%60DUR%60%2C%20and%20due%20date%20%60DUE%60.%20The%20model%20is%20parameterised%20by%20%60OBJ%60%20to%20select%20between%20three%20objectives%3A%0A%0A%20%20%20%20-%20%60OBJ%3D1%60%3A%20minimize%20makespan%20(total%20schedule%20duration).%0A%20%20%20%20-%20%60OBJ%3D2%60%3A%20minimize%20total%20completion%20time%20(average%20completion%20is%20proportional).%0A%20%20%20%20-%20%60OBJ%3D3%60%3A%20minimize%20total%20tardiness%20(sum%20of%20lateness%20above%20zero).%0A%0A%20%20%20%20Binary%20variable%20%24%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%24%20if%20job%20%24j%24%20is%20placed%20at%20position%20%24k%24.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BJOBS%7D%20%3D%201..7%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Bstart%7D_k%20%5Cgeq%200%24%3B%20%24%5Cmathit%7Bcomp%7D_k%20%5Cgeq%200%24%3B%20%24%5Cmathit%7Blate%7D_k%20%5Cgeq%200%24%3B%20%24%5Cmathit%7Bfinish%7D%20%5Cgeq%200%24.%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Cbegin%7Bcases%7D%20%5Cmathit%7Bfinish%7D%20%26%20%5Ctext%7Bif%20OBJ%7D%3D1%20%5C%5C%20%5Csum_k%20%5Cmathit%7Bcomp%7D_k%20%26%20%5Ctext%7Bif%20OBJ%7D%3D2%20%5C%5C%20%5Csum_k%20%5Cmathit%7Blate%7D_k%20%26%20%5Ctext%7Bif%20OBJ%7D%3D3%20%5Cend%7Bcases%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_j%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%2C%5Cquad%20%5Csum_k%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%3D%201%20%5Cquad%20%5Ctext%7B(assignment)%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_%7Bk%2B1%7D%20%5Cgeq%20%5Cmathit%7Bstart%7D_k%20%2B%20%5Csum_j%20%5Ctext%7BDUR%7D_j%20%5Ccdot%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%5Cquad%20%5Ctext%7B(sequencing)%7D%20%5C%5C%0A%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%26%20%5Cmathit%7Bstart%7D_k%20%5Cgeq%20%5Csum_j%20%5Ctext%7BREL%7D_j%20%5Ccdot%20%5Cmathit%7Brank%7D_%7Bj%2Ck%7D%20%5Cquad%20%5Ctext%7B(release%20dates)%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%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%20Define%20job%20data%20in%20Python%3A%20release%20dates%20%60REL%60%2C%20durations%20%60DUR%60%2C%20and%20due%20dates%20%60DUE%60%2C%20all%201-indexed%20dicts.%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%20REL%20%20%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B2%2C5%2C4%2C0%2C0%2C8%2C9%5D)%7D%0A%20%20%20%20DUR_S%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B5%2C6%2C8%2C4%2C2%2C4%2C2%5D)%7D%0A%20%20%20%20DUE%20%20%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B10%2C21%2C15%2C10%2C5%2C15%2C22%5D)%7D%0A%20%20%20%20return%20DUE%2C%20DUR_S%2C%20REL%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%20The%20model%20is%20compiled%20and%20loaded%20once.%20%60run()%60%20is%20then%20called%20three%20times%20-%20each%20time%20with%20a%20different%20%60OBJ%60%20value%20via%20%60exec_params%60.%20This%20is%20the%20iterative%20solving%20pattern%3A%20one%20compiled%20model%2C%20multiple%20parametric%20runs%20with%20varying%20parameters.%20The%20same%20%60input_data%60%20dict%20is%20reused%20across%20all%20three%20runs.%20Results%20are%20collected%20into%20a%20%60pd.DataFrame%60%20for%20comparison.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DUE%2C%20DUR_S%2C%20MODELS%2C%20REL%2C%20mp%2C%20pd)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb4seq_p.mos'%2C%20f'%7BMODELS%7D%2Fb4seq_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fb4seq_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20obj_labels%20%3D%20%7B1%3A%20'Makespan'%2C%202%3A%20'Total%20completion'%2C%203%3A%20'Total%20tardiness'%7D%0A%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20for%20obj_id%20in%20%5B1%2C%202%2C%203%5D%3A%0A%20%20%20%20%20%20%20%20out%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'OBJ'%3A%20obj_id%7D%2C%20input_data%3D%7B'REL'%3A%20REL%2C%20'DUR'%3A%20DUR_S%2C%20'DUE'%3A%20DUE%7D)%0A%20%20%20%20%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_sol_rank%20%3D%20out%5B'sol_rank'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20sol_comp%20%3D%20out%5B'sol_comp'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20sol_start_1%20%3D%20out%5B'sol_start'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20makespan%20%3D%20out%5B'makespan'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20job_seq%20%3D%20%5Bround(sum((j%20*%20_sol_rank.get((j%2C%20k)%2C%200)%20for%20j%20in%20range(1%2C%208))))%20for%20k%20in%20range(1%2C%208)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20rows.append(%7B'Objective'%3A%20obj_labels%5Bobj_id%5D%2C%20'Obj%20value'%3A%20out%5B'obj_val'%5D%2C%20'Makespan'%3A%20makespan%2C%20'Total%20completion'%3A%20sum((sol_comp.get(k%2C%200)%20for%20k%20in%20range(1%2C%208)))%2C%20'Job%20sequence'%3A%20str(job_seq)%7D)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20for%20OBJ%3D%7Bobj_id%7D%20(%7B_model.prob_status%7D)')%0A%20%20%20%20df_b4%20%3D%20pd.DataFrame(rows).set_index('Objective')%0A%20%20%20%20print('B-4%20Sequencing%20-%20results%20by%20objective%3A')%0A%20%20%20%20print(df_b4.to_string())%0A%20%20%20%20return%20df_b4%2C%20obj_labels%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%20Grouped%20bar%20chart%20comparing%20the%20three%20objective%20values%20across%20different%20criteria.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(df_b4%2C%20mo%2C%20obj_labels%2C%20plt)%3A%0A%20%20%20%20if%20not%20df_b4.empty%3A%0A%20%20%20%20%20%20%20%20metrics%20%3D%20%5B'Makespan'%2C%20'Total%20completion'%5D%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%5B'%231E88E5'%2C%20'%23FB8C00'%2C%20'%2343A047'%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(10%2C%204))%0A%20%20%20%20%20%20%20%20for%20ax_idx%2C%20metric%20in%20enumerate(metrics)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5Bax_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20vals%20%3D%20%5Bdf_b4.loc%5Bobj_labels%5Bi%5D%2C%20metric%5D%20for%20i%20in%20range(1%2C%204)%20if%20obj_labels%5Bi%5D%20in%20df_b4.index%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20labels%20%3D%20%5Bobj_labels%5Bi%5D%20for%20i%20in%20range(1%2C%204)%20if%20obj_labels%5Bi%5D%20in%20df_b4.index%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(labels%2C%20vals%2C%20color%3D_colors%5B%3Alen(vals)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_title(metric)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_ylabel('Time%20units')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.tick_params(axis%3D'x'%2C%20rotation%3D20)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20bar%2C%20val%20in%20zip(_bars%2C%20vals)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(bar.get_x()%20%2B%20bar.get_width()%20%2F%202%2C%20bar.get_height()%20%2B%200.5%2C%20f'%7Bval%3A.0f%7D'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20plt.suptitle('B-4%20Sequencing%20-%20objective%20comparison')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20results%20available%20-%20skipping%20objective%20comparison%20chart')%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%20B-5%20Paint%20production%20(MIP)%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*Applications%20Book%20Chapter%20B%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60b5paint_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb5paint_p.mos)%0A%0A%20%20%20%20Five%20paint%20batches%20must%20be%20produced%20in%20a%20cycle%20each%20week.%20The%20blender%20must%20be%20cleaned%20between%20batches%3B%20cleaning%20time%20depends%20on%20which%20batch%20follows%20which.%20The%20problem%20finds%20the%20optimal%20cyclic%20sequence%20(a%20Hamiltonian%20circuit%20%2F%20TSP%20on%205%20nodes)%20to%20minimize%20total%20cycle%20time.%0A%0A%20%20%20%20Binary%20variable%20%24%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D%20%3D%201%24%20if%20batch%20%24i%24%20is%20immediately%20followed%20by%20batch%20%24j%24%20in%20the%20cycle.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BJOBS%7D%20%3D%201..5%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20for%20%24i%20%5Cneq%20j%24%3B%20%24y_j%20%5Cgeq%200%24%20(subtour%20elimination).%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bi%20%5Cneq%20j%7D%20(%5Ctext%7BDUR%7D_i%20%2B%20%5Ctext%7BCLEAN%7D_%7Bi%2Cj%7D)%20%5Ccdot%20%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bj%20%5Cneq%20i%7D%20%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20i%20%5Cquad%20%5Ctext%7B(one%20successor%20per%20batch)%7D%20%5C%5C%0A%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%26%20%5Csum_%7Bi%20%5Cneq%20j%7D%20%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20j%20%5Cquad%20%5Ctext%7B(one%20predecessor%20per%20batch)%7D%20%5C%5C%0A%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%26%20y_j%20%5Cgeq%20y_i%20%2B%201%20-%205(1%20-%20%5Cmathit%7Bsucc%7D_%7Bi%2Cj%7D)%20%5Cquad%20%5Cforall%5C%2C%20i%2C%5C%2C%20j%20%5Cin%202..5%2C%5C%2C%20i%20%5Cneq%20j%20%5Cquad%20%5Ctext%7B(subtour%20elimination)%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20This%20section%20demonstrates%20how%20to%20capture%20Mosel's%20%60writeln%60%20output%20to%20a%20Python%20string%20using%20%60StringIO%60%20and%20the%20%60moselpy%3Aout%60%20driver%20-%20useful%20when%20the%20model%20produces%20a%20formatted%20text%20report%20that%20you%20want%20to%20display%20or%20parse%20in%20Python.%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%20Define%20batch%20data%20in%20Python%3A%20durations%20%60DUR%60%20and%20cleaning%20times%20%60CLEAN%60%20(a%202D%20dict%20keyed%20by%20%60(i%2Cj)%60%20pairs).%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%20NB%20%3D%205%0A%20%20%20%20DUR_P%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B40%2C35%2C45%2C32%2C50%5D)%7D%0A%20%20%20%20CLEAN_raw%20%3D%20%5B%5B0%2C11%2C7%2C13%2C11%5D%2C%5B5%2C0%2C13%2C15%2C15%5D%2C%5B13%2C15%2C0%2C23%2C11%5D%2C%5B9%2C13%2C5%2C0%2C3%5D%2C%5B3%2C7%2C7%2C7%2C0%5D%5D%0A%20%20%20%20CLEAN%20%3D%20%7B(i%2B1%2Cj%2B1)%3A%20CLEAN_raw%5Bi%5D%5Bj%5D%20for%20i%20in%20range(NB)%20for%20j%20in%20range(NB)%7D%0A%20%20%20%20return%20CLEAN%2C%20CLEAN_raw%2C%20DUR_P%2C%20NB%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%20Heatmap%20of%20the%20cleaning%20times%20matrix%3A%20each%20cell%20shows%20how%20long%20the%20blender%20needs%20cleaning%20when%20switching%20from%20one%20paint%20batch%20(row)%20to%20another%20(column).%20Zero%20on%20the%20diagonal%20means%20no%20cleaning%20when%20a%20batch%20immediately%20follows%20itself.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CLEAN_raw%2C%20NB%2C%20mo%2C%20plt)%3A%0A%20%20%20%20batch_labels%20%3D%20%5Bf'Batch%20%7Bi%7D'%20for%20i%20in%20range(1%2C%20NB%20%2B%201)%5D%0A%20%20%20%20clean_matrix%20%3D%20%5B%5BCLEAN_raw%5Bi%5D%5Bj%5D%20for%20j%20in%20range(NB)%5D%20for%20i%20in%20range(NB)%5D%0A%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(6%2C%205))%0A%20%20%20%20im%20%3D%20_ax.imshow(clean_matrix%2C%20cmap%3D'YlOrRd'%2C%20aspect%3D'auto')%0A%20%20%20%20plt.colorbar(im%2C%20ax%3D_ax%2C%20label%3D'Cleaning%20time%20(min)')%0A%20%20%20%20_ax.set_xticks(range(NB))%0A%20%20%20%20_ax.set_xticklabels(%5Bf'To%5Cn%7Bb%7D'%20for%20b%20in%20batch_labels%5D%2C%20fontsize%3D8)%0A%20%20%20%20_ax.set_yticks(range(NB))%0A%20%20%20%20_ax.set_yticklabels(%5Bf'From%20%7Bb%7D'%20for%20b%20in%20batch_labels%5D%2C%20fontsize%3D8)%0A%20%20%20%20for%20_i%20in%20range(NB)%3A%0A%20%20%20%20%20%20%20%20for%20_j%20in%20range(NB)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_j%2C%20_i%2C%20str(clean_matrix%5B_i%5D%5B_j%5D)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D9%2C%20color%3D'white'%20if%20clean_matrix%5B_i%5D%5B_j%5D%20%3E%2015%20else%20'black')%0A%20%20%20%20_ax.set_title('B-5%20Paint%20-%20cleaning%20times%20between%20batches%20(min)')%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20mo.output.append(plt.gcf())%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%20The%20%60moselpy%3Aout%60%20stream%20driver%20captures%20everything%20Mosel%20writes%20to%20the%20default%20output%20stream%20into%20the%20bound%20Python%20object%20-%20here%20a%20%60StringIO%60%20buffer.%20Set%20the%20model's%20output%20stream%20to%20%60%22moselpy%3Aout%22%60%20and%20bind%20the%20%60%22out%22%60%20symbol%20to%20a%20%60StringIO%60%20instance%20before%20calling%20%60run()%60.%20After%20the%20run%2C%20%60output_buf.getvalue()%60%20contains%20all%20the%20text%20the%20model%20would%20have%20printed%20to%20the%20terminal.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CLEAN%2C%20DUR_P%2C%20MODELS%2C%20StringIO%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb5paint_p.mos'%2C%20f'%7BMODELS%7D%2Fb5paint_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fb5paint_p.bim')%0A%20%20%20%20output_buf%20%3D%20StringIO()%0A%20%20%20%20_model.symbols%5B'out'%5D%20%3D%20output_buf%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'moselpy%3Aout')%0A%20%20%20%20output_2%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'DUR'%3A%20DUR_P%2C%20'CLEAN'%3A%20CLEAN%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_2%20%3D%20output_2%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_succ%20%3D%20output_2%5B'sol_succ'%5D%0A%20%20%20%20%20%20%20%20captured%20%3D%20output_buf.getvalue()%0A%20%20%20%20%20%20%20%20print(f'Minimum%20cycle%20time%3A%20%7Bobj_val_2%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print('Mosel%20output%20captured%20via%20moselpy%3Aout%3A')%0A%20%20%20%20%20%20%20%20print(captured%20if%20captured.strip()%20else%20'(no%20output%20captured%20-%20stream%20was%20null)')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20return%20obj_val_2%2C%20sol_succ%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%20Reconstruct%20the%20production%20sequence%20from%20the%20%60sol_succ%60%20successor%20matrix%20and%20display%20it%20as%20a%20circular%20ordering%20diagram.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20np%2C%20obj_val_2%2C%20plt%2C%20sol_succ)%3A%0A%20%20%20%20if%20'sol_succ'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20NJ_viz%20%3D%205%0A%20%20%20%20%20%20%20%20_DUR_viz%20%3D%20%5B40%2C%2035%2C%2045%2C%2032%2C%2050%5D%0A%20%20%20%20%20%20%20%20CLEAN_viz%20%3D%20%5B%5B0%2C%2011%2C%207%2C%2013%2C%2011%5D%2C%20%5B5%2C%200%2C%2013%2C%2015%2C%2015%5D%2C%20%5B13%2C%2015%2C%200%2C%2023%2C%2011%5D%2C%20%5B9%2C%2013%2C%205%2C%200%2C%203%5D%2C%20%5B3%2C%207%2C%207%2C%207%2C%200%5D%5D%0A%20%20%20%20%20%20%20%20batch_names%20%3D%20%5B'Batch%201%20(40min)'%2C%20'Batch%202%20(35min)'%2C%20'Batch%203%20(45min)'%2C%20'Batch%204%20(32min)'%2C%20'Batch%205%20(50min)'%5D%0A%20%20%20%20%20%20%20%20sequence%20%3D%20%5B1%5D%0A%20%20%20%20%20%20%20%20current%20%3D%201%0A%20%20%20%20%20%20%20%20for%20_%20in%20range(NJ_viz%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nxt%20%3D%20round(sum((j%20*%20sol_succ.get((current%2C%20j)%2C%200)%20for%20j%20in%20range(1%2C%20NJ_viz%20%2B%201)%20if%20j%20!%3D%20current)))%0A%20%20%20%20%20%20%20%20%20%20%20%20sequence.append(nxt)%0A%20%20%20%20%20%20%20%20%20%20%20%20current%20%3D%20nxt%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(7%2C%206))%0A%20%20%20%20%20%20%20%20theta%20%3D%20np.linspace(0%2C%202%20*%20np.pi%2C%20NJ_viz%2C%20endpoint%3DFalse)%0A%20%20%20%20%20%20%20%20xs%20%3D%20np.cos(theta)%0A%20%20%20%20%20%20%20%20ys%20%3D%20np.sin(theta)%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%5B'%231E88E5'%2C%20'%23FB8C00'%2C%20'%2343A047'%2C%20'%23E53935'%2C%20'%238E24AA'%5D%0A%20%20%20%20%20%20%20%20for%20idx%2C%20batch%20in%20enumerate(sequence)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(xs%5Bidx%5D%2C%20ys%5Bidx%5D%2C%20s%3D2000%2C%20color%3D_colors%5Bbatch%20-%201%5D%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(xs%5Bidx%5D%20*%201.3%2C%20ys%5Bidx%5D%20*%201.3%2C%20batch_names%5Bbatch%20-%201%5D%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(xs%5Bidx%5D%2C%20ys%5Bidx%5D%2C%20str(idx%20%2B%201)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D11%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20for%20idx%20in%20range(NJ_viz)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20src%20%3D%20sequence%5Bidx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20dst%20%3D%20sequence%5B(idx%20%2B%201)%20%25%20NJ_viz%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20clean_t%20%3D%20CLEAN_viz%5Bsrc%20-%201%5D%5Bdst%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(xs%5B(idx%20%2B%201)%20%25%20NJ_viz%5D%2C%20ys%5B(idx%20%2B%201)%20%25%20NJ_viz%5D)%2C%20xytext%3D(xs%5Bidx%5D%2C%20ys%5Bidx%5D)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3D'gray'%2C%20lw%3D1.5))%0A%20%20%20%20%20%20%20%20%20%20%20%20mid_x%20%3D%20(xs%5Bidx%5D%20%2B%20xs%5B(idx%20%2B%201)%20%25%20NJ_viz%5D)%20%2F%202%20*%200.85%0A%20%20%20%20%20%20%20%20%20%20%20%20mid_y%20%3D%20(ys%5Bidx%5D%20%2B%20ys%5B(idx%20%2B%201)%20%25%20NJ_viz%5D)%20%2F%202%20*%200.85%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(mid_x%2C%20mid_y%2C%20f'%2B%7Bclean_t%7D'%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D7%2C%20color%3D'gray')%0A%20%20%20%20%20%20%20%20_ax.set_xlim(-1.7%2C%201.7)%0A%20%20%20%20%20%20%20%20_ax.set_ylim(-1.7%2C%201.7)%0A%20%20%20%20%20%20%20%20_ax.axis('off')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'B-5%20Paint%20production%20schedule%20-%20cycle%20time%20%7Bobj_val_2%3A.0f%7D%20min%5CnOrder%3A%20%7Bsequence%7D')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20production%20sequence%20diagram')%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%20B-6%20Assembly%20line%20balancing%20(MIP)%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*Applications%20Book%20Chapter%20B%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60b6linebal_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fscheduling_sequencing%2Fb6linebal_p.mos)%0A%0A%20%20%20%20An%20electronics%20factory%20assembles%20amplifiers%20across%204%20workstations.%20Each%20amplifier%20requires%2012%20tasks%20with%20precedence%20relations%20(defined%20by%20%60ARC%60).%20Each%20task%20must%20be%20assigned%20to%20exactly%20one%20workstation%3B%20the%20cycle%20time%20is%20the%20maximum%20workload%20across%20stations.%20The%20goal%20is%20to%20minimize%20this%20cycle%20time.%0A%0A%20%20%20%20Binary%20variable%20%24%5Cmathit%7Bprocess%7D_%7Bi%2Cm%7D%20%3D%201%24%20if%20task%20%24i%24%20is%20assigned%20to%20workstation%20%24m%24.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BTASKS%7D%20%3D%201..12%24%2C%20%24%5Ctext%7BMACH%7D%20%3D%201..4%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bprocess%7D_%7Bi%2Cm%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Bcycle%7D%20%5Cgeq%200%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Cmathit%7Bcycle%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_m%20%5Cmathit%7Bprocess%7D_%7Bi%2Cm%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20i%20%5Cquad%20%5Ctext%7B(one%20station%20per%20task)%7D%20%5C%5C%0A%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%26%20%5Csum_m%20m%20%5Ccdot%20%5Cmathit%7Bprocess%7D_%7B%5Ctext%7BARC%7D(a%2C1)%2Cm%7D%20%5Cleq%20%5Csum_m%20m%20%5Ccdot%20%5Cmathit%7Bprocess%7D_%7B%5Ctext%7BARC%7D(a%2C2)%2Cm%7D%20%5Cquad%20%5Cforall%5C%2C%20a%20%5Cquad%20%5Ctext%7B(precedence)%7D%20%5C%5C%0A%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%26%20%5Csum_i%20%5Ctext%7BDUR%7D_i%20%5Ccdot%20%5Cmathit%7Bprocess%7D_%7Bi%2Cm%7D%20%5Cleq%20%5Cmathit%7Bcycle%7D%20%5Cquad%20%5Cforall%5C%2C%20m%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%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%20Define%20task%20data%20in%20Python%3A%20durations%20%60DUR%60%20and%20precedence%20arcs%20%60ARC%60.%20%60ARC%60%20is%20indexed%20by%20a%201-based%20arc%20number%20with%20column%20indices%201%20and%202%20for%20the%20predecessor%20and%20successor%20task%20respectively.%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%20NARC%20%3D%2016%0A%20%20%20%20ARC_L%20%3D%20%7B(1%2C1)%3A1%2C(1%2C2)%3A2%2C(2%2C1)%3A1%2C(2%2C2)%3A3%2C(3%2C1)%3A2%2C(3%2C2)%3A4%2C(4%2C1)%3A2%2C(4%2C2)%3A5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20(5%2C1)%3A2%2C(5%2C2)%3A6%2C(6%2C1)%3A3%2C(6%2C2)%3A6%2C(7%2C1)%3A3%2C(7%2C2)%3A7%2C(8%2C1)%3A4%2C(8%2C2)%3A9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20(9%2C1)%3A5%2C(9%2C2)%3A9%2C(10%2C1)%3A6%2C(10%2C2)%3A8%2C(11%2C1)%3A7%2C(11%2C2)%3A11%2C(12%2C1)%3A8%2C(12%2C2)%3A9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20(13%2C1)%3A8%2C(13%2C2)%3A10%2C(14%2C1)%3A9%2C(14%2C2)%3A12%2C(15%2C1)%3A10%2C(15%2C2)%3A12%2C(16%2C1)%3A11%2C(16%2C2)%3A10%7D%0A%20%20%20%20DUR_L%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B3%2C6%2C7%2C6%2C4%2C8%2C9%2C11%2C2%2C13%2C4%2C3%5D)%7D%0A%20%20%20%20return%20ARC_L%2C%20DUR_L%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%20Compile%20%60b6linebal_p.mos%60%20with%20the%20%60%22g%22%60%20flag%20so%20%60find_identifier%60%20can%20access%20all%20model%20symbols.%20After%20the%20run%2C%20%60find_identifier(%22sol_process%22%2C%20use_pandas%3DTrue)%60%20returns%20a%20MultiIndex%20Series%20keyed%20by%20%60(task%2C%20machine)%60.%20Calling%20%60.unstack(level%3D1)%60%20pivots%20the%20machine%20index%20into%20columns%2C%20giving%20a%20task-by-workstation%20assignment%20DataFrame.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ARC_L%2C%20DUR_L%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fb6linebal_p.mos'%2C%20f'%7BMODELS%7D%2Fb6linebal_p.bim'%2C%20'g')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fb6linebal_p.bim')%0A%20%20%20%20output_3%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'ARC'%3A%20ARC_L%2C%20'DUR'%3A%20DUR_L%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_3%20%3D%20output_3%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20process_df%20%3D%20_model.find_identifier('sol_process'%2C%20use_pandas%3DTrue).unstack(level%3D1)%0A%20%20%20%20%20%20%20%20process_df.index.name%20%3D%20'Task'%0A%20%20%20%20%20%20%20%20process_df.columns.name%20%3D%20'Workstation'%0A%20%20%20%20%20%20%20%20print(f'Minimum%20cycle%20time%3A%20%7Bobj_val_3%3A.0f%7D%20time%20units')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20_DUR_b6%20%3D%20%5B3%2C%206%2C%207%2C%206%2C%204%2C%208%2C%209%2C%2011%2C%202%2C%2013%2C%204%2C%203%5D%0A%20%20%20%20%20%20%20%20print(f'%7B'Workstation'%3A%3C14%7D%20%7B'Tasks'%3A%3C25%7D%20%7B'Total%20duration'%7D')%0A%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_tasks%20%3D%20%5Bi%20for%20i%20in%20range(1%2C%2013)%20if%20process_df.at%5Bi%2C%20_m%5D%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20dur%20%3D%20sum((_DUR_b6%5Bi%20-%201%5D%20for%20i%20in%20_tasks))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Station%20%7B_m%3A%3C5%7D%20%20%20%7Bstr(_tasks)%3A%3C25%7D%20%7Bdur%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20return%20obj_val_3%2C%20process_df%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%20Stacked%20horizontal%20bar%20chart%3A%20each%20bar%20is%20a%20workstation%3B%20segments%20are%20tasks%20(width%20%3D%20duration).%20The%20total%20bar%20length%20equals%20the%20workstation%20workload.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20obj_val_3%2C%20plt%2C%20process_df)%3A%0A%20%20%20%20if%20'process_df'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_DUR_b6%20%3D%20%5B3%2C%206%2C%207%2C%206%2C%204%2C%208%2C%209%2C%2011%2C%202%2C%2013%2C%204%2C%203%5D%0A%20%20%20%20%20%20%20%20task_colors%20%3D%20plt.cm.tab20.colors%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(12%2C%204))%0A%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_tasks%20%3D%20sorted(%5Bi%20for%20i%20in%20range(1%2C%2013)%20if%20process_df.at%5Bi%2C%20_m%5D%20%3E%200.5%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%200%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20task%20in%20_tasks%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_d%20%3D%20_DUR_b6%5Btask%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(_m%20-%201%2C%20_d%2C%20left%3Dleft%2C%20color%3Dtask_colors%5B(task%20-%201)%20%25%2020%5D%2C%20edgecolor%3D'white'%2C%20height%3D0.6)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(left%20%2B%20_d%20%2F%202%2C%20_m%20-%201%2C%20str(task)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D9%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20left%20%2B%20_d%0A%20%20%20%20%20%20%20%20_ax.axvline(obj_val_3%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'Cycle%20time%20%3D%20%7Bobj_val_3%3A.0f%7D')%0A%20%20%20%20%20%20%20%20_ax.set_yticks(range(4))%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Workstation%20%7Bm%7D'%20for%20m%20in%20range(1%2C%205)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Time%20units')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'B-6%20Assembly%20line%20balancing%20-%20cycle%20time%20%7Bobj_val_3%3A.0f%7D')%0A%20%20%20%20%20%20%20%20_ax.legend(loc%3D'lower%20right')%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20workstation%20assignment%20chart')%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
4daa8eda4b4d1002b08c08af14408fe8