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%20Loading%20and%20Cutting%22%0A%23%20description%20%3D%20%22Wagon%2C%20ship%2C%20tanks%2C%20bin%20packing%2C%20cutting%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-loadingcutting.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%20D%3A%20Loading%20and%20Cutting%20Stock**%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_loading_cutting_stock.py***%20-%20Loading%20and%20cutting%20stock%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20wagon%20load%20balancing%2C%20ship%20loading%2C%20tank%20filling%2C%20bin%20packing%2C%20and%20cutting%20stock%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%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%22loading_cutting_stock%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22loading_cutting_stock%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%20mp%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%20Loading%20and%20cutting%20stock%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%20MIP%20examples%20from%20Chapter%20D%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20range%20from%20bin-packing%20and%20assignment%20problems%20(wagon%20loading%2C%20tank%20filling%2C%20file%20backup)%20to%20cutting-stock%20problems%20(sheet%20metal%2C%20steel%20bars).%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Floading_cutting_stock%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Floading_cutting_stock%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Floading_cutting_stock)%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_4)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**LP%2FMIP%20file%20export**%3A%20D-1%20calls%20%60model.export_problem(file_name%3D...)%60%20after%20solving%20to%20write%20a%20human-readable%20LP%20file%20for%20inspection%20or%20re-use.%0A%20%20%20%20-%20**Warm-start%20via%20%60addmipsol%60**%3A%20D-1%20(variant%202)%20feeds%20a%20Python-computed%20LPT%20heuristic%20solution%20into%20the%20MIP%20solver%20as%20an%20initial%20integer%20solution%20via%20%60addmipsol%60.%0A%20%20%20%20-%20**Parametric%20variant%20selection**%3A%20D-2%20accepts%20a%20%60VARIANT%60%20parameter%20selecting%20one%20of%20three%20progressively%20constrained%20LP%2FMIP%20formulations%3B%20all%20three%20run%20from%20Python%20in%20the%20compile-load-run%20sequence.%0A%20%20%20%20-%20**%60pd.DataFrame%60%20for%202D%20input**%3A%20D-5%20receives%20the%204x16%20cutting%20matrix%20as%20a%20DataFrame%3B%20MoselPy%20maps%20row%20and%20column%20indices%20to%20the%20Mosel%202D%20array.%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%20D-1%20Wagon%20load%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%20D%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60d1wagon_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd1wagon_p.mos)%2C%20%5B%60d1wagon2_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd1wagon2_p.mos)%0A%0A%20%20%20%20Sixteen%20boxes%20of%20varying%20weights%20must%20be%20loaded%20onto%20three%20railway%20wagons.%20Each%20wagon%20has%20a%20weight%20capacity%20of%20100%20quintals.%20The%20goal%20is%20to%20assign%20boxes%20to%20wagons%20so%20that%20the%20heaviest%20wagon%20load%20is%20minimized%20(a%20minimax%20objective).%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BBOXES%7D%20%3D%201..16%24%2C%20%24%5Ctext%7BWAGONS%7D%20%3D%201..3%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bifload%7D_%7Bb%2Cw%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20box%20%24b%24%20goes%20to%20wagon%20%24w%24)%3B%20%24%5Cmathit%7Bmaxweight%7D%20%5Cgeq%200%24%20(heaviest%20load)%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%7Bmaxweight%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bw%7D%20%5Cmathit%7Bifload%7D_%7Bb%2Cw%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20b%20%5Cquad%20%5Ctext%7B(each%20box%20in%20one%20wagon)%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_%7Bb%7D%20%5Ctext%7BWEIGHT%7D_b%20%5Ccdot%20%5Cmathit%7Bifload%7D_%7Bb%2Cw%7D%20%5Cleq%20%5Cmathit%7Bmaxweight%7D%20%5Cquad%20%5Cforall%5C%2C%20w%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%7Bmaxweight%7D%20%5Cleq%20%5Ctext%7BWMAX%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%7Bmaxweight%7D%20%5Cgeq%20%5Cleft%5Clceil%20%5Cfrac%7B%5Csum_b%20%5Ctext%7BWEIGHT%7D_b%7D%7B3%7D%20%5Cright%5Crceil%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%20the%2016%20box%20weights%20and%20the%20wagon%20capacity%20limit%20in%20Python.%20Both%20are%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%20WEIGHT%20%3D%20%7Bi%2B1%3A%20v%20for%20i%2Cv%20in%20enumerate(%5B34%2C6%2C8%2C17%2C16%2C5%2C13%2C21%2C25%2C31%2C14%2C13%2C33%2C9%2C25%2C25%5D)%7D%0A%20%20%20%20WMAX%20%20%20%3D%20100%0A%20%20%20%20return%20WEIGHT%2C%20WMAX%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%20Use%20the%20compile-load-run%20sequence.%20After%20%60run()%60%2C%20%60model.export_problem(file_name%3D...)%60%20writes%20the%20MIP%20formulation%20to%20an%20LP%20file.%20Pass%20the%20path%20as%20a%20keyword%20argument%20-%20the%20first%20positional%20argument%20is%20%60options%60%20(format%20flags)%2C%20not%20the%20file%20name.%20The%20LP%20file%20is%20useful%20for%20inspecting%20the%20formulation%20in%20a%20text%20editor%20or%20external%20solver.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20WEIGHT%2C%20WMAX%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fd1wagon_p.mos'%2C%20f'%7BMODELS%7D%2Fd1wagon_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fd1wagon_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%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'WEIGHT'%3A%20WEIGHT%2C%20'WMAX'%3A%20WMAX%7D)%0A%20%20%20%20_model.export_problem(file_name%3Df'%7BMODELS%7D%2Fd1wagon_p.lp')%0A%20%20%20%20print('LP%20file%20written%20to%20'%20%2B%20MODELS%20%2B%20'%2Fd1wagon_p.lp')%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_ifload%20%3D%20_output%5B'sol_ifload'%5D%0A%20%20%20%20%23%20Export%20the%20MIP%20formulation%20to%20LP%20format%20after%20solving%20(problem%20must%20be%20built%20first)%0A%20%20%20%20%20%20%20%20print(f'%5CnMinimax%20wagon%20weight%3A%20%7Bobj_val%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f'%7B'Wagon'%3A%3C8%7D%20%7B'Boxes'%3A%3C40%7D%20%7B'Total%20weight'%7D')%0A%20%20%20%20%20%20%20%20for%20_w%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_boxes%20%3D%20%5Bb%20for%20b%20in%20range(1%2C%2017)%20if%20sol_ifload.get((b%2C%20_w)%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_total%20%3D%20sum((WEIGHT%5Bb%5D%20for%20b%20in%20_boxes))%20%20%23%20dict%3A%20(box%2C%20wagon)%20-%3E%20real%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_w%3A%3C6%7D%20%7Bstr(_boxes)%3A%3C40%7D%20%7B_total%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%2C%20sol_ifload%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%20bar%20chart%20showing%20the%20box%20assignments%20across%20wagons.%20Each%20bar%20is%20one%20wagon%3B%20segments%20are%20individual%20boxes%20(width%20proportional%20to%20box%20weight).%20The%20red%20dashed%20line%20marks%20the%20WMAX%20capacity%20limit.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(WEIGHT%2C%20WMAX%2C%20mo%2C%20obj_val%2C%20plt%2C%20sol_ifload)%3A%0A%20%20%20%20if%20'sol_ifload'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_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(10%2C%204))%0A%20%20%20%20%20%20%20%20for%20_w%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_boxes%20%3D%20sorted(%5Bb%20for%20b%20in%20range(1%2C%2017)%20if%20sol_ifload.get((b%2C%20_w)%2C%200)%20%3E%200.5%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_left%20%3D%200%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20b%20in%20_boxes%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20wt%20%3D%20WEIGHT%5Bb%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(_w%20-%201%2C%20wt%2C%20left%3D_left%2C%20color%3D_colors%5B(b%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%20wt%20%2F%202%2C%20_w%20-%201%2C%20str(b)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D7%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_left%20%3D%20_left%20%2B%20wt%0A%20%20%20%20%20%20%20%20_ax.axvline(WMAX%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'Capacity%20limit%20WMAX%3D%7BWMAX%7D')%0A%20%20%20%20%20%20%20%20_ax.axvline(obj_val%2C%20color%3D'navy'%2C%20linestyle%3D'%3A'%2C%20linewidth%3D1.5%2C%20label%3Df'Optimal%20max%20weight%3D%7Bobj_val%3A.0f%7D')%0A%20%20%20%20%20%20%20%20_ax.set_yticks(range(3))%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Wagon%20%7Bw%7D'%20for%20w%20in%20range(1%2C%204)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Weight%20(quintals)')%0A%20%20%20%20%20%20%20%20_ax.set_title('D-1%20Wagon%20load%20balancing%20-%20box%20assignments')%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%20wagon%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%20Warm-start%20from%20Python%20(d1wagon2_p)%0A%0A%20%20%20%20%60d1wagon2_p.mos%60%20uses%20a%20warm%20start%20to%20speed%20up%20the%20MIP.%20The%20LPT%20(Longest%20Processing%20Time)%20heuristic%20is%20implemented%20here%20in%20Python%3A%20boxes%20are%20sorted%20by%20weight%20descending%20and%20assigned%20one%20by%20one%20to%20the%20least-loaded%20wagon.%20The%20resulting%20assignment%20(%60HeurSol%60)%20is%20passed%20to%20Mosel%20via%20%60input_data%60.%20Inside%20the%20model%2C%20%60addmipsol%60%20feeds%20it%20to%20the%20Xpress%20branch-and-bound%20as%20an%20initial%20integer%20solution.%0A%0A%20%20%20%20This%20demonstrates%20a%20clean%20MoselPy%20pattern%3A%20**the%20heuristic%20lives%20in%20Python%2C%20the%20warm-start%20mechanism%20lives%20in%20Mosel**.%20The%20%60solnotify%60%20Mosel%20callback%20reports%20whether%20the%20optimizer%20accepted%20and%20used%20the%20solution.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(WEIGHT)%3A%0A%20%20%20%20wagon_loads_h%20%3D%20%7B1%3A%200%2C%202%3A%200%2C%203%3A%200%7D%0A%20%20%20%20heur_sol%20%3D%20%7B%7D%0A%20%20%20%20for%20box%20in%20sorted(WEIGHT%2C%20key%3DWEIGHT.get%2C%20reverse%3DTrue)%3A%0A%20%20%20%20%20%20%20%20wagon%20%3D%20min(wagon_loads_h%2C%20key%3Dwagon_loads_h.get)%0A%20%20%20%20%20%20%20%20heur_sol%5Bbox%5D%20%3D%20wagon%0A%20%20%20%20%20%20%20%20wagon_loads_h%5Bwagon%5D%20%3D%20wagon_loads_h%5Bwagon%5D%20%2B%20WEIGHT%5Bbox%5D%0A%20%20%20%20heur_max%20%3D%20max(wagon_loads_h.values())%0A%20%20%20%20print(f'LPT%20heuristic%20-%20max%20wagon%20weight%3A%20%7Bheur_max%7D')%0A%20%20%20%20for%20_w%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20boxes_w%20%3D%20sorted((b%20for%20b%20in%20range(1%2C%2017)%20if%20heur_sol%5Bb%5D%20%3D%3D%20_w))%0A%20%20%20%20%20%20%20%20print(f'%20%20Wagon%20%7B_w%7D%20(%7Bwagon_loads_h%5B_w%5D%7D%20quintals)%3A%20boxes%20%7Bboxes_w%7D')%0A%20%20%20%20return%20heur_max%2C%20heur_sol%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%20%60d1wagon2_p.mos%60%20with%20the%20Python-computed%20%60HeurSol%60%20passed%20via%20%60input_data%60.%20The%20model%20feeds%20it%20to%20the%20optimizer%20via%20%60addmipsol%60%20before%20branch-and-bound%20starts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20WEIGHT%2C%20WMAX%2C%20heur_max%2C%20heur_sol%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f%22%7BMODELS%7D%2Fd1wagon2_p.mos%22%2C%20f%22%7BMODELS%7D%2Fd1wagon2_p.bim%22)%0A%20%20%20%20with%20mp.load_model(f%22%7BMODELS%7D%2Fd1wagon2_p.bim%22)%20as%20model2%3A%0A%20%20%20%20%20%20%20%20model2.set_default_stream(mp.StreamType.OUTPUT%2C%20%22null%3A%22)%0A%20%20%20%20%20%20%20%20out2%20%3D%20model2.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20exec_params%3D%7B%22DATASOURCE%22%3A%20%22moselpy%3A%22%2C%20%22OUTFILE%22%3A%20%22moselpy%3A%22%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20input_data%3D%7B%22WEIGHT%22%3A%20WEIGHT%2C%20%22WMAX%22%3A%20WMAX%2C%20%22HeurSol%22%3A%20heur_sol%7D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ok2%20%3D%20model2.prob_status.is_solution_available%0A%20%20%20%20%20%20%20%20sol_ifload_opt%20%3D%20out2.get(%22sol_ifload%22%2C%20%7B%7D)%20if%20ok2%20else%20%7B%7D%0A%0A%20%20%20%20if%20ok2%3A%0A%20%20%20%20%20%20%20%20opt_max%20%3D%20out2%5B%22obj_val%22%5D%0A%20%20%20%20%20%20%20%20print(f%22LPT%20heuristic%20max%20weight%3A%20%7Bheur_max%3A.0f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22MIP%20optimal%20%20max%20weight%3A%20%20%7Bopt_max%3A.0f%7D%22)%0A%20%20%20%20%20%20%20%20print(f%22Gap%20closed%3A%20%20%7Bheur_max%20-%20opt_max%3A.0f%7D%20quintals%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(%22No%20solution%20found%20for%20d1wagon2_p%22)%0A%20%20%20%20return%20ok2%2C%20out2%2C%20sol_ifload_opt%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%20Side-by-side%20comparison%20of%20the%20LPT%20heuristic%20assignment%20(left)%20and%20the%20MIP%20optimal%20assignment%20(right).%20Each%20row%20is%20a%20wagon%3B%20segments%20represent%20individual%20boxes%2C%20width%20proportional%20to%20weight.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(WEIGHT%2C%20WMAX%2C%20heur_max%2C%20heur_sol%2C%20mo%2C%20ok2%2C%20out2%2C%20plt%2C%20sol_ifload_opt)%3A%0A%20%20%20%20if%20ok2%3A%0A%20%20%20%20%20%20%20%20_colors%20%3D%20plt.cm.tab20.colors%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(14%2C%204)%2C%20sharey%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20draw_wagons(ax%2C%20assignment_fn%2C%20title%2C%20max_w)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20w%20in%20range(1%2C%204)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20boxes%20%3D%20sorted((b%20for%20b%20in%20range(1%2C%2017)%20if%20assignment_fn(b)%20%3D%3D%20w))%0A%20%20%20%20%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%20%20%20%20%20for%20b%20in%20boxes%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20wt%20%3D%20WEIGHT%5Bb%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.barh(w%20-%201%2C%20wt%2C%20left%3Dleft%2C%20color%3D_colors%5B(b%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%20%20%20%20ax.text(left%20%2B%20wt%20%2F%202%2C%20w%20-%201%2C%20str(b)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D7%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20left%20%2B%20wt%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axvline(WMAX%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'WMAX%3D%7BWMAX%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.axvline(max_w%2C%20color%3D'navy'%2C%20linestyle%3D'%3A'%2C%20linewidth%3D1.5%2C%20label%3Df'Max%20load%3D%7Bmax_w%3A.0f%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_yticks(range(3))%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_yticklabels(%5Bf'Wagon%20%7Bw%7D'%20for%20w%20in%20range(1%2C%204)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xlabel('Weight%20(quintals)')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(title)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.legend(loc%3D'lower%20right')%0A%20%20%20%20%20%20%20%20draw_wagons(_axes%5B0%5D%2C%20lambda%20b%3A%20heur_sol%5Bb%5D%2C%20f'LPT%20heuristic%20(max%3D%7Bheur_max%3A.0f%7D)'%2C%20heur_max)%0A%20%20%20%20%20%20%20%20draw_wagons(_axes%5B1%5D%2C%20lambda%20b%3A%20round(sum((w%20*%20sol_ifload_opt.get((b%2C%20w)%2C%200)%20for%20w%20in%20range(1%2C%204))))%2C%20f'MIP%20optimal%20(max%3D%7Bout2%5B'obj_val'%5D%3A.0f%7D)'%2C%20out2%5B'obj_val'%5D)%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%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%20D-2%20Ship%20loading%20(LP%2FMIP)%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%20D%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60d2ship_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd2ship_p.mos)%0A%0A%20%20%20%20A%20shipper%20has%207%20clients%20wishing%20to%20ship%20wheat.%20Each%20client%20offers%20varying%20numbers%20of%20lots%2C%20lot%20sizes%2C%20prices%2C%20and%20transport%20costs.%20The%20shipper%20maximizes%20profit%20subject%20to%20a%20ship%20capacity%20constraint.%20The%20model%20accepts%20a%20%60VARIANT%60%20parameter%20to%20select%20one%20of%20three%20progressively%20constrained%20variants%3A%0A%0A%20%20%20%20-%20**VARIANT%3D1%20(LP)%3A**%20unlimited%20lot%20availability%20per%20client.%0A%20%20%20%20-%20**VARIANT%3D2%20(LP)%3A**%20each%20client%20has%20an%20upper%20bound%20on%20available%20lots.%0A%20%20%20%20-%20**VARIANT%3D3%20(MIP)%3A**%20lots%20must%20be%20integer-valued.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BCLIENTS%7D%20%3D%201..7%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bloadqty%7D_c%20%5Cgeq%200%24%20(lots%20taken%20from%20client%20%24c%24)%0A%0A%20%20%20%20**Derived%20parameter%3A**%20%24%5Ctext%7BPROF%7D_c%20%3D%20%5Ctext%7BPRICE%7D_c%20-%20%5Ctext%7BCOST%7D_c%20%5Ccdot%20%5Ctext%7BSIZE%7D_c%24%20(profit%20per%20lot%20from%20client%20%24c%24)%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bc%7D%20%5Ctext%7BPROF%7D_c%20%5Ccdot%20%5Cmathit%7Bloadqty%7D_c%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bc%7D%20%5Ctext%7BSIZE%7D_c%20%5Ccdot%20%5Cmathit%7Bloadqty%7D_c%20%5Cleq%20%5Ctext%7BCAP%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%7Bloadqty%7D_c%20%5Cleq%20%5Ctext%7BAVAIL%7D_c%20%5Cquad%20%5Cforall%5C%2C%20c%20%5Cquad%20%5Ctext%7B(Problem%202%20and%203%20only)%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%7Bloadqty%7D_c%20%5Cin%20%5Cmathbb%7BZ%7D_%7B%5Cgeq%200%7D%20%5Cquad%20%5Cforall%5C%2C%20c%20%5Cquad%20%5Ctext%7B(Problem%203%20only)%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%20client%20data%20in%20Python%3A%20available%20lots%20%60AVAIL%60%2C%20lot%20sizes%20%60SIZE%60%2C%20prices%20%60PRICE%60%2C%20transport%20costs%20%60COST%60%2C%20and%20ship%20capacity%20%60CAP%60%2C%20all%20as%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%20AVAIL%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B12%2C%2031%2C%2020%2C%2025%2C%2050%2C%2040%2C%2060%5D)%7D%0A%20%20%20%20SIZE%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B10%2C%208%2C%206%2C%209%2C%2015%2C%2010%2C%2012%5D)%7D%0A%20%20%20%20PRICE%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B1000%2C%20600%2C%20600%2C%20800%2C%201200%2C%20800%2C%201100%5D)%7D%0A%20%20%20%20COST%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B80%2C%2070%2C%2085%2C%2080%2C%2073%2C%2070%2C%2080%5D)%7D%0A%20%20%20%20CAP%20%3D%201500%0A%20%20%20%20PROF%20%3D%20%7Bc%3A%20PRICE%5Bc%5D%20-%20COST%5Bc%5D%20*%20SIZE%5Bc%5D%20for%20c%20in%20range(1%2C%208)%7D%0A%20%20%20%20%23%20Derived%3A%20profit%20per%20lot%20(for%20display)%0A%20%20%20%20print('Profit%20per%20lot%20by%20client%3A')%0A%20%20%20%20for%20_c%20in%20range(1%2C%208)%3A%0A%20%20%20%20%20%20%20%20print(f'%20%20Client%20%7B_c%7D%3A%20price%3D%7BPRICE%5B_c%5D%7D%2C%20cost%3D%7BCOST%5B_c%5D%7D%2C%20size%3D%7BSIZE%5B_c%5D%7D%20-%3E%20profit%3D%7BPROF%5B_c%5D%7D')%0A%20%20%20%20return%20AVAIL%2C%20CAP%2C%20COST%2C%20PRICE%2C%20PROF%2C%20SIZE%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%60d2ship_p.mos%60%20once%2C%20then%20run%20it%20three%20times%20with%20%60VARIANT%3D1%60%2C%20%60VARIANT%3D2%60%2C%20and%20%60VARIANT%3D3%60.%20Each%20run%20returns%20%60sol_obj%60%20(the%20profit%20for%20that%20variant)%20and%20%60sol_load%60%20(the%20lots%20per%20client).%20Running%20separately%20lets%20you%20inspect%20the%20solution%20at%20each%20stage%20rather%20than%20only%20seeing%20the%20final%20MIP%20result.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(AVAIL%2C%20CAP%2C%20COST%2C%20MODELS%2C%20PRICE%2C%20PROF%2C%20SIZE%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fd2ship_p.mos'%2C%20f'%7BMODELS%7D%2Fd2ship_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fd2ship_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_obj_labels%20%3D%20%7B1%3A%20'LP%2C%20no%20limits'%2C%202%3A%20'LP%2C%20availability%20limits'%2C%203%3A%20'MIP%2C%20integer%20lots'%7D%0A%20%20%20%20results%20%3D%20%7B%7D%0A%20%20%20%20for%20_obj%20in%20%5B1%2C%202%2C%203%5D%3A%0A%20%20%20%20%20%20%20%20_output%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'VARIANT'%3A%20_obj%7D%2C%20input_data%3D%7B'AVAIL'%3A%20AVAIL%2C%20'SIZE'%3A%20SIZE%2C%20'PRICE'%3A%20PRICE%2C%20'COST'%3A%20COST%2C%20'CAP'%3A%20CAP%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%20results%5B_obj%5D%20%3D%20_output%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'MOD%3D%7B_obj%7D%3A%20No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20for%20_obj%2C%20_output%20in%20results.items()%3A%0A%20%20%20%20%20%20%20%20sol_obj%20%3D%20_output%5B'sol_obj'%5D%0A%20%20%20%20%20%20%20%20_sol_load%20%3D%20_output%5B'sol_load'%5D%0A%20%20%20%20%20%20%20%20print(f'OBJ%20%7B_obj%7D%20(%7B_obj_labels%5B_obj%5D%7D)%3A%20profit%20%3D%20%7Bsol_obj%3A%2C.2f%7D')%0A%20%20%20%20%20%20%20%20for%20_c%20in%20range(1%2C%208)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20q%20%3D%20_sol_load.get(_c%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20q%20%3E%200.001%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Client%20%7B_c%7D%3A%20%7Bq%3A.4g%7D%20lots%20%20(contribution%3A%20%7BPROF%5B_c%5D%20*%20q%3A%2C.0f%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20return%20(results%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%20Left%3A%20bar%20chart%20comparing%20the%20three%20profit%20objectives.%20Right%3A%20per-client%20profit%20contribution%20for%20each%20variant%2C%20showing%20how%20availability%20and%20integrality%20constraints%20change%20the%20loading%20decision.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(PROF%2C%20mo%2C%20np%2C%20plt%2C%20results)%3A%0A%20%20%20%20if%20results%3A%0A%20%20%20%20%20%20%20%20_obj_labels%20%3D%20%7B1%3A%20'OBJ%201%5Cn(LP%2C%20unlimited)'%2C%202%3A%20'OBJ%202%5Cn(LP%2C%20limited)'%2C%203%3A%20'OBJ%203%5Cn(MIP%2C%20integer)'%7D%0A%20%20%20%20%20%20%20%20_colors%20%3D%20%7B1%3A%20'%2342A5F5'%2C%202%3A%20'%23FB8C00'%2C%203%3A%20'%2343A047'%7D%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(13%2C%204))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20labels%20%3D%20%5B_obj_labels%5Bo%5D%20for%20o%20in%20%5B1%2C%202%2C%203%5D%5D%0A%20%20%20%20%20%20%20%20values%20%3D%20%5Bresults%5Bo%5D%5B'sol_obj'%5D%20for%20o%20in%20%5B1%2C%202%2C%203%5D%5D%20%20%23%20Left%3A%20objective%20comparison%0A%20%20%20%20%20%20%20%20bars%20%3D%20_ax.bar(labels%2C%20values%2C%20color%3D%5B_colors%5Bo%5D%20for%20o%20in%20%5B1%2C%202%2C%203%5D%5D%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20for%20bar%2C%20_val%20in%20zip(bars%2C%20values)%3A%0A%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%20200%2C%20f'%7B_val%3A%2C.0f%7D'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Profit')%0A%20%20%20%20%20%20%20%20_ax.set_title('D-2%20Ship%20loading%20-%20profit%20by%20variant')%0A%20%20%20%20%20%20%20%20ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20x%20%3D%20np.arange(1%2C%208)%0A%20%20%20%20%20%20%20%20width%20%3D%200.25%0A%20%20%20%20%20%20%20%20for%20i%2C%20_obj%20in%20enumerate(%5B1%2C%202%2C%203%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_sol_load%20%3D%20results%5B_obj%5D%5B'sol_load'%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20contribs%20%3D%20%5BPROF%5Bc%5D%20*%20_sol_load.get(c%2C%200)%20for%20c%20in%20range(1%2C%208)%5D%20%20%23%20Right%3A%20per-client%20contributions%20per%20variant%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.bar(x%20%2B%20(i%20-%201)%20*%20width%2C%20contribs%2C%20width%3Dwidth%2C%20color%3D_colors%5B_obj%5D%2C%20edgecolor%3D'white'%2C%20label%3Df'OBJ%20%7B_obj%7D')%0A%20%20%20%20%20%20%20%20ax2.set_xticks(x)%0A%20%20%20%20%20%20%20%20ax2.set_xticklabels(%5Bf'Client%20%7Bc%7D'%20for%20c%20in%20range(1%2C%208)%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20ax2.set_ylabel('Profit%20contribution')%0A%20%20%20%20%20%20%20%20ax2.set_title('D-2%20Client%20profit%20contributions%20by%20variant')%0A%20%20%20%20%20%20%20%20ax2.legend(fontsize%3D8)%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%20ship%20loading%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%20D-3%20Tank%20loading%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%20D%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60d3tanks_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd3tanks_p.mos)%0A%0A%20%20%20%20Five%20tanker%20ships%20carrying%20chemical%20liquids%20must%20be%20unloaded%20into%209%20storage%20tanks.%20Liquids%20must%20not%20be%20mixed.%20Two%20tanks%20already%20have%20partial%20contents.%20The%20model%20is%20solved%20for%20two%20objectives%20in%20sequence%3A%0A%0A%20%20%20%20-%20**Objective%201%3A**%20minimize%20total%20capacity%20of%20tanks%20occupied%20(prefer%20filling%20fewer%2C%20larger%20tanks).%0A%20%20%20%20-%20**Objective%202%3A**%20minimize%20number%20of%20tanks%20used.%0A%0A%20%20%20%20Entries%20of%20the%20decision%20variable%20array%20%60ifload%60%20are%20created%20dynamically%20via%20%60create()%60%20-%20only%20for%20the%20empty%20tanks.%20The%20%60TINIT%60%20and%20%60QINIT%60%20arrays%20record%20the%20initial%20partial%20contents.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BTANKS%7D%20%3D%201..9%24%2C%20%24%5Ctext%7BLIQ%7D%20%3D%20%5C%7B%5Ctext%7BBenzol%2C%20Butanol%2C%20Propanol%2C%20Styrene%2C%20THF%7D%5C%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bifload%7D_%7Bl%2Ct%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20for%20%24t%24%20with%20no%20initial%20contents%20(1%20if%20liquid%20%24l%24%20uses%20tank%20%24t%24)%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7BObj%201%3A%7D%5Cquad%20%5Ctext%7Bminimize%7D%20%5Cquad%20%26%20%5Csum_%7Bl%2Ct%7D%20%5Ctext%7BCAP%7D_t%20%5Ccdot%20%5Cmathit%7Bifload%7D_%7Bl%2Ct%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7BObj%202%3A%7D%5Cquad%20%5Ctext%7Bminimize%7D%20%5Cquad%20%26%20%5Csum_%7Bl%2Ct%7D%20%5Cmathit%7Bifload%7D_%7Bl%2Ct%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bl%7D%20%5Cmathit%7Bifload%7D_%7Bl%2Ct%7D%20%5Cleq%201%20%5Cquad%20%5Cforall%5C%2C%20t%20%5Cquad%20%5Ctext%7B(no%20mixing)%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_%7Bt%7D%20%5Ctext%7BCAP%7D_t%20%5Ccdot%20%5Cmathit%7Bifload%7D_%7Bl%2Ct%7D%20%5Cgeq%20%5Ctext%7BREST%7D_l%20%5Cquad%20%5Cforall%5C%2C%20l%20%5Cquad%20%5Ctext%7B(satisfy%20remaining%20quantities)%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20where%20%24%5Ctext%7BREST%7D_l%20%3D%20%5Ctext%7BARR%7D_l%20-%20%5Csum_%7Bt%3A%5C%2C%5Ctext%7BTINIT%7D_t%3Dl%7D(%5Ctext%7BCAP%7D_t%20-%20%5Ctext%7BQINIT%7D_t)%24%20is%20the%20quantity%20not%20covered%20by%20the%20pre-filled%20tanks.%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%209%20tank%20capacities%2C%20arriving%20quantities%20per%20liquid%2C%20initial%20tank%20assignments%20(%60TINIT%60%20and%20%60QINIT%60%20for%20the%20two%20pre-filled%20tanks)%2C%20and%20pass%20them%20via%20%60input_data%60.%20The%20%60LIQ%60%20set%20is%20auto-populated%20from%20the%20string%20keys%20of%20the%20%60ARR%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%20CAP_1%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B500%2C%20400%2C%20400%2C%20600%2C%20600%2C%20900%2C%20800%2C%20800%2C%20800%5D)%7D%0A%20%20%20%20ARR%20%3D%20%7B'Benzol'%3A%201200%2C%20'Butanol'%3A%20700%2C%20'Propanol'%3A%201000%2C%20'Styrene'%3A%20450%2C%20'THF'%3A%201200%7D%0A%20%20%20%20TINIT%20%3D%20%7B2%3A%20'Benzol'%2C%207%3A%20'THF'%7D%0A%20%20%20%20QINIT%20%3D%20%7B2%3A%20100%2C%207%3A%20300%7D%0A%20%20%20%20print('Tank%20capacities%3A'%2C%20list(CAP_1.values()))%0A%20%20%20%20print('Arriving%20quantities%3A'%2C%20ARR)%0A%20%20%20%20print('Pre-filled%20tanks%3A'%2C%20%7Bt%3A%20f'%7BTINIT%5Bt%5D%7D%20(%7BQINIT%5Bt%5D%7D)'%20for%20t%20in%20TINIT%7D)%0A%20%20%20%20return%20ARR%2C%20CAP_1%2C%20QINIT%2C%20TINIT%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%20accepts%20an%20%60OBJ%60%20parameter%20(1%20%3D%20minimize%20total%20capacity%20used%2C%202%20%3D%20minimize%20number%20of%20tanks).%20Compile%20and%20load%20once%2C%20then%20run%20twice%20with%20%60exec_params%3D%7B%22OBJ%22%3A%201%7D%60%20and%20%60exec_params%3D%7B%22OBJ%22%3A%202%7D%60.%20Both%20runs%20return%20%60sol_TankUse%60%2C%20%60sol_TankNum%60%2C%20and%20%60sol_ifload%60%20(the%20detailed%20assignment)%2C%20enabling%20a%20side-by-side%20comparison.%20Pre-filled%20tanks%202%20(Benzol%2C%20100)%20and%207%20(THF%2C%20300)%20are%20fixed%20and%20excluded%20from%20the%20counts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ARR%2C%20CAP_1%2C%20MODELS%2C%20QINIT%2C%20TINIT%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fd3tanks_p.mos'%2C%20f'%7BMODELS%7D%2Fd3tanks_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fd3tanks_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20prefilled_cap%20%3D%20sum((CAP_1%5Bt%5D%20for%20t%20in%20QINIT))%0A%20%20%20%20prefilled_num%20%3D%20len(QINIT)%0A%20%20%20%20results_1%20%3D%20%7B%7D%0A%20%20%20%20for%20_obj%20in%20%5B1%2C%202%5D%3A%0A%20%20%20%20%20%20%20%20_output%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'VARIANT'%3A%20_obj%7D%2C%20input_data%3D%7B'CAP'%3A%20CAP_1%2C%20'ARR'%3A%20ARR%2C%20'TINIT'%3A%20TINIT%2C%20'QINIT'%3A%20QINIT%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%20results_1%5B_obj%5D%20%3D%20_output%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'MOD%3D%7B_obj%7D%3A%20No%20solution%20found%20(%7B_model.prob_status%7D)')%0A%20%20%20%20_obj_labels%20%3D%20%7B1%3A%20'min%20capacity'%2C%202%3A%20'min%20tanks'%7D%0A%20%20%20%20for%20_obj%2C%20_output%20in%20results_1.items()%3A%0A%20%20%20%20%20%20%20%20sol_ifload_1%20%3D%20%7B(l%2C%20t)%3A%20v%20for%20(l%2C%20t)%2C%20v%20in%20_output%5B'sol_ifload'%5D.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20sol_TankUse%20%3D%20_output%5B'sol_TankUse'%5D%0A%20%20%20%20%20%20%20%20sol_TankNum%20%3D%20_output%5B'sol_TankNum'%5D%0A%20%20%20%20%20%20%20%20print(f'OBJ%20%7B_obj%7D%20(%7B_obj_labels%5B_obj%5D%7D)%3A')%0A%20%20%20%20%20%20%20%20print(f'%20%20Capacity%20of%20newly%20used%20tanks%3A%20%7Bsol_TankUse%3A.0f%7D%20%20(total%20incl.%20pre-filled%3A%20%7Bsol_TankUse%20%2B%20prefilled_cap%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20print(f'%20%20Number%20%20of%20newly%20used%20tanks%3A%20%20%7Bsol_TankNum%3A.0f%7D%20%20(total%20incl.%20pre-filled%3A%20%7Bsol_TankNum%20%2B%20prefilled_num%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20for%20t%20in%20range(1%2C%2010)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20t%20in%20QINIT%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%20%20Tank%20%7Bt%7D%20(cap%3D%7BCAP_1%5Bt%5D%3A%3E4%7D)%3A%20%7BTINIT%5Bt%5D%7D%20%5Bpre-filled%2C%20qty%3D%7BQINIT%5Bt%5D%7D%5D')%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20liq_here%20%3D%20%5Bl%20for%20l%2C%20tk%20in%20sol_ifload_1%20if%20tk%20%3D%3D%20t%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%20%20Tank%20%7Bt%7D%20(cap%3D%7BCAP_1%5Bt%5D%3A%3E4%7D)%3A%20%7B(liq_here%5B0%5D%20if%20liq_here%20else%20'empty')%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20return%20prefilled_cap%2C%20prefilled_num%2C%20results_1%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%20Side-by-side%20bar%20charts%20comparing%20the%20two%20objective%20assignments.%20Each%20tank%20is%20colored%20by%20its%20assigned%20liquid.%20Pre-filled%20tanks%20are%20shown%20with%20a%20hatched%20pattern%3B%20empty%20tanks%20are%20gray.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ARR%2C%0A%20%20%20%20CAP_1%2C%0A%20%20%20%20QINIT%2C%0A%20%20%20%20TINIT%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20prefilled_cap%2C%0A%20%20%20%20prefilled_num%2C%0A%20%20%20%20results_1%2C%0A)%3A%0A%20%20%20%20if%20results_1%3A%0A%20%20%20%20%20%20%20%20liq_names%20%3D%20list(ARR.keys())%0A%20%20%20%20%20%20%20%20liq_colors%20%3D%20dict(zip(liq_names%2C%20%5B'%231E88E5'%2C%20'%23FB8C00'%2C%20'%2343A047'%2C%20'%23E53935'%2C%20'%238E24AA'%5D))%0A%20%20%20%20%20%20%20%20_obj_labels%20%3D%20%7B1%3A%20'Obj%201%3A%20min%20capacity'%2C%202%3A%20'Obj%202%3A%20min%20tanks'%7D%0A%0A%20%20%20%20%20%20%20%20def%20draw_tanks(ax%2C%20assigned%2C%20sol_TankUse%2C%20sol_TankNum%2C%20title)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20seen%20%3D%20set()%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20t%20in%20range(1%2C%2010)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cap%20%3D%20CAP_1%5Bt%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20t%20in%20QINIT%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20liq%20%3D%20TINIT%5Bt%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%20%3D%20f'%7Bliq%7D%20(pre-filled)'%20if%20liq%20not%20in%20seen%20else%20''%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seen.add(liq)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.bar(t%2C%20cap%2C%20color%3Dliq_colors.get(liq%2C%20'%2390CAF9')%2C%20edgecolor%3D'gray'%2C%20hatch%3D'%2F%2F%2F'%2C%20alpha%3D0.7%2C%20label%3Dlabel)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20liq_here%20%3D%20%5Bl%20for%20l%2C%20tk%20in%20assigned%20if%20tk%20%3D%3D%20t%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20liq_here%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%20liq%20%3D%20liq_here%5B0%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%20label%20%3D%20liq%20if%20liq%20not%20in%20seen%20else%20''%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seen.add(liq)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.bar(t%2C%20cap%2C%20color%3Dliq_colors.get(liq%2C%20'%2390CAF9')%2C%20edgecolor%3D'white'%2C%20label%3Dlabel)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%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%20label%20%3D%20'Empty'%20if%20'Empty'%20not%20in%20seen%20else%20''%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seen.add('Empty')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.bar(t%2C%20cap%2C%20color%3D'%23BDBDBD'%2C%20edgecolor%3D'white'%2C%20label%3Dlabel)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticks(range(1%2C%2010))%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_xticklabels(%5Bf'T%7Bt%7D'%20for%20t%20in%20range(1%2C%2010)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_ylabel('Tank%20capacity')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.set_title(f'%7Btitle%7D%5Cn(cap%20used%3D%7Bsol_TankUse%20%2B%20prefilled_cap%3A.0f%7D%2C%20tanks%20used%3D%7Bsol_TankNum%20%2B%20prefilled_num%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax.legend(loc%3D'upper%20right'%2C%20fontsize%3D7)%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(14%2C%204))%0A%20%20%20%20%20%20%20%20for%20_obj%2C%20_ax%20in%20zip(%5B1%2C%202%5D%2C%20_axes)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_output%20%3D%20results_1%5B_obj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20assigned%20%3D%20%7B(l%2C%20t)%3A%20v%20for%20(l%2C%20t)%2C%20v%20in%20_output%5B'sol_ifload'%5D.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20draw_tanks(_ax%2C%20assigned%2C%20_output%5B'sol_TankUse'%5D%2C%20_output%5B'sol_TankNum'%5D%2C%20_obj_labels%5B_obj%5D)%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%20tank%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%20D-4%20Bin%20packing%3A%20file%20backup%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%20D%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60d4backup_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd4backup_p.mos)%0A%0A%20%20%20%20Sixteen%20files%20of%20varying%20sizes%20must%20be%20backed%20up%20onto%20external%20storage%20disks%2C%20each%20with%20capacity%201440%20MB.%20The%20goal%20is%20to%20minimize%20the%20number%20of%20disks%20used.%20This%20is%20the%20classic%20bin-packing%20problem.%20The%20model%20computes%20the%20minimum%20number%20of%20disks%20required%20(%60ND%60)%20as%20an%20upper%20bound%20before%20building%20the%20MIP.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BFILES%7D%20%3D%201..16%24%2C%20%24%5Ctext%7BDISKS%7D%20%3D%201..%5Ctext%7BND%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bifsave%7D_%7Bf%2Cd%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20file%20%24f%24%20saved%20on%20disk%20%24d%24)%3B%20%24%5Cmathit%7Bdiskuse%7D%20%5Cgeq%200%24%20(index%20of%20last%20disk%20used)%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%7Bdiskuse%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Cmathit%7Bdiskuse%7D%20%5Cgeq%20%5Csum_d%20d%20%5Ccdot%20%5Cmathit%7Bifsave%7D_%7Bf%2Cd%7D%20%5Cquad%20%5Cforall%5C%2C%20f%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_d%20%5Cmathit%7Bifsave%7D_%7Bf%2Cd%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20f%20%5Cquad%20%5Ctext%7B(each%20file%20on%20one%20disk)%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_f%20%5Ctext%7BSIZE%7D_f%20%5Ccdot%20%5Cmathit%7Bifsave%7D_%7Bf%2Cd%7D%20%5Cleq%20%5Ctext%7BCAP%7D%20%5Cquad%20%5Cforall%5C%2C%20d%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%20file%20sizes%20and%20disk%20capacity%20in%20Python.%20%60SIZE%60%20is%20a%201-indexed%20dict%3B%20%60CAP%60%20is%20a%20scalar.%20The%20model%20computes%20%60ND%60%20internally%20using%20%60ceil(sum(SIZE)%2FCAP)%60.%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%20SIZE_1%20%3D%20%7Bi%20%2B%201%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B46%2C%2055%2C%2062%2C%2087%2C%20108%2C%20114%2C%20137%2C%20164%2C%20253%2C%20364%2C%20372%2C%20388%2C%20406%2C%20432%2C%20461%2C%20851%5D)%7D%0A%20%20%20%20CAP_2%20%3D%201440%0A%20%20%20%20_total%20%3D%20sum(SIZE_1.values())%0A%20%20%20%20nd_lb%20%3D%20-(-_total%20%2F%2F%20CAP_2)%0A%20%20%20%20print(f'Total%20file%20size%3A%20%7B_total%7D%20MB%2C%20disk%20capacity%3A%20%7BCAP_2%7D%20MB')%0A%20%20%20%20print(f'Lower%20bound%20on%20disks%20needed%3A%20%7Bnd_lb%7D')%0A%20%20%20%20return%20CAP_2%2C%20SIZE_1%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%20%60d4backup_p.mos%60%20using%20the%20single%20execution%20command.%20The%20model%20computes%20%60ND%60%20internally%20and%20builds%20%60DISKS%20%3D%201..ND%60%20before%20declaring%20%60ifsave%60.%20After%20solving%2C%20%60obj_val%60%20is%20the%20number%20of%20disks%20used%20and%20%60sol_ifsave%60%20is%20the%20full%20assignment%20matrix.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP_2%2C%20MODELS%2C%20SIZE_1%2C%20mp)%3A%0A%20%20%20%20_model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fd4backup_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'SIZE'%3A%20SIZE_1%2C%20'CAP'%3A%20CAP_2%7D)%0A%20%20%20%20if%20_output%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20obj_val_d4%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_ifsave%20%3D%20_output%5B'sol_ifsave'%5D%0A%20%20%20%20%20%20%20%20_nd_used%20%3D%20int(round(obj_val_d4))%0A%20%20%20%20%20%20%20%20print(f'Minimum%20number%20of%20disks%3A%20%7B_nd_used%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_d%20in%20range(1%2C%20_nd_used%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_files%20%3D%20%5Bf%20for%20f%20in%20range(1%2C%2017)%20if%20sol_ifsave.get((f%2C%20_d)%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20used%20%3D%20sum((SIZE_1%5Bf%5D%20for%20f%20in%20_files))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Disk%20%7B_d%7D%3A%20files%20%7B_files%7D%20%20used%3D%7Bused%7D%20MB%20%20free%3D%7BCAP_2%20-%20used%7D%20MB')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20output%20returned')%0A%20%20%20%20return%20obj_val_d4%2C%20sol_ifsave%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%20Horizontal%20stacked%20bar%20chart%20of%20disk%20utilization.%20Each%20disk%20is%20a%20bar%3B%20segments%20are%20individual%20files%20(width%20%3D%20file%20size%20in%20MB).%20The%20dashed%20line%20marks%20the%201440%20MB%20capacity.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP_2%2C%20SIZE_1%2C%20mo%2C%20obj_val_d4%2C%20plt%2C%20sol_ifsave)%3A%0A%20%20%20%20if%20'sol_ifsave'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_nd_used%20%3D%20int(round(obj_val_d4))%0A%20%20%20%20%20%20%20%20file_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(11%2C%20max(3%2C%20_nd_used%20*%200.6%20%2B%201)))%0A%20%20%20%20%20%20%20%20for%20_d%20in%20range(1%2C%20_nd_used%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_files%20%3D%20sorted(%5Bf%20for%20f%20in%20range(1%2C%2017)%20if%20sol_ifsave.get((f%2C%20_d)%2C%200)%20%3E%200.5%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_left%20%3D%200%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20f%20in%20_files%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sz%20%3D%20SIZE_1%5Bf%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(_d%20-%201%2C%20sz%2C%20left%3D_left%2C%20color%3Dfile_colors%5B(f%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%20sz%20%2F%202%2C%20_d%20-%201%2C%20str(f)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D7%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_left%20%3D%20_left%20%2B%20sz%0A%20%20%20%20%20%20%20%20_ax.axvline(CAP_2%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'Capacity%20%3D%20%7BCAP_2%7D%20MB')%0A%20%20%20%20%20%20%20%20_ax.set_yticks(range(_nd_used))%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Disk%20%7Bd%7D'%20for%20d%20in%20range(1%2C%20_nd_used%20%2B%201)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Size%20(MB)')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'D-4%20Bin%20packing%20-%20%7B_nd_used%7D%20disks%20used')%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%20backup%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%20D-5%20Sheet%20metal%20cutting%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%20D%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60d5cutsh_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd5cutsh_p.mos)%0A%0A%20%20%20%20A%20sheet%20metal%20shop%20cuts%20large%20sheets%20into%204%20smaller%20sizes.%20There%20are%2016%20pre-computed%20cutting%20patterns.%20The%20goal%20is%20to%20satisfy%20demand%20for%20each%20size%20using%20the%20minimum%20number%20of%20large%20sheets.%20This%20is%20a%20covering%20(cutting%20stock)%20problem.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BPATTERNS%7D%20%3D%201..16%24%2C%20%24%5Ctext%7BSIZES%7D%20%3D%201..4%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Buse%7D_p%20%5Cin%20%5Cmathbb%7BZ%7D_%7B%5Cgeq%200%7D%24%20(how%20many%20times%20to%20cut%20pattern%20%24p%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_%7Bp%7D%20%5Cmathit%7Buse%7D_p%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bp%7D%20%5Ctext%7BCUT%7D_%7Bs%2Cp%7D%20%5Ccdot%20%5Cmathit%7Buse%7D_p%20%5Cgeq%20%5Ctext%7BDEM%7D_s%20%5Cquad%20%5Cforall%5C%2C%20s%20%5Cin%20%5Ctext%7BSIZES%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%20demands%20%60DEM%60%20and%20the%204x16%20cutting%20matrix%20%60CUT%60%20in%20Python.%20%60CUT%60%20is%20passed%20as%20a%20%60pd.DataFrame%60%20with%20size%20indices%20as%20rows%20and%20pattern%20indices%20as%20columns.%20Passing%20a%20DataFrame%20directly%20as%20%60input_data%60%20is%20a%20convenient%20way%20to%20supply%202D%20data%20without%20manually%20constructing%20a%20tuple-keyed%20dict.%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%20DEM%20%3D%20%7B1%3A%208%2C%202%3A%2013%2C%203%3A%205%2C%204%3A%2015%7D%0A%0A%20%20%20%20cut_rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B1%2C1%2C1%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%5D%2C%20%20%20%23%20size%201%0A%20%20%20%20%20%20%20%20%5B2%2C1%2C0%2C2%2C1%2C0%2C3%2C2%2C1%2C0%2C5%2C4%2C3%2C2%2C1%2C0%5D%2C%20%20%20%23%20size%202%0A%20%20%20%20%20%20%20%20%5B0%2C0%2C0%2C2%2C2%2C2%2C1%2C1%2C1%2C1%2C0%2C0%2C0%2C0%2C0%2C0%5D%2C%20%20%20%23%20size%203%0A%20%20%20%20%20%20%20%20%5B0%2C1%2C3%2C0%2C1%2C3%2C0%2C2%2C3%2C5%2C0%2C1%2C3%2C5%2C6%2C8%5D%2C%20%20%20%23%20size%204%0A%20%20%20%20%5D%0A%20%20%20%20CUT%20%3D%20pd.DataFrame(cut_rows%2C%20index%3D%5B1%2C2%2C3%2C4%5D%2C%20columns%3Drange(1%2C17))%0A%20%20%20%20CUT.index.name%20%20%20%3D%20%22Size%22%0A%20%20%20%20CUT.columns.name%20%3D%20%22Pattern%22%0A%0A%20%20%20%20print(%22Demands%3A%22%2C%20DEM)%0A%20%20%20%20print(%22%5CnCutting%20patterns%20(rows%3Dsizes%2C%20columns%3Dpatterns)%3A%22)%0A%20%20%20%20print(CUT.to_string())%0A%20%20%20%20return%20CUT%2C%20DEM%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%20the%20model%20passing%20%60CUT%60%20as%20a%20DataFrame%20in%20%60input_data%60.%20MoselPy%20maps%20the%20DataFrame's%20row%2Fcolumn%20indices%20directly%20to%20the%20Mosel%202D%20array.%20After%20solving%2C%20%60sol_use%60%20is%20a%20dict%20mapping%20pattern%20index%20to%20number%20of%20times%20it%20is%20used.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CUT%2C%20DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20_model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fd5cutsh_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'DEM'%3A%20DEM%2C%20'CUT'%3A%20CUT%7D)%0A%20%20%20%20if%20_output%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20obj_val_d5%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_use_d5%20%3D%20_output%5B'sol_use'%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20large%20sheets%20required%3A%20%7Bobj_val_d5%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print('Patterns%20used%3A')%0A%20%20%20%20%20%20%20%20for%20_p%20in%20range(1%2C%2017)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_qty%20%3D%20sol_use_d5.get(_p%2C%200)%20%20%23%20dict%3A%20pattern%20-%3E%20real%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_qty%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pieces%20%3D%20%5Bf'size%20%7Bs%7D%3A%20%7BCUT.at%5Bs%2C%20_p%5D%7D'%20for%20s%20in%20range(1%2C%205)%20if%20CUT.at%5Bs%2C%20_p%5D%20%3E%200%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Pattern%20%7B_p%3A2d%7D%20x%20%7B_qty%3A.0f%7D%3A%20%20%7B'%2C%20'.join(_pieces)%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_produced%20%3D%20sum((CUT.at%5B_s%2C%20p%5D%20*%20sol_use_d5.get(p%2C%200)%20for%20p%20in%20range(1%2C%2017)))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Size%20%7B_s%7D%3A%20demand%3D%7BDEM%5B_s%5D%7D%2C%20produced%3D%7B_produced%3A.0f%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20output%20returned')%0A%20%20%20%20return%20obj_val_d5%2C%20sol_use_d5%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%204x16%20cutting%20pattern%20matrix.%20Darker%20cells%20indicate%20more%20pieces%20of%20that%20size%20cut%20per%20sheet.%20Patterns%20actually%20used%20in%20the%20solution%20are%20highlighted%20with%20a%20border.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CUT%2C%20mo%2C%20obj_val_d5%2C%20plt%2C%20sol_use_d5)%3A%0A%20%20%20%20if%20'sol_use_d5'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20cut_matrix%20%3D%20CUT.values%20%20%23%20shape%20(4%2C%2016)%0A%20%20%20%20%20%20%20%20used_patterns%20%3D%20%5Bp%20for%20p%20in%20range(1%2C%2017)%20if%20sol_use_d5.get(p%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(13%2C%203.5))%0A%20%20%20%20%20%20%20%20im%20%3D%20_ax.imshow(cut_matrix%2C%20cmap%3D'Blues'%2C%20aspect%3D'auto'%2C%20vmin%3D0)%0A%20%20%20%20%20%20%20%20plt.colorbar(im%2C%20ax%3D_ax%2C%20label%3D'Pieces%20per%20sheet')%0A%20%20%20%20%20%20%20%20for%20s_idx%20in%20range(4)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20p_idx%20in%20range(16)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_val%20%3D%20cut_matrix%5Bs_idx%2C%20p_idx%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(p_idx%2C%20s_idx%2C%20str(_val)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20color%3D'white'%20if%20_val%20%3E%203%20else%20'black')%0A%20%20%20%20%20%20%20%20for%20_p%20in%20used_patterns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p_idx%20%3D%20_p%20-%201%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.add_patch(plt.Rectangle((p_idx%20-%200.5%2C%20-0.5)%2C%201%2C%204%2C%20fill%3DFalse%2C%20edgecolor%3D'red'%2C%20linewidth%3D2.5))%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(16))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(%5Bf'P%7Bp%7D'%20for%20p%20in%20range(1%2C%2017)%5D%2C%20fontsize%3D7)%20%20%23%20Highlight%20used%20patterns%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'Size%20%7Bs%7D'%20for%20s%20in%20range(1%2C%205)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_title(f'D-5%20Sheet%20cutting%20patterns%20(red%20border%20%3D%20patterns%20used%2C%20total%20sheets%3D%7Bobj_val_d5%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%20pattern%20heatmap')%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%20D-6%20Cutting%20steel%20bars%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%20D%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60d6cutbar_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Floading_cutting_stock%2Fd6cutbar_p.mos)%0A%0A%20%20%20%20A%20company%20produces%20school%20desks%20in%20three%20heights%20(40%2C%2060%2C%2070%20cm).%20Desk%20legs%20are%20cut%20from%20steel%20bars%20of%20two%20lengths%20(150%20cm%20and%20200%20cm).%20Each%20bar%20has%20its%20own%20set%20of%20pre-computed%20patterns.%20The%20goal%20is%20to%20minimize%20total%20material%20waste%20(trim%20loss).%0A%0A%20%20%20%20Note%20that%20%60SIZES%60%20is%20declared%20as%20%60set%20of%20integer%60%20in%20the%20Mosel%20model%2C%20which%20is%20more%20general%20than%20a%20range.%20When%20passing%20%60DEM%60%20from%20Python%20as%20a%20dict%20keyed%20by%20integer%2C%20MoselPy%20automatically%20populates%20%60SIZES%60%20from%20the%20dict%20keys.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BPAT1%7D%20%3D%201..6%24%2C%20%24%5Ctext%7BPAT2%7D%20%3D%207..12%24%2C%20%24%5Ctext%7BPATTERNS%7D%20%3D%20%5Ctext%7BPAT1%7D%20%5Ccup%20%5Ctext%7BPAT2%7D%24%2C%20%24%5Ctext%7BSIZES%7D%20%3D%20%5C%7B40%2C%2060%2C%2070%5C%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Buse%7D_p%20%5Cin%20%5Cmathbb%7BZ%7D_%7B%5Cgeq%200%7D%24%20(how%20many%20times%20to%20use%20pattern%20%24p%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_%7Bp%20%5Cin%20%5Ctext%7BPAT1%7D%7D%20%5Ctext%7BLEN%7D_1%20%5Ccdot%20%5Cmathit%7Buse%7D_p%20%2B%20%5Csum_%7Bp%20%5Cin%20%5Ctext%7BPAT2%7D%7D%20%5Ctext%7BLEN%7D_2%20%5Ccdot%20%5Cmathit%7Buse%7D_p%20-%20%5Csum_%7Bs%20%5Cin%20%5Ctext%7BSIZES%7D%7D%204%20%5Ccdot%20%5Ctext%7BDEM%7D_s%20%5Ccdot%20s%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bp%20%5Cin%20%5Ctext%7BPATTERNS%7D%7D%20%5Ctext%7BCUT%7D_%7Bp%2Cs%7D%20%5Ccdot%20%5Cmathit%7Buse%7D_p%20%5Cgeq%204%20%5Ccdot%20%5Ctext%7BDEM%7D_s%20%5Cquad%20%5Cforall%5C%2C%20s%20%5Cin%20%5Ctext%7BSIZES%7D%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20The%20factor%20of%204%20accounts%20for%204%20legs%20per%20desk.%20The%20objective%20is%20the%20total%20bar%20material%20used%20minus%20total%20leg%20material%20needed.%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%20demands%20by%20desk%20height%20and%20the%20cutting%20patterns%20in%20Python.%20The%20data%20is%20defined%20here%20for%20readability%2C%20but%20the%20model%20reads%20from%20the%20%60.dat%60%20file.%20Unlike%20%60set%20of%20string%60%2C%20a%20%60set%20of%20integer%60%20index%20(%60SIZES%60)%20cannot%20be%20initialized%20via%20the%20%60moselpy%3A%60%20driver%20-%20this%20is%20the%20same%20limitation%20as%20%60array%20of%20set%20of%20integer%60%20seen%20in%20%60a5mine2_p%60%20and%20%60b1stadium2_p%60.%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%20DEM_1%20%3D%20%7B40%3A%20108%2C%2060%3A%20125%2C%2070%3A%20100%7D%0A%20%20%20%20LEN%20%3D%20%7B1%3A%20150%2C%202%3A%20200%7D%0A%20%20%20%20CUT_1%20%3D%20%7B(1%2C%2040)%3A%200%2C%20(1%2C%2060)%3A%200%2C%20(1%2C%2070)%3A%202%2C%20(2%2C%2040)%3A%200%2C%20(2%2C%2060)%3A%201%2C%20(2%2C%2070)%3A%201%2C%20(3%2C%2040)%3A%202%2C%20(3%2C%2060)%3A%200%2C%20(3%2C%2070)%3A%201%2C%20(4%2C%2040)%3A%200%2C%20(4%2C%2060)%3A%202%2C%20(4%2C%2070)%3A%200%2C%20(5%2C%2040)%3A%202%2C%20(5%2C%2060)%3A%201%2C%20(5%2C%2070)%3A%200%2C%20(6%2C%2040)%3A%203%2C%20(6%2C%2060)%3A%200%2C%20(6%2C%2070)%3A%200%2C%20(7%2C%2040)%3A%200%2C%20(7%2C%2060)%3A%201%2C%20(7%2C%2070)%3A%202%2C%20(8%2C%2040)%3A%200%2C%20(8%2C%2060)%3A%202%2C%20(8%2C%2070)%3A%201%2C%20(9%2C%2040)%3A%201%2C%20(9%2C%2060)%3A%200%2C%20(9%2C%2070)%3A%202%2C%20(10%2C%2040)%3A%203%2C%20(10%2C%2060)%3A%200%2C%20(10%2C%2070)%3A%201%2C%20(11%2C%2040)%3A%200%2C%20(11%2C%2060)%3A%203%2C%20(11%2C%2070)%3A%200%2C%20(12%2C%2040)%3A%205%2C%20(12%2C%2060)%3A%200%2C%20(12%2C%2070)%3A%200%7D%0A%20%20%20%20print('Demands%20by%20height%20(x4%20legs%20per%20desk)%3A')%0A%20%20%20%20for%20_s%2C%20_d%20in%20DEM_1.items()%3A%0A%20%20%20%20%20%20%20%20print(f'%20%20Height%20%7B_s%7D%20cm%3A%20%7B_d%7D%20desks%20%3D%20%7B4%20*%20_d%7D%20legs')%0A%20%20%20%20print(f'%5CnBar%20lengths%3A%20%7BLEN%7D')%0A%20%20%20%20return%20CUT_1%2C%20DEM_1%2C%20LEN%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%20%60d6cutbar_p.mos%60%20reading%20from%20the%20%60.dat%60%20file.%20After%20solving%2C%20%60obj_val%60%20is%20the%20trim%20loss%20in%20cm%20and%20%60sol_use%60%20maps%20pattern%20index%20to%20usage%20count.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CUT_1%2C%20DATA%2C%20DEM_1%2C%20LEN%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20_model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fd6cutbar_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20f'%7BDATA%7D%2Fd6cutbar_p.dat'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D)%0A%20%20%20%20if%20_output%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20obj_val_d6%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_use_d6%20%3D%20_output%5B'sol_use'%5D%0A%20%20%20%20%20%20%20%20short_used%20%3D%20sum((sol_use_d6.get(p%2C%200)%20for%20p%20in%20range(1%2C%207)))%0A%20%20%20%20%20%20%20%20long_used%20%3D%20sum((sol_use_d6.get(p%2C%200)%20for%20p%20in%20range(7%2C%2013)))%0A%20%20%20%20%20%20%20%20print(f'Total%20trim%20loss%3A%20%7Bobj_val_d6%3A.0f%7D%20cm')%0A%20%20%20%20%20%20%20%20print(f'Short%20bars%20(150%20cm)%20used%3A%20%7Bshort_used%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Long%20bars%20%20(200%20cm)%20used%3A%20%7Blong_used%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print('Patterns%20used%3A')%0A%20%20%20%20%20%20%20%20for%20_p%20in%20range(1%2C%2013)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_qty%20%3D%20sol_use_d6.get(_p%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_qty%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bar_len%20%3D%20LEN%5B1%5D%20if%20_p%20%3C%3D%206%20else%20LEN%5B2%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pieces%20%3D%20%5Bf'%7Bs%7Dcm%20x%7BCUT_1.get((_p%2C%20s)%2C%200)%7D'%20for%20s%20in%20%5B40%2C%2060%2C%2070%5D%20if%20CUT_1.get((_p%2C%20s)%2C%200)%20%3E%200%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Pattern%20%7B_p%3A2d%7D%20(bar%3D%7Bbar_len%7Dcm)%20x%20%7B_qty%3A.0f%7D%3A%20%20%7B'%2C%20'.join(_pieces)%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_s%20in%20%5B40%2C%2060%2C%2070%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_produced%20%3D%20sum((CUT_1.get((p%2C%20_s)%2C%200)%20*%20sol_use_d6.get(p%2C%200)%20for%20p%20in%20range(1%2C%2013)))%0A%20%20%20%20%20%20%20%20%20%20%20%20needed%20%3D%204%20*%20DEM_1%5B_s%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Height%20%7B_s%7D%20cm%3A%20%7B_produced%3A.0f%7D%20legs%20produced%2C%20%7Bneeded%7D%20needed')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20output%20returned')%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
832585989085b69503e06dd2ddbdbcdb