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%20Ground%20Transport%22%0A%23%20description%20%3D%20%22Fleet%2C%20min-cost%20flow%2C%20depots%2C%20delivery%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-groundtransport.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%20E%3A%20Ground%20Transport**%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_ground_transport.py***%20-%20Ground%20transport%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20fleet%20management%2C%20network%20flow%2C%20depot%20location%2C%20vehicle%20routing%2C%20combined%20transport%2C%20and%20van%20rental%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%20%20%20%20import%20copy%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%22ground_transport%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22ground_transport%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%20MODELS%2C%20copy%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%20Ground%20transport%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%20examples%20from%20Chapter%20E%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20range%20from%20fleet%20management%20and%20network%20flow%20to%20depot%20location%2C%20vehicle%20routing%2C%20combined%20transport%20mode%20selection%2C%20and%20van%20rental%20planning.%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Fground_transport%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Fground_transport%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Fground_transport)%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_5)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**%60copy.deepcopy(model)%60**%3A%20E-1%20clones%20a%20loaded%20car%20rental%20model%20to%20run%20two%20stock%20scenarios%20without%20recompiling.%0A%20%20%20%20-%20**Sparse%20dict%20input**%3A%20E-2%20passes%20only%20the%20non-zero%20entries%20of%20%60MINCAP%60%2C%20%60MAXCAP%60%2C%20and%20%60COST%60%3B%20Mosel%20fills%20missing%20values%20with%20zero%20automatically.%0A%20%20%20%20-%20**%60model.stop()%60%20with%20threading**%3A%20E-4%20demonstrates%20graceful%20early%20termination%20of%20a%20running%20MIP%20from%20a%20background%20thread.%0A%20%20%20%20-%20**%60pd.DataFrame%60%20for%20string-indexed%20input**%3A%20E-5%20passes%20%60CTRANS%60%20(transport%20modes%20x%20legs)%20as%20a%20DataFrame%3B%20the%20%60MODES%60%20set%20of%20strings%20auto-populates%20from%20the%20row%20index.%0A%20%20%20%20-%20**%60exec_params%60%20sensitivity**%3A%20E-6%20varies%20%60NINIT%60%20(initial%20van%20fleet%20size)%20across%20a%20parametric%20loop%20and%20collects%20total%20cost%20into%20a%20%60pd.Series%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%20E-1%20Car%20rental%20fleet%20management%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%20E%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e1carrent_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe1carrent_p.mos)%0A%0A%20%20%20%20A%20car%20rental%20company%20operates%20at%2010%20locations.%20Each%20location%20has%20a%20current%20stock%20of%20cars%20and%20a%20daily%20requirement.%20Agents%20with%20surplus%20cars%20must%20transfer%20them%20to%20agents%20with%20unmet%20demand.%20The%20transfer%20cost%20is%20proportional%20to%20the%20Euclidean%20distance%20between%20locations.%20The%20model%20minimizes%20total%20transfer%20cost.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BAGENTS%7D%20%3D%201..10%24%2C%20%24%5Ctext%7BEXCESS%7D%20%5Csubseteq%20%5Ctext%7BAGENTS%7D%24%20(surplus%20locations)%2C%20%24%5Ctext%7BNEED%7D%20%5Csubseteq%20%5Ctext%7BAGENTS%7D%24%20(deficit%20locations)%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BREQ%7D_a%24%20-%20required%20cars%3B%20%24%5Ctext%7BSTOCK%7D_a%24%20-%20cars%20in%20stock%3B%20%24%5Ctext%7BCOST%7D%24%20-%20cost%20per%20unit%20distance%3B%20%24%5Ctext%7BDIST%7D_%7Be%2Cn%7D%20%3D%20%5Csqrt%7B(X_e%20-%20X_n)%5E2%20%2B%20(Y_e%20-%20Y_n)%5E2%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bmove%7D_%7Be%2Cn%7D%20%5Cgeq%200%24%20-%20cars%20transferred%20from%20excess%20location%20%24e%24%20to%20need%20location%20%24n%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_%7Be%20%5Cin%20%5Ctext%7BEXCESS%7D%2C%5C%2C%20n%20%5Cin%20%5Ctext%7BNEED%7D%7D%20%5Ctext%7BCOST%7D%20%5Ccdot%20%5Ctext%7BDIST%7D_%7Be%2Cn%7D%20%5Ccdot%20%5Cmathit%7Bmove%7D_%7Be%2Cn%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Be%20%5Cin%20%5Ctext%7BEXCESS%7D%7D%20%5Cmathit%7Bmove%7D_%7Be%2Cn%7D%20%5Cgeq%20%5Ctext%7BREQ%7D_n%20-%20%5Ctext%7BSTOCK%7D_n%20%5Cquad%20%5Cforall%5C%2C%20n%20%5Cin%20%5Ctext%7BNEED%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_%7Bn%20%5Cin%20%5Ctext%7BNEED%7D%7D%20%5Cmathit%7Bmove%7D_%7Be%2Cn%7D%20%5Cleq%20%5Ctext%7BSTOCK%7D_e%20-%20%5Ctext%7BREQ%7D_e%20%5Cquad%20%5Cforall%5C%2C%20e%20%5Cin%20%5Ctext%7BEXCESS%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%20location%20data%20in%20Python%3A%20car%20requirements%20%60REQ%60%2C%20current%20stock%20%60STOCK%60%2C%20and%20coordinates%20%60X%60%2C%20%60Y%60%2C%20all%20as%201-indexed%20dicts.%20The%20base%20transfer%20cost%20per%20unit%20distance%20%60COST%60%20is%20passed%20as%20a%20scalar%20via%20%60exec_params%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%20REQ%20%20%20%20%20%20%20%20%20%3D%20%7B1%3A10%2C%202%3A6%2C%20%203%3A8%2C%20%204%3A11%2C%205%3A9%2C%20%206%3A7%2C%20%207%3A15%2C%208%3A7%2C%20%209%3A9%2C%20%2010%3A12%7D%0A%20%20%20%20STOCK_BASE%20%20%3D%20%7B1%3A8%2C%20%202%3A13%2C%203%3A4%2C%20%204%3A8%2C%20%205%3A12%2C%206%3A2%2C%20%207%3A14%2C%208%3A11%2C%209%3A15%2C%2010%3A7%7D%0A%20%20%20%20STOCK_REDI%20%20%3D%20%7B1%3A8%2C%20%202%3A8%2C%20%203%3A4%2C%20%204%3A8%2C%20%205%3A12%2C%206%3A2%2C%20%207%3A14%2C%208%3A11%2C%209%3A20%2C%2010%3A7%7D%0A%20%20%20%20X%20%20%20%20%20%20%20%20%20%20%20%3D%20%7B1%3A0%2C%20%202%3A20%2C%203%3A18%2C%204%3A30%2C%205%3A35%2C%206%3A33%2C%207%3A5%2C%20%208%3A5%2C%20%209%3A11%2C%2010%3A2%7D%0A%20%20%20%20Y%20%20%20%20%20%20%20%20%20%20%20%3D%20%7B1%3A0%2C%20%202%3A20%2C%203%3A10%2C%204%3A12%2C%205%3A0%2C%20%206%3A25%2C%207%3A27%2C%208%3A10%2C%209%3A0%2C%20%2010%3A15%7D%0A%0A%20%20%20%20%23%20NEED%2FEXCESS%20membership%20is%20the%20same%20in%20both%20scenarios%0A%20%20%20%20need_agents%20%20%20%3D%20%5Ba%20for%20a%20in%20range(1%2C%2011)%20if%20REQ%5Ba%5D%20%3E%20STOCK_BASE%5Ba%5D%5D%0A%20%20%20%20excess_agents%20%3D%20%5Ba%20for%20a%20in%20range(1%2C%2011)%20if%20STOCK_BASE%5Ba%5D%20%3E%20REQ%5Ba%5D%5D%0A%20%20%20%20print(f%22Agents%20needing%20cars%20(NEED)%3A%20%20%20%7Bneed_agents%7D%22)%0A%20%20%20%20print(f%22Agents%20with%20surplus%20(EXCESS)%3A%20%7Bexcess_agents%7D%22)%0A%20%20%20%20print()%0A%20%20%20%20print(%22Surplus%20by%20agent%20-%20base%3A%20%20%20%20%20%20%20%20%22%2C%20%7Ba%3A%20STOCK_BASE%5Ba%5D-REQ%5Ba%5D%20for%20a%20in%20excess_agents%7D)%0A%20%20%20%20print(%22Surplus%20by%20agent%20-%20redistributed%3A%22%2C%20%7Ba%3A%20STOCK_REDI%5Ba%5D-REQ%5Ba%5D%20for%20a%20in%20excess_agents%7D)%0A%20%20%20%20return%20REQ%2C%20STOCK_BASE%2C%20STOCK_REDI%2C%20X%2C%20Y%2C%20excess_agents%2C%20need_agents%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%20section%20demonstrates%20%60copy.deepcopy(model)%60%20-%20a%20MoselPy%20feature%20that%20clones%20a%20fully%20loaded%20(but%20not%20yet%20run)%20model.%20Cloning%20lets%20you%20branch%20off%20multiple%20scenarios%20from%20the%20same%20compiled%20binary%20without%20recompiling.%0A%0A%20%20%20%20Here%20we%20compare%20two%20stock%20distributions%20with%20the%20same%20total%20fleet%3A%20in%20the%20base%20scenario%20agent%202%20(upper-right%2C%20coordinates%20(20%2C20))%20holds%2013%20cars%3B%20in%20the%20redistributed%20scenario%20its%20surplus%20is%20transferred%20to%20agent%209%20(lower-centre%2C%20(11%2C0)).%20Because%20the%20surplus%20is%20now%20concentrated%20at%20a%20different%20location%2C%20the%20optimal%20routing%20changes%20completely.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20REQ%2C%20STOCK_BASE%2C%20STOCK_REDI%2C%20X%2C%20Y%2C%20copy%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fe1carrent_p.mos'%2C%20f'%7BMODELS%7D%2Fe1carrent_p.bim')%0A%20%20%20%20model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fe1carrent_p.bim')%0A%20%20%20%20model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20model_base%20%3D%20copy.deepcopy(model)%0A%20%20%20%20model_redi%20%3D%20copy.deepcopy(model)%0A%20%20%20%20out_base%20%3D%20model_base.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'COST'%3A%200.5%7D%2C%20input_data%3D%7B'REQ'%3A%20REQ%2C%20'STOCK'%3A%20STOCK_BASE%2C%20'X'%3A%20X%2C%20'Y'%3A%20Y%7D)%0A%20%20%20%20out_redi%20%3D%20model_redi.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'COST'%3A%200.5%7D%2C%20input_data%3D%7B'REQ'%3A%20REQ%2C%20'STOCK'%3A%20STOCK_REDI%2C%20'X'%3A%20X%2C%20'Y'%3A%20Y%7D)%0A%20%20%20%20for%20label%2C%20model_%2C%20_out%20in%20%5B('Base%20(agent%202%3A%20surplus%207%2C%20agent%209%3A%20surplus%206)'%2C%20model_base%2C%20out_base)%2C%20('Redistributed%20(agent%202%3A%20surplus%202%2C%20agent%209%3A%20surplus%2011)'%2C%20model_redi%2C%20out_redi)%5D%3A%0A%20%20%20%20%20%20%20%20if%20model_.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20transfers%20%3D%20%5B(e%2C%20n%2C%20v)%20for%20(e%2C%20n)%2C%20v%20in%20_out%5B'sol_move'%5D.items()%20if%20v%20%3E%200.01%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7Blabel%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Total%20transfer%20cost%3A%20%7B_out%5B'obj_val'%5D%3A.2f%7D%20%20%20Routes%3A%20%7Blen(transfers)%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_e%2C%20_n%2C%20_v%20in%20sorted(transfers)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%20%20Agent%20%7B_e%7D%20-%3E%20Agent%20%7B_n%7D%3A%20%7B_v%3A.1f%7D%20cars')%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7Blabel%7D%3A%20No%20solution%20found%20(%7Bmodel_.prob_status%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20sol_move_base%20%3D%20out_base%5B'sol_move'%5D%0A%20%20%20%20sol_move_redi%20%3D%20out_redi%5B'sol_move'%5D%0A%20%20%20%20return%20sol_move_base%2C%20sol_move_redi%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%20Map%20of%20the%2010%20rental%20locations%20showing%20car%20transfers%20for%20both%20stock%20scenarios.%20Arrows%20indicate%20car%20movements%3B%20arrow%20thickness%20reflects%20the%20number%20of%20cars%20transferred.%20Green%20squares%20are%20surplus%20locations%3B%20red%20circles%20are%20deficit%20locations.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20X%2C%0A%20%20%20%20Y%2C%0A%20%20%20%20excess_agents%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20mpatches%2C%0A%20%20%20%20need_agents%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20sol_move_base%2C%0A%20%20%20%20sol_move_redi%2C%0A)%3A%0A%20%20%20%20if%20'sol_move_base'%20in%20locals()%20and%20'sol_move_redi'%20in%20locals()%3A%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%205))%0A%20%20%20%20%20%20%20%20for%20_ax%2C%20sol_move%2C%20cost_label%20in%20%5B(_axes%5B0%5D%2C%20sol_move_base%2C%20'Base%20(agent%202%3A%20%2B7%2C%20agent%209%3A%20%2B6)')%2C%20(_axes%5B1%5D%2C%20sol_move_redi%2C%20'Redistributed%20(agent%202%3A%20%2B2%2C%20agent%209%3A%20%2B11)')%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_a%20in%20range(1%2C%2011)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_a%20in%20excess_agents%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(X%5B_a%5D%2C%20Y%5B_a%5D%2C%20s%3D180%2C%20color%3D'%2343A047'%2C%20marker%3D's'%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20_a%20in%20need_agents%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(X%5B_a%5D%2C%20Y%5B_a%5D%2C%20s%3D180%2C%20color%3D'%23E53935'%2C%20marker%3D'o'%2C%20zorder%3D3)%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%20_ax.scatter(X%5B_a%5D%2C%20Y%5B_a%5D%2C%20s%3D100%2C%20color%3D'%2390CAF9'%2C%20marker%3D'o'%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(str(_a)%2C%20(X%5B_a%5D%2C%20Y%5B_a%5D)%2C%20textcoords%3D'offset%20points'%2C%20xytext%3D(5%2C%205)%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(_e%2C%20_n)%2C%20_v%20in%20sol_move.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_v%20%3E%200.01%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lw%20%3D%201%20%2B%20_v%20*%200.4%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(X%5B_n%5D%2C%20Y%5B_n%5D)%2C%20xytext%3D(X%5B_e%5D%2C%20Y%5B_e%5D)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3D'%231565C0'%2C%20lw%3Dlw%2C%20connectionstyle%3D'arc3%2Crad%3D0.1'))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mid_x%20%3D%20(X%5B_e%5D%20%2B%20X%5B_n%5D)%20%2F%202%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mid_y%20%3D%20(Y%5B_e%5D%20%2B%20Y%5B_n%5D)%20%2F%202%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(mid_x%2C%20mid_y%2C%20f'%7B_v%3A.0f%7D'%2C%20fontsize%3D7%2C%20color%3D'%231565C0'%2C%20ha%3D'center'%2C%20va%3D'center')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_xlabel('X%20coordinate')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_ylabel('Y%20coordinate')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_title(f'E-1%20Car%20rental%20transfers%20(%7Bcost_label%7D)')%0A%20%20%20%20%20%20%20%20%20%20%20%20green_patch%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3D'Surplus%20(EXCESS)')%0A%20%20%20%20%20%20%20%20%20%20%20%20red_patch%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Deficit%20(NEED)')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bgreen_patch%2C%20red_patch%5D%2C%20fontsize%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%20car%20rental%20map')%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%20E-2%20Minimum%20cost%20flow%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%20E%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e2minflow_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe2minflow_p.mos)%0A%0A%20%20%20%20A%20transport%20network%20connects%20a%20single%20source%20to%20three%20customers%20(C1%2C%20C2%2C%20C3)%20via%20four%20depots%20(D1-D4)%20using%20rail%20and%20road%20links.%20Flow%20must%20reach%20a%20minimum%20total%20throughput%20of%20180%20units.%20Some%20arcs%20have%20minimum%20and%20maximum%20capacity%20constraints.%20Transport%20costs%20vary%20by%20arc.%20The%20model%20minimizes%20total%20cost.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BARCS%7D%20%3D%201..24%24%2C%20%24%5Ctext%7BNODES%7D%24%20(auto-built%20from%20arc%20endpoints)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bflow%7D_a%20%5Cgeq%200%24%20for%20each%20arc%20%24a%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_%7Ba%20%5Cin%20%5Ctext%7BARCS%7D%7D%20%5Ctext%7BCOST%7D_a%20%5Ccdot%20%5Cmathit%7Bflow%7D_a%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Ba%3A%5C%2C%20A_%7Ba%2C2%7D%3Dn%7D%20%5Cmathit%7Bflow%7D_a%20%3D%20%5Csum_%7Ba%3A%5C%2C%20A_%7Ba%2C1%7D%3Dn%7D%20%5Cmathit%7Bflow%7D_a%20%5Cquad%20%5Cforall%5C%2C%20n%20%5Cnotin%20%5C%7B%5Ctext%7BSOURCE%7D%2C%5Ctext%7BSINK%7D%5C%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%7Bflow%7D_a%20%5Cgeq%20%5Ctext%7BMINCAP%7D_a%20%5Cquad%20%5Cforall%5C%2C%20a%20%5Ctext%7B%20with%20minimum%20capacity%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%7Bflow%7D_a%20%5Cleq%20%5Ctext%7BMAXCAP%7D_a%20%5Cquad%20%5Cforall%5C%2C%20a%20%5Ctext%7B%20with%20maximum%20capacity%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_%7Ba%3A%5C%2C%20A_%7Ba%2C1%7D%3D%5Ctext%7BSOURCE%7D%7D%20%5Cmathit%7Bflow%7D_a%20%5Cgeq%20%5Ctext%7BMINQ%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%20arc%20data%20in%20Python.%20%60A%60%20is%20a%202D%20dict%20with%20keys%20%60(arc_index%2C%201)%60%20and%20%60(arc_index%2C%202)%60%20holding%20the%20from-%20and%20to-node%20names.%20%60MINCAP%60%2C%20%60MAXCAP%60%2C%20and%20%60COST%60%20are%20sparse%201D%20dicts%20-%20only%20arcs%20with%20non-zero%20values%20are%20listed.%20Passing%20sparse%20dicts%20is%20efficient%3A%20Mosel%20initializes%20missing%20entries%20to%20zero%20automatically.%0A%0A%20%20%20%20A%20%60pd.DataFrame%60%20is%20built%20from%20%60A%60%20for%20display.%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%20A%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(1%2C1)%3A%22SOURCE%22%2C%20%20(1%2C2)%3A%22D1%22%2C%0A%20%20%20%20%20%20%20%20(2%2C1)%3A%22SOURCE%22%2C%20%20(2%2C2)%3A%22D2%22%2C%0A%20%20%20%20%20%20%20%20(3%2C1)%3A%22SOURCE%22%2C%20%20(3%2C2)%3A%22D3%22%2C%0A%20%20%20%20%20%20%20%20(4%2C1)%3A%22SOURCE%22%2C%20%20(4%2C2)%3A%22D4%22%2C%0A%20%20%20%20%20%20%20%20(5%2C1)%3A%22D1%22%2C%20%20%20%20%20%20(5%2C2)%3A%22road-C1%22%2C%0A%20%20%20%20%20%20%20%20(6%2C1)%3A%22D1%22%2C%20%20%20%20%20%20(6%2C2)%3A%22road-C2%22%2C%0A%20%20%20%20%20%20%20%20(7%2C1)%3A%22D2%22%2C%20%20%20%20%20%20(7%2C2)%3A%22rail-C1%22%2C%0A%20%20%20%20%20%20%20%20(8%2C1)%3A%22D2%22%2C%20%20%20%20%20%20(8%2C2)%3A%22road-C1%22%2C%0A%20%20%20%20%20%20%20%20(9%2C1)%3A%22D3%22%2C%20%20%20%20%20%20(9%2C2)%3A%22road-C2%22%2C%0A%20%20%20%20%20%20%20%20(10%2C1)%3A%22D3%22%2C%20%20%20%20%20(10%2C2)%3A%22rail-C3%22%2C%0A%20%20%20%20%20%20%20%20(11%2C1)%3A%22D3%22%2C%20%20%20%20%20(11%2C2)%3A%22road-C3%22%2C%0A%20%20%20%20%20%20%20%20(12%2C1)%3A%22D4%22%2C%20%20%20%20%20(12%2C2)%3A%22rail-C2%22%2C%0A%20%20%20%20%20%20%20%20(13%2C1)%3A%22D4%22%2C%20%20%20%20%20(13%2C2)%3A%22road-C2%22%2C%0A%20%20%20%20%20%20%20%20(14%2C1)%3A%22D4%22%2C%20%20%20%20%20(14%2C2)%3A%22rail-C3%22%2C%0A%20%20%20%20%20%20%20%20(15%2C1)%3A%22D4%22%2C%20%20%20%20%20(15%2C2)%3A%22road-C3%22%2C%0A%20%20%20%20%20%20%20%20(16%2C1)%3A%22rail-C1%22%2C(16%2C2)%3A%22C1%22%2C%0A%20%20%20%20%20%20%20%20(17%2C1)%3A%22road-C1%22%2C(17%2C2)%3A%22C1%22%2C%0A%20%20%20%20%20%20%20%20(18%2C1)%3A%22rail-C2%22%2C(18%2C2)%3A%22C2%22%2C%0A%20%20%20%20%20%20%20%20(19%2C1)%3A%22road-C2%22%2C(19%2C2)%3A%22C2%22%2C%0A%20%20%20%20%20%20%20%20(20%2C1)%3A%22rail-C3%22%2C(20%2C2)%3A%22C3%22%2C%0A%20%20%20%20%20%20%20%20(21%2C1)%3A%22road-C3%22%2C(21%2C2)%3A%22C3%22%2C%0A%20%20%20%20%20%20%20%20(22%2C1)%3A%22C1%22%2C%20%20%20%20%20(22%2C2)%3A%22SINK%22%2C%0A%20%20%20%20%20%20%20%20(23%2C1)%3A%22C2%22%2C%20%20%20%20%20(23%2C2)%3A%22SINK%22%2C%0A%20%20%20%20%20%20%20%20(24%2C1)%3A%22C3%22%2C%20%20%20%20%20(24%2C2)%3A%22SINK%22%2C%0A%20%20%20%20%7D%0A%20%20%20%20MINQ%20%20%20%20%3D%20180%0A%20%20%20%20MINCAP%20%20%3D%20%7B7%3A10%2C%2010%3A10%2C%2012%3A10%2C%2014%3A10%7D%0A%20%20%20%20MAXCAP%20%20%3D%20%7B1%3A50%2C%202%3A40%2C%20%203%3A35%2C%20%204%3A65%2C%20%207%3A50%2C%2010%3A50%2C%2012%3A50%2C%2014%3A50%7D%0A%20%20%20%20COST_ARC%20%3D%20%7B5%3A12%2C%206%3A11%2C%207%3A12%2C%20%208%3A14%2C%20%209%3A9%2C%20%2010%3A4%2C%20%2011%3A5%2C%20%2012%3A11%2C%2013%3A14%2C%2014%3A10%2C%2015%3A14%7D%0A%0A%20%20%20%20%23%20Build%20arc%20DataFrame%20for%20display%0A%20%20%20%20arc_df%20%3D%20pd.DataFrame(%0A%20%20%20%20%20%20%20%20%7B%22From%22%3A%20%5BA%5B(i%2C1)%5D%20for%20i%20in%20range(1%2C25)%5D%2C%0A%20%20%20%20%20%20%20%20%20%22To%22%3A%20%20%20%5BA%5B(i%2C2)%5D%20for%20i%20in%20range(1%2C25)%5D%2C%0A%20%20%20%20%20%20%20%20%20%22MinCap%22%3A%20%5BMINCAP.get(i%2C%200)%20for%20i%20in%20range(1%2C25)%5D%2C%0A%20%20%20%20%20%20%20%20%20%22MaxCap%22%3A%20%5BMAXCAP.get(i%2C%200)%20for%20i%20in%20range(1%2C25)%5D%2C%0A%20%20%20%20%20%20%20%20%20%22Cost%22%3A%20%20%20%5BCOST_ARC.get(i%2C%200)%20for%20i%20in%20range(1%2C25)%5D%7D%2C%0A%20%20%20%20%20%20%20%20index%3Drange(1%2C%2025)%2C%0A%20%20%20%20)%0A%20%20%20%20arc_df.index.name%20%3D%20%22Arc%22%0A%20%20%20%20print(%22Network%20arcs%3A%22)%0A%20%20%20%20print(arc_df.to_string())%0A%20%20%20%20return%20A%2C%20COST_ARC%2C%20MAXCAP%2C%20MINCAP%2C%20MINQ%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%20single%20execution%20command.%20Note%20how%20%60COST_ARC%60%20is%20passed%20under%20the%20name%20%60COST%60%20to%20match%20the%20Mosel%20identifier.%20After%20solving%2C%20%60sol_flow%60%20is%20a%20dict%20mapping%20arc%20index%20to%20flow%20value.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(A%2C%20COST_ARC%2C%20MAXCAP%2C%20MINCAP%2C%20MINQ%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_1%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fe2minflow_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'MINQ'%3A%20MINQ%7D%2C%20input_data%3D%7B'A'%3A%20A%2C%20'MINCAP'%3A%20MINCAP%2C%20'MAXCAP'%3A%20MAXCAP%2C%20'COST'%3A%20COST_ARC%7D)%0A%20%20%20%20if%20_output%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20obj_val_e2%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_flow%20%3D%20_output%5B'sol_flow'%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20cost%3A%20%7Bobj_val_e2%3A.2f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20total_out%20%3D%20sum((sol_flow.get(a%2C%200)%20for%20a%20in%20%5B1%2C%202%2C%203%2C%204%5D))%0A%20%20%20%20%20%20%20%20print(f'Total%20flow%20from%20SOURCE%3A%20%7Btotal_out%3A.1f%7D%20%20(minimum%20required%3A%20%7BMINQ%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f'%7B'Arc'%3A%3E4%7D%20%20%7B'From'%3A%3C12%7D%20%7B'To'%3A%3C12%7D%20%7B'Flow'%3A%3E6%7D%20%7B'MinCap'%3A%3E7%7D%20%7B'MaxCap'%3A%3E7%7D%20%7B'Cost'%3A%3E5%7D')%0A%20%20%20%20%20%20%20%20for%20_a%20in%20range(1%2C%2025)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20flow_a%20%3D%20sol_flow.get(_a%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20flow_a%20%3E%200.01%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_a%3A%3E2%7D%20%20%7BA%5B_a%2C%201%5D%3A%3C12%7D%20%7BA%5B_a%2C%202%5D%3A%3C12%7D%20%7Bflow_a%3A%3E6.1f%7D%20%7BMINCAP.get(_a%2C%200)%3A%3E7%7D%20%7BMAXCAP.get(_a%2C%200)%3A%3E7%7D%20%7BCOST_ARC.get(_a%2C%200)%3A%3E5%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_e2%2C%20sol_flow%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%20Bar%20chart%20of%20arc%20flows%20for%20all%20arcs%20carrying%20positive%20flow.%20Arcs%20are%20colored%20by%20transport%20mode%20(SOURCE%2Fsink%20connectors%20in%20gray%2C%20rail%20in%20blue%2C%20road%20in%20orange).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(A%2C%20mo%2C%20mpatches%2C%20obj_val_e2%2C%20plt%2C%20sol_flow)%3A%0A%20%20%20%20if%20'sol_flow'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20active_arcs%20%3D%20%5B(a%2C%20sol_flow.get(a%2C%200))%20for%20a%20in%20range(1%2C%2025)%20if%20sol_flow.get(a%2C%200)%20%3E%200.01%5D%0A%0A%20%20%20%20%20%20%20%20def%20arc_color(a)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20from_node%20%3D%20A%5Ba%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20to_node%20%3D%20A%5Ba%2C%202%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20'rail'%20in%20from_node%20or%20'rail'%20in%20to_node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20'%231E88E5'%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20'road'%20in%20from_node%20or%20'road'%20in%20to_node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20'%23FB8C00'%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20'%2378909C'%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%20labels%20%3D%20%5Bf'%7BA%5Ba%2C%201%5D%7D%5Cn-%3E%7BA%5Ba%2C%202%5D%7D'%20for%20a%2C%20_%20in%20active_arcs%5D%0A%20%20%20%20%20%20%20%20flows%20%3D%20%5Bf%20for%20_%2C%20f%20in%20active_arcs%5D%0A%20%20%20%20%20%20%20%20colors%20%3D%20%5Barc_color(a)%20for%20a%2C%20_%20in%20active_arcs%5D%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(range(len(active_arcs))%2C%20flows%2C%20color%3Dcolors%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(len(active_arcs)))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(labels%2C%20rotation%3D40%2C%20ha%3D'right'%2C%20fontsize%3D7)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Flow')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'E-2%20Minimum%20cost%20flow%20-%20arc%20flows%20(total%20cost%3D%7Bobj_val_e2%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20rail_patch%20%3D%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'Rail%20arc')%0A%20%20%20%20%20%20%20%20road_patch%20%3D%20mpatches.Patch(color%3D'%23FB8C00'%2C%20label%3D'Road%20arc')%0A%20%20%20%20%20%20%20%20gray_patch%20%3D%20mpatches.Patch(color%3D'%2378909C'%2C%20label%3D'Source%2Fsink%20arc')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Brail_patch%2C%20road_patch%2C%20gray_patch%5D%2C%20fontsize%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%20flow%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%20E-3%20Depot%20location%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%20E%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e3depot_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe3depot_p.mos)%0A%0A%20%20%20%20A%20company%20must%20decide%20which%20depots%20to%20open%20(out%20of%2012%20candidates)%20and%20how%20to%20assign%2012%20customers%20to%20open%20depots.%20Each%20depot%20has%20a%20fixed%20opening%20cost%20and%20a%20capacity.%20Transport%20cost%20depends%20on%20the%20depot-customer%20pair.%20The%20model%20minimizes%20total%20fixed%20plus%20transport%20cost.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BDEPOTS%7D%20%3D%201..12%24%2C%20%24%5Ctext%7BCUST%7D%20%3D%201..12%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bbuild%7D_d%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20depot%20%24d%24%20is%20opened)%3B%20%24%5Cmathit%7Bfflow%7D_%7Bd%2Cc%7D%20%5Cin%20%5B0%2C1%5D%24%20(fraction%20of%20customer%20%24c%24%20demand%20served%20by%20depot%20%24d%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_%7Bd%7D%20%5Ctext%7BCFIX%7D_d%20%5Ccdot%20%5Cmathit%7Bbuild%7D_d%20%2B%20%5Csum_%7Bd%2Cc%7D%20%5Ctext%7BCOST%7D_%7Bd%2Cc%7D%20%5Ccdot%20%5Ctext%7BDEM%7D_c%20%5Ccdot%20%5Cmathit%7Bfflow%7D_%7Bd%2Cc%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bd%7D%20%5Cmathit%7Bfflow%7D_%7Bd%2Cc%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20c%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_%7Bc%7D%20%5Ctext%7BDEM%7D_c%20%5Ccdot%20%5Cmathit%7Bfflow%7D_%7Bd%2Cc%7D%20%5Cleq%20%5Ctext%7BCAP%7D_d%20%5Ccdot%20%5Cmathit%7Bbuild%7D_d%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%20the%2012x12%20transport%20cost%20matrix%20as%20a%20%60pd.DataFrame%60%20with%20depot%20indices%20as%20rows%20and%20customer%20indices%20as%20columns.%20Passing%20a%20DataFrame%20directly%20via%20%60input_data%60%20is%20a%20convenient%20way%20to%20supply%202D%20data%20-%20MoselPy%20maps%20the%20row%20and%20column%20indices%20to%20the%20Mosel%202D%20array.%20Fixed%20costs%20%60CFIX%60%2C%20capacities%20%60CAP%60%2C%20and%20demands%20%60DEM%60%20are%201-indexed%20dicts.%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%20cost_rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B100%2C%2080%2C%20%2050%2C%20%2050%2C%20%2060%2C%20%20100%2C%20120%2C%2090%2C%20%2060%2C%20%2070%2C%20%2065%2C%20%20110%5D%2C%0A%20%20%20%20%20%20%20%20%5B120%2C%2090%2C%20%2060%2C%20%2070%2C%20%2065%2C%20%20110%2C%20140%2C%20110%2C%2080%2C%20%2080%2C%20%2075%2C%20%20130%5D%2C%0A%20%20%20%20%20%20%20%20%5B140%2C%20110%2C%2080%2C%20%2080%2C%20%2075%2C%20%20130%2C%20160%2C%20125%2C%20100%2C%20100%2C%2080%2C%20%20150%5D%2C%0A%20%20%20%20%20%20%20%20%5B160%2C%20125%2C%20100%2C%20100%2C%2080%2C%20%20150%2C%20190%2C%20150%2C%20130%2C%201e6%2C%201e6%2C%201e6%5D%2C%0A%20%20%20%20%20%20%20%20%5B190%2C%20150%2C%20130%2C%201e6%2C%201e6%2C%201e6%2C%20200%2C%20180%2C%20150%2C%201e6%2C%201e6%2C%201e6%5D%2C%0A%20%20%20%20%20%20%20%20%5B200%2C%20180%2C%20150%2C%201e6%2C%201e6%2C%201e6%2C%20100%2C%2080%2C%20%2050%2C%20%2050%2C%20%2060%2C%20%20100%5D%2C%0A%20%20%20%20%20%20%20%20%5B100%2C%2080%2C%20%2050%2C%20%2050%2C%20%2060%2C%20%20100%2C%20120%2C%2090%2C%20%2060%2C%20%2070%2C%20%2065%2C%20%20110%5D%2C%0A%20%20%20%20%20%20%20%20%5B120%2C%2090%2C%20%2060%2C%20%2070%2C%20%2065%2C%20%20110%2C%20140%2C%20110%2C%2080%2C%20%2080%2C%20%2075%2C%20%20130%5D%2C%0A%20%20%20%20%20%20%20%20%5B140%2C%20110%2C%2080%2C%20%2080%2C%20%2075%2C%20%20130%2C%20160%2C%20125%2C%20100%2C%20100%2C%2080%2C%20%20150%5D%2C%0A%20%20%20%20%20%20%20%20%5B160%2C%20125%2C%20100%2C%20100%2C%2080%2C%20%20150%2C%20190%2C%20150%2C%20130%2C%201e6%2C%201e6%2C%201e6%5D%2C%0A%20%20%20%20%20%20%20%20%5B190%2C%20150%2C%20130%2C%201e6%2C%201e6%2C%201e6%2C%20200%2C%20180%2C%20150%2C%201e6%2C%201e6%2C%201e6%5D%2C%0A%20%20%20%20%20%20%20%20%5B200%2C%20180%2C%20150%2C%201e6%2C%201e6%2C%201e6%2C%20100%2C%2080%2C%20%2050%2C%20%2050%2C%20%2060%2C%20%20100%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20COST_DF%20%3D%20pd.DataFrame(cost_rows%2C%20index%3Drange(1%2C13)%2C%20columns%3Drange(1%2C13))%0A%20%20%20%20COST_DF.index.name%20%20%20%3D%20%22Depot%22%0A%20%20%20%20COST_DF.columns.name%20%3D%20%22Customer%22%0A%0A%20%20%20%20CFIX%20%3D%20%7B1%3A3500%2C%20%202%3A9000%2C%20%203%3A10000%2C%204%3A4000%2C%20%205%3A3000%2C%20%206%3A9000%2C%0A%20%20%20%20%20%20%20%20%20%20%20%207%3A9000%2C%20%208%3A3000%2C%20%209%3A4000%2C%20%2010%3A10000%2C%2011%3A9000%2C%2012%3A3500%7D%0A%20%20%20%20CAP%20%20%3D%20%7B1%3A300%2C%202%3A250%2C%203%3A100%2C%204%3A180%2C%205%3A275%2C%206%3A300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%207%3A200%2C%208%3A220%2C%209%3A270%2C%2010%3A250%2C%2011%3A230%2C%2012%3A180%7D%0A%20%20%20%20DEM%20%20%3D%20%7B1%3A120%2C%202%3A80%2C%20%203%3A75%2C%20%204%3A100%2C%205%3A110%2C%206%3A100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%207%3A90%2C%20%208%3A60%2C%20%209%3A30%2C%20%2010%3A150%2C%2011%3A95%2C%2012%3A120%7D%0A%0A%20%20%20%20print(f%22Total%20demand%3A%20%7Bsum(DEM.values())%7D%22)%0A%20%20%20%20print(f%22Total%20capacity%3A%20%7Bsum(CAP.values())%7D%22)%0A%20%20%20%20print(f%22%5CnCost%20matrix%20(first%206%20rows%2Fcols%2C%201e6%20%3D%20infeasible%20route)%3A%22)%0A%20%20%20%20print(COST_DF.iloc%5B%3A6%2C%20%3A6%5D.to_string())%0A%20%20%20%20return%20CAP%2C%20CFIX%2C%20COST_DF%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%20%60e3depot_p.mos%60%20using%20the%20compile-load-run%20sequence%2C%20passing%20%60COST_DF%60%20as%20the%202D%20cost%20matrix.%20After%20solving%2C%20%60sol_build%60%20shows%20which%20depots%20are%20opened%20and%20%60sol_fflow%60%20gives%20the%20fractional%20assignments%20of%20customers%20to%20depots.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP%2C%20CFIX%2C%20COST_DF%2C%20DEM%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fe3depot_p.mos'%2C%20f'%7BMODELS%7D%2Fe3depot_p.bim')%0A%20%20%20%20model_2%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fe3depot_p.bim')%0A%20%20%20%20model_2.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_2.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'COST'%3A%20COST_DF%2C%20'CFIX'%3A%20CFIX%2C%20'CAP'%3A%20CAP%2C%20'DEM'%3A%20DEM%7D)%0A%20%20%20%20if%20model_2.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_e3%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_build%20%3D%20_output%5B'sol_build'%5D%0A%20%20%20%20%20%20%20%20sol_fflow%20%3D%20_output%5B'sol_fflow'%5D%0A%20%20%20%20%20%20%20%20open_depots%20%3D%20%5Bd%20for%20d%20in%20range(1%2C%2013)%20if%20sol_build.get(d%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20total%20cost%3A%20%7Bobj_val_e3%3A%2C.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Open%20depots%3A%20%7Bopen_depots%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20d%20in%20open_depots%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20assigned%20%3D%20%7Bc%3A%20sol_fflow.get((d%2C%20c)%2C%200)%20for%20c%20in%20range(1%2C%2013)%20if%20sol_fflow.get((d%2C%20c)%2C%200)%20%3E%200.01%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20load%20%3D%20sum((DEM%5Bc%5D%20*%20v%20for%20c%2C%20v%20in%20assigned.items()))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Depot%20%7Bd%7D%20(cap%3D%7BCAP%5Bd%5D%7D%2C%20fixed%3D%7BCFIX%5Bd%5D%3A%2C%7D)%3A%20load%3D%7Bload%3A.0f%7D%2C%20customers%3D%7Blist(assigned.keys())%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_2.prob_status%7D)')%0A%20%20%20%20return%20open_depots%2C%20sol_build%2C%20sol_fflow%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%20Two-panel%20visualization%3A%20(left)%20which%20depots%20are%20opened%20vs%20closed%20with%20their%20fixed%20costs%3B%20(right)%20heatmap%20of%20the%20fractional%20flow%20assignments%20from%20open%20depots%20to%20customers.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CFIX%2C%20mo%2C%20mpatches%2C%20np%2C%20open_depots%2C%20plt%2C%20sol_build%2C%20sol_fflow)%3A%0A%20%20%20%20if%20'sol_build'%20in%20locals()%3A%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%205))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20statuses%20%3D%20%5Bsol_build.get(d%2C%200)%20for%20d%20in%20range(1%2C%2013)%5D%20%20%23%20Left%3A%20depot%20open%2Fclose%20with%20fixed%20costs%0A%20%20%20%20%20%20%20%20colors_d%20%3D%20%5B'%2343A047'%20if%20s%20%3E%200.5%20else%20'%23BDBDBD'%20for%20s%20in%20statuses%5D%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(range(1%2C%2013)%2C%20%5BCFIX%5Bd%5D%20for%20d%20in%20range(1%2C%2013)%5D%2C%20color%3Dcolors_d%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(1%2C%2013))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(%5Bf'D%7Bd%7D'%20for%20d%20in%20range(1%2C%2013)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Fixed%20opening%20cost')%0A%20%20%20%20%20%20%20%20_ax.set_title('E-3%20Depot%20location%20-%20open%20vs%20closed%20depots')%0A%20%20%20%20%20%20%20%20open_p%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3D'Open')%0A%20%20%20%20%20%20%20%20close_p%20%3D%20mpatches.Patch(color%3D'%23BDBDBD'%2C%20label%3D'Closed')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bopen_p%2C%20close_p%5D)%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20flow_matrix%20%3D%20np.array(%5B%5Bsol_fflow.get((d%2C%20c)%2C%200)%20for%20c%20in%20range(1%2C%2013)%5D%20for%20d%20in%20open_depots%5D)%0A%20%20%20%20%20%20%20%20im%20%3D%20_ax2.imshow(flow_matrix%2C%20cmap%3D'Blues'%2C%20aspect%3D'auto'%2C%20vmin%3D0%2C%20vmax%3D1)%0A%20%20%20%20%20%20%20%20plt.colorbar(im%2C%20ax%3D_ax2%2C%20label%3D'Fraction%20of%20demand%20served')%0A%20%20%20%20%20%20%20%20_ax2.set_xticks(range(12))%20%20%23%20Right%3A%20fractional%20flow%20heatmap%20(open%20depots%20only)%0A%20%20%20%20%20%20%20%20_ax2.set_xticklabels(%5Bf'C%7Bc%7D'%20for%20c%20in%20range(1%2C%2013)%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax2.set_yticks(range(len(open_depots)))%0A%20%20%20%20%20%20%20%20_ax2.set_yticklabels(%5Bf'Depot%20%7Bd%7D'%20for%20d%20in%20open_depots%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax2.set_title('E-3%20Fractional%20flow%20(open%20depots%20x%20customers)')%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%20depot%20location%20charts')%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%20E-4%20Heating%20oil%20delivery%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%20E%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e4deliver_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe4deliver_p.mos)%0A%0A%20%20%20%20A%20delivery%20truck%20departs%20from%20a%20depot%20(site%201)%20and%20must%20visit%206%20customers%20before%20returning.%20Each%20customer%20has%20a%20known%20demand%3B%20the%20truck%20has%20a%20capacity%20of%2039%2C000%20litres.%20The%20model%20finds%20the%20minimum%20distance%20tour%20that%20satisfies%20all%20customer%20demands%20without%20exceeding%20truck%20capacity.%20This%20is%20a%20capacitated%20vehicle%20routing%20%2F%20TSP%20problem.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BSITES%7D%20%3D%201..7%24%20(site%201%20is%20the%20depot)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bprec%7D_%7Bi%2Cj%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20site%20%24i%24%20is%20visited%20directly%20before%20%24j%24)%3B%20%24%5Cmathit%7Bcumload%7D_s%20%5Cgeq%200%24%20(cumulative%20load%20at%20site%20%24s%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_%7Bi%20%5Cneq%20j%7D%20%5Ctext%7BDIST%7D_%7Bi%2Cj%7D%20%5Ccdot%20%5Cmathit%7Bprec%7D_%7Bi%2Cj%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bi%20%5Cneq%20j%7D%20%5Cmathit%7Bprec%7D_%7Bi%2Cj%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20j%20%5Cquad%20%5Ctext%7B(enter%20each%20site%20once)%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_%7Bj%20%5Cneq%20i%7D%20%5Cmathit%7Bprec%7D_%7Bi%2Cj%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20i%20%5Cquad%20%5Ctext%7B(leave%20each%20site%20once)%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%7Bcumload%7D_j%20%5Cgeq%20%5Cmathit%7Bcumload%7D_i%20%2B%20%5Ctext%7BDEM%7D_j%20-%20%5Ctext%7BCAP%7D(1%20-%20%5Cmathit%7Bprec%7D_%7Bi%2Cj%7D)%20%5Cquad%20%5Cforall%5C%2C%20i%20%5Cneq%20j%2C%5C%2C%20j%20%3E%201%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%207x7%20distance%20matrix%20%60DIST%60%20as%20a%20dict%20with%20%60(i%2Cj)%60%20tuple%20keys%2C%20customer%20demands%20%60DEM%60%20as%20a%20sparse%201-indexed%20dict%20(site%201%20is%20the%20depot%20with%20demand%200)%2C%20and%20truck%20capacity%20%60CAP%60%20as%20a%20scalar%20passed%20via%20%60exec_params%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%20dist_raw%20%3D%20%5B%5B0%2C%20148%2C%2055%2C%2032%2C%2070%2C%20140%2C%2073%5D%2C%20%5B148%2C%200%2C%2093%2C%20180%2C%2099%2C%2012%2C%2072%5D%2C%20%5B55%2C%2093%2C%200%2C%2085%2C%2020%2C%2083%2C%2028%5D%2C%20%5B32%2C%20180%2C%2085%2C%200%2C%20100%2C%20174%2C%2099%5D%2C%20%5B70%2C%2099%2C%2020%2C%20100%2C%200%2C%2085%2C%2049%5D%2C%20%5B140%2C%2012%2C%2083%2C%20174%2C%2085%2C%200%2C%2073%5D%2C%20%5B73%2C%2072%2C%2028%2C%2099%2C%2049%2C%2073%2C%200%5D%5D%0A%20%20%20%20DIST%20%3D%20%7B(i%20%2B%201%2C%20j%20%2B%201)%3A%20dist_raw%5Bi%5D%5Bj%5D%20for%20i%20in%20range(7)%20for%20j%20in%20range(7)%7D%0A%20%20%20%20DEM_1%20%3D%20%7B2%3A%2014000%2C%203%3A%203000%2C%204%3A%206000%2C%205%3A%2016000%2C%206%3A%2015000%2C%207%3A%205000%7D%0A%20%20%20%20CAP_1%20%3D%2039000%0A%20%20%20%20total_dem%20%3D%20sum(DEM_1.values())%0A%20%20%20%20print(f'Total%20customer%20demand%3A%20%7Btotal_dem%3A%2C%7D%20litres')%0A%20%20%20%20print(f'Truck%20capacity%3A%20%7BCAP_1%3A%2C%7D%20litres')%0A%20%20%20%20print(f'Capacity%20utilization%3A%20%7B100%20*%20total_dem%20%2F%20CAP_1%3A.1f%7D%25')%0A%20%20%20%20return%20CAP_1%2C%20DEM_1%2C%20DIST%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%20section%20demonstrates%20%60model.stop()%60%20-%20a%20MoselPy%20feature%20for%20graceful%20early%20termination%20of%20a%20running%20solve.%20A%20background%20thread%20waits%205%20seconds%20and%20then%20calls%20%60model.stop()%60%20if%20the%20model%20is%20still%20running.%20For%20this%20small%20TSP%20instance%20the%20solver%20typically%20finishes%20well%20under%205%20seconds%2C%20so%20%60stop()%60%20will%20usually%20not%20trigger.%20The%20pattern%20is%20shown%20regardless%3A%20it%20is%20essential%20for%20larger%20MIP%20problems%20where%20you%20want%20to%20impose%20a%20wall-clock%20time%20limit%20and%20use%20the%20best%20solution%20found%20so%20far.%0A%0A%20%20%20%20After%20the%20run%2C%20%60model.prob_status.is_solution_available%60%20distinguishes%20between%20a%20full%20optimal%20solve%20and%20an%20early-terminated%20solve%20with%20an%20incumbent.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP_1%2C%20DEM_1%2C%20DIST%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20import%20threading%2C%20time%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fe4deliver_p.mos'%2C%20f'%7BMODELS%7D%2Fe4deliver_p.bim')%0A%20%20%20%20model_3%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fe4deliver_p.bim')%0A%20%20%20%20model_3.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%0A%20%20%20%20def%20stop_after(mdl%2C%20seconds)%3A%0A%20%20%20%20%20%20%20%20time.sleep(seconds)%0A%20%20%20%20%20%20%20%20if%20mdl.is_running%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%5Bstop_after%5D%20%7Bseconds%7Ds%20elapsed%20-%20calling%20model.stop()')%0A%20%20%20%20%20%20%20%20%20%20%20%20mdl.stop()%0A%20%20%20%20t%20%3D%20threading.Thread(target%3Dstop_after%2C%20args%3D(model_3%2C%205))%0A%20%20%20%20t.start()%0A%20%20%20%20_output%20%3D%20model_3.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'CAP'%3A%20CAP_1%7D%2C%20input_data%3D%7B'DIST'%3A%20DIST%2C%20'DEM'%3A%20DEM_1%7D)%0A%20%20%20%20t.join()%0A%20%20%20%20if%20model_3.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_e4%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_prec%20%3D%20_output%5B'sol_prec'%5D%0A%20%20%20%20%20%20%20%20print(f'Best%20solution%20found%3A%20%7Bobj_val_e4%3A.0f%7D%20km')%0A%20%20%20%20%20%20%20%20tour%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(6)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_nxt%20%3D%20next((j%20for%20j%20in%20range(1%2C%208)%20if%20j%20!%3D%20current%20and%20sol_prec.get((current%2C%20j)%2C%200)%20%3E%200.5)%2C%20None)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_nxt%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20tour.append(_nxt)%0A%20%20%20%20%20%20%20%20%20%20%20%20current%20%3D%20_nxt%0A%20%20%20%20%20%20%20%20tour.append(1)%0A%20%20%20%20%20%20%20%20print(f'Tour%3A%20%7B'%20-%3E%20'.join((str(s)%20for%20s%20in%20tour))%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_k%20in%20range(len(tour)%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_s%2C%20_nxt%20%3D%20(tour%5B_k%5D%2C%20tour%5B_k%20%2B%201%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20dem_s%20%3D%20DEM_1.get(_nxt%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_s%7D%20-%3E%20%7B_nxt%7D%3A%20%20dist%3D%7BDIST%5B_s%2C%20_nxt%5D%7D%2C%20demand%20at%20%7B_nxt%7D%3D%7Bdem_s%3A%2C%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_3.prob_status%7D)')%0A%20%20%20%20return%20obj_val_e4%2C%20tour%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%20Route%20visualization%20on%20a%202D%20coordinate%20grid.%20Sites%20are%20plotted%20at%20representative%20positions%3B%20arrows%20show%20the%20delivery%20tour%20sequence.%20The%20depot%20(site%201)%20is%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DEM_1%2C%20mo%2C%20mpatches%2C%20obj_val_e4%2C%20plt%2C%20tour)%3A%0A%20%20%20%20if%20'sol_prec'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20coords%20%3D%20%7B1%3A%20(0%2C%200)%2C%202%3A%20(14%2C%208)%2C%203%3A%20(5%2C%203)%2C%204%3A%20(3%2C%2010)%2C%205%3A%20(7%2C%202)%2C%206%3A%20(13%2C%209)%2C%207%3A%20(6%2C%205)%7D%0A%20%20%20%20%20%20%20%20site_names%20%3D%20%7B1%3A%20'Depot'%2C%202%3A%20'C2'%2C%203%3A%20'C3'%2C%204%3A%20'C4'%2C%205%3A%20'C5'%2C%206%3A%20'C6'%2C%207%3A%20'C7'%7D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%206))%0A%20%20%20%20%20%20%20%20for%20_s%2C%20(cx%2C%20cy)%20in%20coords.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20color%20%3D%20'%23E53935'%20if%20_s%20%3D%3D%201%20else%20'%231E88E5'%0A%20%20%20%20%20%20%20%20%20%20%20%20marker%20%3D%20's'%20if%20_s%20%3D%3D%201%20else%20'o'%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(cx%2C%20cy%2C%20s%3D200%2C%20color%3Dcolor%2C%20marker%3Dmarker%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(f'%7B_s%7D%5Cn(%7BDEM_1.get(_s%2C%200)%3A%2C%7DL)'%20if%20_s%20%3E%201%20else%20'Depot'%2C%20(cx%2C%20cy)%2C%20textcoords%3D'offset%20points'%2C%20xytext%3D(6%2C%206)%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20if%20'tour'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_k%20in%20range(len(tour)%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_s%2C%20_nxt%20%3D%20(tour%5B_k%5D%2C%20tour%5B_k%20%2B%201%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sx%2C%20sy%20%3D%20coords%5B_s%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ex%2C%20ey%20%3D%20coords%5B_nxt%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(ex%2C%20ey)%2C%20xytext%3D(sx%2C%20sy)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3D'%2343A047'%2C%20lw%3D2.0))%0A%20%20%20%20%20%20%20%20_ax.set_title(f'E-4%20Delivery%20tour%20-%20total%20distance%20%7Bobj_val_e4%3A.0f%7D')%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('X')%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Y')%0A%20%20%20%20%20%20%20%20depot_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Depot%20(site%201)')%0A%20%20%20%20%20%20%20%20cust_p%20%3D%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'Customer%20site')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bdepot_p%2C%20cust_p%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%20delivery%20tour%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%20E-5%20Combined%20transport%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%20E%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e5combine_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe5combine_p.mos)%0A%0A%20%20%20%20Goods%20must%20be%20transported%20over%20four%20consecutive%20route%20legs.%20Three%20transport%20modes%20-%20rail%2C%20road%2C%20and%20air%20-%20are%20available%20for%20each%20leg%2C%20with%20different%20costs%20per%20leg.%20Switching%20between%20modes%20between%20consecutive%20legs%20incurs%20a%20changeover%20cost.%20The%20model%20selects%20one%20mode%20per%20leg%20to%20minimize%20total%20transport%20plus%20changeover%20cost.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BLEGS%7D%20%3D%201..4%24%2C%20%24%5Ctext%7BMODES%7D%20%3D%20%5C%7B%5Ctext%7Brail%7D%2C%5Ctext%7Broad%7D%2C%5Ctext%7Bair%7D%5C%7D%24%20(auto-populated%20from%20data)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Buse%7D_%7Bm%2Cl%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(1%20if%20mode%20%24m%24%20used%20on%20leg%20%24l%24)%3B%20%24%5Cmathit%7Bchg%7D_%7Bm_1%2Cm_2%2Cl%7D%20%5Cgeq%200%24%20(1%20if%20switching%20from%20%24m_1%24%20to%20%24m_2%24%20before%20leg%20%24l%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%2Cl%7D%20%5Ctext%7BCTRANS%7D_%7Bm%2Cl%7D%20%5Ccdot%20%5Cmathit%7Buse%7D_%7Bm%2Cl%7D%20%2B%20%5Csum_%7Bm_1%2Cm_2%2Cl%20%5Cgeq%202%7D%20%5Ctext%7BCCHG%7D_%7Bm_1%2Cm_2%7D%20%5Ccdot%20%5Cmathit%7Bchg%7D_%7Bm_1%2Cm_2%2Cl%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_m%20%5Cmathit%7Buse%7D_%7Bm%2Cl%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20l%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%7Bchg%7D_%7Bm_1%2Cm_2%2Cl%7D%20%5Cgeq%20%5Cmathit%7Buse%7D_%7Bm_1%2Cl-1%7D%20%2B%20%5Cmathit%7Buse%7D_%7Bm_2%2Cl%7D%20-%201%20%5Cquad%20%5Cforall%5C%2C%20m_1%20%5Cneq%20m_2%2C%5C%2C%20l%20%5Cgeq%202%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%20transport%20costs%20%60CTRANS%60%20as%20a%20%60pd.DataFrame%60%20with%20mode%20names%20as%20rows%20and%20leg%20indices%201..4%20as%20columns.%20Changeover%20costs%20%60CCHG%60%20are%20a%20dict%20with%20%60(mode1%2C%20mode2)%60%20tuple%20keys.%20Passing%20%60CTRANS%60%20as%20a%20DataFrame%20lets%20MoselPy%20automatically%20populate%20the%20%60MODES%60%20set%20of%20strings%20from%20the%20row%20index.%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%20ctrans_data%20%3D%20%7B'rail'%3A%20%5B30%2C%2025%2C%2040%2C%2060%5D%2C%20'road'%3A%20%5B25%2C%2040%2C%2045%2C%2050%5D%2C%20'air'%3A%20%5B40%2C%2020%2C%2050%2C%2045%5D%7D%0A%20%20%20%20CTRANS_DF%20%3D%20pd.DataFrame(ctrans_data%2C%20index%3D%5B1%2C%202%2C%203%2C%204%5D).T%0A%20%20%20%20CTRANS_DF.index.name%20%3D%20'Mode'%0A%20%20%20%20CTRANS_DF.columns.name%20%3D%20'Leg'%0A%20%20%20%20CCHG%20%3D%20%7B('rail'%2C%20'road')%3A%205%2C%20('rail'%2C%20'air')%3A%2012%2C%20('road'%2C%20'rail')%3A%208%2C%20('road'%2C%20'air')%3A%2010%2C%20('air'%2C%20'rail')%3A%2015%2C%20('air'%2C%20'road')%3A%2010%7D%0A%20%20%20%20print('Transport%20costs%20(mode%20x%20leg)%3A')%0A%20%20%20%20print(CTRANS_DF.to_string())%0A%20%20%20%20print()%0A%20%20%20%20print('Changeover%20costs%3A')%0A%20%20%20%20for%20(m1%2C%20m2)%2C%20_c%20in%20CCHG.items()%3A%0A%20%20%20%20%20%20%20%20print(f'%20%20%7Bm1%7D%20-%3E%20%7Bm2%7D%3A%20%7B_c%7D')%0A%20%20%20%20return%20CCHG%2C%20CTRANS_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%20Use%20the%20single%20execution%20command.%20%60CTRANS_DF%60%20is%20passed%20via%20%60input_data%60%20as%20a%20DataFrame%3B%20MoselPy%20maps%20its%20index%20(mode%20names)%20and%20columns%20(leg%20indices)%20directly%20to%20the%20Mosel%202D%20array.%20The%20%60MODES%60%20set%20of%20strings%20auto-populates%20from%20the%20row%20labels.%20After%20solving%2C%20%60sol_use%60%20is%20a%20dict%20mapping%20%60(mode%2C%20leg)%60%20pairs%20to%200%2F1%20values.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CCHG%2C%20CTRANS_DF%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20model_4%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fe5combine_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'CTRANS'%3A%20CTRANS_DF%2C%20'CCHG'%3A%20CCHG%7D)%0A%20%20%20%20if%20_output%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20obj_val_e5%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_use%20%3D%20_output%5B'sol_use'%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20total%20cost%3A%20%7Bobj_val_e5%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20modes_sorted%20%3D%20sorted(set((m%20for%20m%2C%20l%20in%20sol_use)))%0A%20%20%20%20%20%20%20%20_selected_modes%20%3D%20%7Bl%3A%20m%20for%20(m%2C%20l)%2C%20v%20in%20sol_use.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20print('Selected%20mode%20per%20leg%3A')%0A%20%20%20%20%20%20%20%20for%20_l%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20_selected_modes.get(_l%2C%20'%3F')%0A%20%20%20%20%20%20%20%20%20%20%20%20trans_cost%20%3D%20CTRANS_DF.at%5B_m%2C%20_l%5D%20if%20_m%20!%3D%20'%3F'%20else%200%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_l%20%3E%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_prev_m%20%3D%20_selected_modes.get(_l%20-%201%2C%20'%3F')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20chg_cost%20%3D%20CCHG.get((_prev_m%2C%20_m)%2C%200)%20if%20_prev_m%20!%3D%20_m%20else%200%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Leg%20%7B_l%7D%3A%20%7B_m%3A5s%7D%20%20transport%3D%7Btrans_cost%3A3d%7D%20%20changeover%20from%20%7B_prev_m%7D%3D%7Bchg_cost%7D')%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%20print(f'%20%20Leg%20%7B_l%7D%3A%20%7B_m%3A5s%7D%20%20transport%3D%7Btrans_cost%3A3d%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_e5%2C%20sol_use%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%20cost%20breakdown%20per%20leg%3A%20transport%20cost%20(solid)%20and%20changeover%20cost%20(hatched).%20Bars%20are%20colored%20by%20selected%20transport%20mode.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CCHG%2C%20CTRANS_DF%2C%20mo%2C%20obj_val_e5%2C%20plt%2C%20sol_use)%3A%0A%20%20%20%20if%20'sol_use'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20_selected_modes%20%3D%20%7Bl%3A%20m%20for%20(m%2C%20l)%2C%20v%20in%20sol_use.items()%20if%20v%20%3E%200.5%7D%0A%20%20%20%20%20%20%20%20mode_colors%20%3D%20%7B'rail'%3A%20'%231E88E5'%2C%20'road'%3A%20'%23FB8C00'%2C%20'air'%3A%20'%2343A047'%7D%0A%20%20%20%20%20%20%20%20trans_costs%20%3D%20%5BCTRANS_DF.at%5B_selected_modes%5Bl%5D%2C%20l%5D%20for%20l%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20chg_costs%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_l%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_l%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20chg_costs.append(0)%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%20_prev_m%20%3D%20_selected_modes.get(_l%20-%201%2C%20'%3F')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20curr_m%20%3D%20_selected_modes.get(_l%2C%20'%3F')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20chg_costs.append(CCHG.get((_prev_m%2C%20curr_m)%2C%200)%20if%20_prev_m%20!%3D%20curr_m%20else%200)%0A%20%20%20%20%20%20%20%20leg_colors%20%3D%20%5Bmode_colors.get(_selected_modes.get(l%2C%20'')%2C%20'%2390CAF9')%20for%20l%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(8%2C%204))%0A%20%20%20%20%20%20%20%20_x%20%3D%20range(1%2C%205)%0A%20%20%20%20%20%20%20%20bars1%20%3D%20_ax.bar(_x%2C%20trans_costs%2C%20color%3Dleg_colors%2C%20edgecolor%3D'white'%2C%20label%3D'Transport%20cost')%0A%20%20%20%20%20%20%20%20bars2%20%3D%20_ax.bar(_x%2C%20chg_costs%2C%20bottom%3Dtrans_costs%2C%20color%3Dleg_colors%2C%20edgecolor%3D'white'%2C%20hatch%3D'%2F%2F%2F'%2C%20alpha%3D0.6%2C%20label%3D'Changeover%20cost')%0A%20%20%20%20%20%20%20%20for%20_l%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mode%20%3D%20_selected_modes.get(_l%2C%20'%3F')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_l%2C%20trans_costs%5B_l%20-%201%5D%20%2F%202%2C%20mode%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_ax.set_xticks(range(1%2C%205))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(%5Bf'Leg%20%7Bl%7D'%20for%20l%20in%20range(1%2C%205)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Cost')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'E-5%20Combined%20transport%20-%20cost%20per%20leg%20(total%3D%7Bobj_val_e5%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20_ax.legend()%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%20combined%20transport%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%20E-6%20Van%20rental%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%20E%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60e6vanrent_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fground_transport%2Fe6vanrent_p.mos)%0A%0A%20%20%20%20A%20company%20needs%20vans%20for%20six%20months.%20Vans%20may%20be%20hired%20on%20contracts%20of%203%2C%204%2C%20or%205%20months%20duration%3B%20longer%20contracts%20cost%20less%20per%20month.%20An%20initial%20fleet%20of%20200%20vans%20is%20available%20for%20the%20first%20three%20months.%20The%20model%20minimizes%20total%20rental%20cost%20while%20meeting%20monthly%20demand.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BMONTHS%7D%20%3D%201..6%24%2C%20%24%5Ctext%7BCONTR%7D%20%3D%203..5%24%20(contract%20durations)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Brent%7D_%7Bc%2Cm%7D%20%5Cgeq%200%24%20integer%20-%20vans%20hired%20on%20%24c%24-month%20contract%20starting%20in%20month%20%24m%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_%7Bc%20%5Cin%20%5Ctext%7BCONTR%7D%2C%5C%2C%20m%20%5Cin%20%5Ctext%7BMONTHS%7D%7D%20%5Ctext%7BCOST%7D_c%20%5Ccdot%20%5Cmathit%7Brent%7D_%7Bc%2Cm%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Ctext%7BNINIT%7D%20%5Ccdot%20%5Cmathbf%7B1%7D_%7Bm%20%5Cleq%203%7D%20%2B%20%5Csum_%7Bc%20%5Cin%20%5Ctext%7BCONTR%7D%7D%20%5Csum_%7Bs%3D%5Cmax(1%2Cm-c%2B1)%7D%5E%7Bm%7D%20%5Cmathit%7Brent%7D_%7Bc%2Cs%7D%20%5Cgeq%20%5Ctext%7BREQ%7D_m%20%5Cquad%20%5Cforall%5C%2C%20m%0A%20%20%20%20%5Cend%7Balign*%7D%0A%20%20%20%20%24%24%0A%0A%20%20%20%20where%20%24%5Ctext%7BNINIT%7D%3D200%24%20is%20the%20initial%20fleet%20contributing%20to%20the%20first%203%20months%20(on%203-month%20contracts).%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%20monthly%20requirements%20%60REQ%60%2C%20contract%20costs%20%60COST%60%2C%20and%20month%20names%20%60NAMES%60%20in%20Python.%20The%20number%20of%20months%20%60NM%60%20and%20initial%20fleet%20%60NINIT%60%20can%20be%20overridden%20via%20%60exec_params%60%2C%20enabling%20sensitivity%20analysis.%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%20REQ_1%20%3D%20%7B1%3A%20430%2C%202%3A%20410%2C%203%3A%20440%2C%204%3A%20390%2C%205%3A%20425%2C%206%3A%20450%7D%0A%20%20%20%20COST%20%3D%20%7B3%3A%201700%2C%204%3A%202200%2C%205%3A%202600%7D%0A%20%20%20%20NAMES%20%3D%20%7B1%3A%20'Jan'%2C%202%3A%20'Feb'%2C%203%3A%20'Mar'%2C%204%3A%20'Apr'%2C%205%3A%20'May'%2C%206%3A%20'Jun'%7D%0A%20%20%20%20NINIT%20%3D%20200%0A%20%20%20%20print('Monthly%20van%20requirements%3A')%0A%20%20%20%20for%20_m%2C%20name%20in%20NAMES.items()%3A%0A%20%20%20%20%20%20%20%20print(f'%20%20Month%20%7B_m%7D%20(%7Bname%7D)%3A%20%7BREQ_1%5B_m%5D%7D%20vans%20required')%0A%20%20%20%20print()%0A%20%20%20%20print('Contract%20costs%3A'%2C%20%7Bf'%7Bc%7D-month'%3A%20v%20for%20c%2C%20v%20in%20COST.items()%7D)%0A%20%20%20%20return%20COST%2C%20NAMES%2C%20NINIT%2C%20REQ_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%20%23%23%23%20Sensitivity%20analysis%3A%20varying%20NINIT%0A%0A%20%20%20%20The%20model%20is%20compiled%20and%20loaded%20once.%20%60run()%60%20is%20then%20called%20multiple%20times%20-%20each%20time%20with%20a%20different%20value%20of%20%60NINIT%60%20(initial%20fleet%20size)%20via%20%60exec_params%60.%20This%20is%20the%20parametric%20loop%20pattern%3A%20one%20compiled%20model%2C%20multiple%20runs%20varying%20a%20single%20parameter.%20Results%20are%20collected%20into%20a%20%60pd.Series%60%20indexed%20by%20%60NINIT%60%20value.%0A%0A%20%20%20%20As%20the%20initial%20fleet%20grows%2C%20the%20optimizer%20can%20rely%20on%20it%20more%20heavily%20for%20the%20first%20three%20months%2C%20reducing%20the%20need%20for%20new%20rental%20contracts%20and%20lowering%20total%20cost.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(COST%2C%20MODELS%2C%20NAMES%2C%20NINIT%2C%20REQ_1%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fe6vanrent_p.mos'%2C%20f'%7BMODELS%7D%2Fe6vanrent_p.bim')%0A%20%20%20%20model_5%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fe6vanrent_p.bim')%0A%20%20%20%20model_5.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20out_default%20%3D%20model_5.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'NINIT'%3A%20NINIT%7D%2C%20input_data%3D%7B'REQ'%3A%20REQ_1%2C%20'COST'%3A%20COST%2C%20'NAMES'%3A%20NAMES%7D)%0A%20%20%20%20if%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_e6%20%3D%20out_default%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_rent%20%3D%20out_default%5B'sol_rent'%5D%0A%20%20%20%20%20%20%20%20NM%20%3D%206%0A%20%20%20%20%20%20%20%20month_names%20%3D%20%5BNAMES%5Bm%5D%20for%20m%20in%20range(1%2C%20NM%20%2B%201)%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20cost%3A%20%7Bobj_val_e6%3A%2C.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'new%20rents%20%20%7B'%20%20'.join((f'%7Bn%3A%3E4%7D'%20for%20n%20in%20month_names))%7D')%0A%20%20%20%20%20%20%20%20for%20_c%20in%20%5B3%2C%204%2C%205%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row%20%3D%20%5Bround(sol_rent.get((_c%2C%20m)%2C%200))%20for%20m%20in%20range(1%2C%20NM%20%2B%201)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%7B_c%7D%20months%20%20%7B'%20%20'.join((f'%7Bv%3A%3E4%7D'%20for%20v%20in%20row))%7D')%0A%20%20%20%20%20%20%20%20totals%20%3D%20%5B(NINIT%20if%20m%20%3C%3D%202%20else%200)%20%2B%20sum((round(sol_rent.get((c%2C%20s)%2C%200))%20for%20c%20in%20%5B3%2C%204%2C%205%5D%20for%20s%20in%20range(max(1%2C%20m%20-%20c%20%2B%201)%2C%20min(m%2C%20NM%20-%20c%20%2B%201)%20%2B%201)))%20for%20m%20in%20range(1%2C%20NM%20%2B%201)%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20%20%20%20%20%7B'%20%20'.join((f'%7Bv%3A%3E4%7D'%20for%20v%20in%20totals))%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20for%20NINIT%3D%7BNINIT%7D%20(%7Bmodel_5.prob_status%7D)')%0A%20%20%20%20return%20model_5%2C%20obj_val_e6%2C%20sol_rent%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%20Sensitivity%20analysis%3A%20varying%20NINIT%0A%0A%20%20%20%20The%20model%20is%20already%20compiled%20and%20loaded.%20Run%20it%20multiple%20times%20with%20different%20%60NINIT%60%20values%20via%20%60exec_params%60%20-%20this%20is%20the%20parametric%20loop%20pattern.%20As%20the%20initial%20fleet%20grows%2C%20fewer%20new%20rental%20contracts%20are%20needed%2C%20reducing%20total%20cost.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(COST%2C%20NAMES%2C%20REQ_1%2C%20model_5%2C%20pd)%3A%0A%20%20%20%20ninit_values%20%3D%20%5B0%2C%2050%2C%20100%2C%20150%2C%20200%2C%20250%2C%20300%5D%0A%20%20%20%20sensitivity_results%20%3D%20%7B%7D%0A%20%20%20%20for%20ninit_val%20in%20ninit_values%3A%0A%20%20%20%20%20%20%20%20_out%20%3D%20model_5.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'NINIT'%3A%20ninit_val%7D%2C%20input_data%3D%7B'REQ'%3A%20REQ_1%2C%20'COST'%3A%20COST%2C%20'NAMES'%3A%20NAMES%7D)%0A%20%20%20%20%20%20%20%20if%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sensitivity_results%5Bninit_val%5D%20%3D%20_out%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'NINIT%3D%7Bninit_val%7D%3A%20no%20solution%20(%7Bmodel_5.prob_status%7D)')%0A%20%20%20%20cost_series%20%3D%20pd.Series(sensitivity_results%2C%20name%3D'Total%20rental%20cost')%0A%20%20%20%20cost_series.index.name%20%3D%20'NINIT'%0A%20%20%20%20print('Total%20cost%20vs%20initial%20fleet%20size%3A')%0A%20%20%20%20print(cost_series.apply(lambda%20v%3A%20f'%7Bv%3A%2C.0f%7D').to_string())%0A%20%20%20%20return%20(cost_series%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%20Two-panel%20visualization%3A%20(left)%20sensitivity%20curve%20showing%20total%20cost%20vs%20initial%20fleet%20size%3B%20(right)%20stacked%20bar%20chart%20of%20van%20contracts%20per%20month%20for%20the%20default%20NINIT%3D200%20solution.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NAMES%2C%20NINIT%2C%20cost_series%2C%20mo%2C%20np%2C%20obj_val_e6%2C%20plt%2C%20sol_rent)%3A%0A%20%20%20%20if%20cost_series%20is%20not%20None%20and%20(not%20cost_series.empty)%3A%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%205))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B0%5D%0A%20%20%20%20%20%20%20%20_ax.plot(cost_series.index%2C%20cost_series.values%2C%20marker%3D'o'%2C%20color%3D'%231E88E5'%2C%20linewidth%3D2)%0A%20%20%20%20%20%20%20%20_ax.axvline(NINIT%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'Default%20NINIT%3D%7BNINIT%7D')%0A%20%20%20%20%20%20%20%20for%20_x%2C%20y%20in%20cost_series.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(f'%7By%3A%2C.0f%7D'%2C%20(_x%2C%20y)%2C%20textcoords%3D'offset%20points'%2C%20xytext%3D(0%2C%208)%2C%20ha%3D'center'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel('Initial%20fleet%20size%20(NINIT)')%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Total%20rental%20cost')%0A%20%20%20%20%20%20%20%20_ax.set_title('E-6%20Van%20rental%20-%20cost%20sensitivity%20vs%20initial%20fleet')%0A%20%20%20%20%20%20%20%20_ax.legend()%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20if%20'sol_rent'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20contract_colors%20%3D%20%7B3%3A%20'%231E88E5'%2C%204%3A%20'%23FB8C00'%2C%205%3A%20'%2343A047'%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20bottom%20%3D%20np.zeros(6)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_c%20in%20%5B3%2C%204%2C%205%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vals%20%3D%20%5Bsol_rent.get((_c%2C%20m)%2C%200)%20for%20m%20in%20range(1%2C%207)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax2.bar(range(1%2C%207)%2C%20vals%2C%20bottom%3Dbottom%2C%20color%3Dcontract_colors%5B_c%5D%2C%20edgecolor%3D'white'%2C%20label%3Df'%7B_c%7D-month%20contract')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bottom%20%3D%20bottom%20%2B%20np.array(vals)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.set_xticks(range(1%2C%207))%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.set_xticklabels(%5BNAMES%5Bm%5D%20for%20m%20in%20range(1%2C%207)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.set_ylabel('Vans%20rented')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.set_title(f'E-6%20Rental%20schedule%20(NINIT%3D%7BNINIT%7D%2C%20cost%3D%7Bobj_val_e6%3A%2C.0f%7D)')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.legend()%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax2.text(0.5%2C%200.5%2C%20'No%20solution'%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20transform%3D_ax2.transAxes)%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%20van%20rental%20charts')%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
1ecffb4fb49523fa96ed8d0dd8e6a6e5