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%20Telecommunications%22%0A%23%20description%20%3D%20%22Network%20reliability%2C%20dimensioning%2C%20routing%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-telecomm.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%20G%3A%20Telecommunications**%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_telecomm.py***%20-%20Telecommunications%20network%20design%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20network%20reliability%2C%20mobile%20network%20dimensioning%2C%20call%20routing%2C%20cable%20networks%2C%20satellite%20scheduling%2C%20and%20transmitter%20placement%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%22telecomm%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22telecomm%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%20mp%2C%20mpatches%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%20Telecommunications%20network%20design%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%20G%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20span%20network%20reliability%20analysis%2C%20mobile%20network%20dimensioning%2C%20telephone%20call%20routing%2C%20cable%20network%20design%2C%20satellite%20scheduling%2C%20and%20transmitter%20placement.%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Ftelecomm%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Ftelecomm%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Ftelecomm)%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_7)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**Dynamic%20sparse%20arrays**%3A%20G-1%20uses%20Mosel%20%60dynamic%20array%60%20with%20%60exists()%60%20and%20%60create()%60%20to%20build%20flow%20variables%20only%20for%20existing%20network%20arcs.%20MoselPy%20reads%20these%20from%20a%20sparse%20Python%20dict%20automatically.%0A%20%20%20%20-%20**%60pd.DataFrame%60%20for%202D%20cost%20input**%3A%20G-2%20passes%20a%2010x5%20cost%20matrix%20as%20a%20DataFrame%3B%20MoselPy%20maps%20rows%20and%20columns%20to%20the%20Mosel%202D%20array%20index.%0A%20%20%20%20-%20**Two%20consecutive%20%60initializations%20from%60%20blocks**%3A%20G-3%20reads%20arc%20capacities%20and%20demands%20first%2C%20computes%20the%20number%20of%20arcs%20dynamically%2C%20then%20reads%20the%20route%20array%20in%20a%20second%20block%20-%20both%20from%20the%20same%20Python%20%60input_data%60%20dict.%0A%20%20%20%20-%20**Model%20introspection%20before%20running**%3A%20G-4%20calls%20%60model.name%60%2C%20%60model.version%60%2C%20and%20%60model.size%60%20after%20%60load_model()%60%20but%20before%20%60run()%60.%0A%20%20%20%20-%20**Context%20manager%20%2B%20iterative%20in-Mosel%20algorithm**%3A%20G-5%20uses%20%60with%20mp.load_model(...)%20as%20model%3A%60%20and%20runs%20an%20iterative%20scheduling%20algorithm%20that%20modifies%20and%20re-solves%20inside%20Mosel%2C%20returning%20the%20complete%20schedule%20to%20Python.%0A%20%20%20%20-%20**%60exec_params%60%20sensitivity%20loop**%3A%20G-6%20varies%20%60BUDGET%60%20across%20a%20parametric%20run%20to%20show%20how%20population%20coverage%20changes%20with%20the%20available%20budget.%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%20G-1%20Network%20reliability%20(max%20flow)%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%20G%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g1rely_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg1rely_p.mos)%0A%0A%20%20%20%20A%20military%20telecommunications%20network%20has%2011%20sites%20connected%20by%20bidirectional%20links.%20Due%20to%20reliability%20requirements%2C%20nodes%2010%20(SOURCE)%20and%2011%20(SINK)%20must%20be%20able%20to%20communicate%20even%20if%20any%20three%20other%20sites%20are%20destroyed.%20This%20is%20equivalent%20to%20asking%20whether%20the%20network%20contains%20at%20least%204%20vertex-disjoint%20paths%20from%20SOURCE%20to%20SINK%20(by%20Menger's%20theorem).%0A%0A%20%20%20%20The%20model%20is%20formulated%20as%20a%20maximum%20flow%20problem.%20Flow%20variables%20are%20created%20only%20for%20existing%20arcs%20using%20Mosel's%20%60dynamic%20array%60%20pattern%20with%20%60exists()%60%20and%20%60create()%60.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BNODES%7D%20%3D%20%5C%7B1%2C%20%5Cldots%2C%2011%5C%7D%24%20(auto-sized%20from%20arc%20data)%3B%20SOURCE%20%24%3D%2010%24%3B%20SINK%20%24%3D%2011%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BARC%7D_%7Bn%2Cm%7D%24%20-%201%20if%20a%20bidirectional%20link%20exists%20between%20nodes%20%24n%24%20and%20%24m%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bflow%7D_%7Bn%2Cm%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20-%201%20if%20flow%20uses%20arc%20%24(n%2Cm)%24%2C%20created%20only%20for%20existing%20arcs%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_%7Bn%20%5Cin%20%5Ctext%7BNODES%7D%7D%20%5Cmathit%7Bflow%7D_%7B%5Ctext%7BSOURCE%7D%2Cn%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_m%20%5Cmathit%7Bflow%7D_%7Bm%2Cn%7D%20%3D%20%5Csum_m%20%5Cmathit%7Bflow%7D_%7Bn%2Cm%7D%20%5Cquad%20%5Cforall%5C%2C%20n%20%5Cnotin%20%5C%7B%5Ctext%7BSOURCE%7D%2C%20%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%5Csum_m%20%5Cmathit%7Bflow%7D_%7Bn%2Cm%7D%20%5Cleq%201%20%5Cquad%20%5Cforall%5C%2C%20n%20%5Cnotin%20%5C%7B%5Ctext%7BSOURCE%7D%2C%20%5Ctext%7BSINK%7D%5C%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%20the%20arc%20set%20as%20a%20sparse%20Python%20dict.%20Only%20the%20upper-triangle%20arcs%20%60(n%2Cm)%60%20with%20%24n%20%3C%20m%24%20are%20provided%3B%20the%20Mosel%20model%20fills%20in%20the%20reverse%20direction%20internally%20using%20%60ARC(m%2Cn)%20%3A%3D%20ARC(n%2Cm)%60.%20MoselPy%20reads%20this%20dict%20into%20a%20%60dynamic%20array%60%2C%20creating%20array%20entries%20only%20for%20the%20keys%20present.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20%23%20Upper-triangle%20arcs%20only%20(n%20%3C%20m).%20Model%20adds%20reverse%20direction.%0A%20%20%20%20ARC%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(1%2C2)%3A1%2C(1%2C3)%3A1%2C(1%2C11)%3A1%2C%0A%20%20%20%20%20%20%20%20(2%2C3)%3A1%2C(2%2C8)%3A1%2C(2%2C9)%3A1%2C%0A%20%20%20%20%20%20%20%20(3%2C4)%3A1%2C(3%2C9)%3A1%2C(3%2C10)%3A1%2C(3%2C11)%3A1%2C%0A%20%20%20%20%20%20%20%20(4%2C5)%3A1%2C(4%2C6)%3A1%2C(4%2C11)%3A1%2C%0A%20%20%20%20%20%20%20%20(5%2C9)%3A1%2C(5%2C11)%3A1%2C%0A%20%20%20%20%20%20%20%20(6%2C7)%3A1%2C(6%2C9)%3A1%2C(6%2C10)%3A1%2C%0A%20%20%20%20%20%20%20%20(7%2C8)%3A1%2C(7%2C10)%3A1%2C%0A%20%20%20%20%20%20%20%20(8%2C10)%3A1%2C%0A%20%20%20%20%20%20%20%20(9%2C10)%3A1%2C%0A%20%20%20%20%7D%0A%20%20%20%20print(f%22Network%3A%2011%20nodes%2C%20%7Blen(ARC)%7D%20undirected%20links%22)%0A%20%20%20%20print(f%22Reliability%20question%3A%20can%20SOURCE(10)%20reach%20SINK(11)%20via%204%2B%20vertex-disjoint%20paths%3F%22)%0A%20%20%20%20return%20(ARC%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%20Use%20the%20single%20execution%20command.%20The%20%60ARC%60%20dict%20is%20passed%20as%20%60input_data%60%3B%20Mosel%20creates%20%60flow%60%20variables%20only%20for%20entries%20where%20%60exists(ARC(n%2Cm))%60%20is%20true.%20After%20solving%2C%20%60sol_flow%60%20is%20a%20sparse%20dict%20containing%20only%20the%20arcs%20that%20were%20created%20(i.e.%20the%2022%20undirected%20links%2C%20represented%20as%20directed%20arcs%20in%20both%20directions).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ARC%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20_model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fg1rely_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'ARC'%3A%20ARC%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_g1%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%20SOURCE%2C%20SINK%20%3D%20(10%2C%2011)%0A%20%20%20%20%20%20%20%20paths%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20start%20in%20range(1%2C%2012)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sol_flow.get((SOURCE%2C%20start)%2C%200)%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_path%20%3D%20%5BSOURCE%2C%20start%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20curr%20%3D%20start%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_%20in%20range(12)%3A%20%20%23%20Reconstruct%20the%204%20vertex-disjoint%20paths%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20curr%20%3D%3D%20SINK%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%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nxt%20%3D%20next((m%20for%20m%20in%20range(1%2C%2012)%20if%20sol_flow.get((curr%2C%20m)%2C%200)%20%3E%200.5)%2C%20None)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20nxt%20is%20None%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%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_path.append(nxt)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20curr%20%3D%20nxt%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20paths.append(_path)%0A%20%20%20%20%20%20%20%20reliable%20%3D%20obj_val_g1%20%3E%3D%204%0A%20%20%20%20%20%20%20%20print(f'Maximum%20vertex-disjoint%20paths%20from%20%7BSOURCE%7D%20to%20%7BSINK%7D%3A%20%7Bobj_val_g1%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Network%20reliable%20(%3E%3D%204%20paths)%3A%20%7Breliable%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_i%2C%20_path%20in%20enumerate(paths%2C%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Path%20%7B_i%7D%3A%20%7B'%20-%3E%20'.join((str(n)%20for%20n%20in%20_path))%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%20SINK%2C%20SOURCE%2C%20obj_val_g1%2C%20paths%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%20Network%20graph%20showing%20all%2011%20nodes%20and%2022%20links.%20The%204%20vertex-disjoint%20paths%20from%20SOURCE%20(10)%20to%20SINK%20(11)%20are%20highlighted%20in%20distinct%20colors.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(ARC%2C%20SINK%2C%20SOURCE%2C%20mo%2C%20mpatches%2C%20obj_val_g1%2C%20paths%2C%20plt)%3A%0A%20%20%20%20if%20'paths'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20node_pos%20%3D%20%7B1%3A%20(1%2C%203)%2C%202%3A%20(2%2C%204)%2C%203%3A%20(2%2C%202)%2C%204%3A%20(3%2C%203)%2C%205%3A%20(4%2C%204)%2C%206%3A%20(4%2C%202)%2C%207%3A%20(5%2C%203)%2C%208%3A%20(3%2C%205)%2C%209%3A%20(3%2C%201)%2C%2010%3A%20(5%2C%202)%2C%2011%3A%20(0%2C%202)%7D%0A%20%20%20%20%20%20%20%20path_colors%20%3D%20%5B'%23E53935'%2C%20'%231E88E5'%2C%20'%2343A047'%2C%20'%23FB8C00'%5D%0A%20%20%20%20%20%20%20%20path_edges%20%3D%20set()%0A%20%20%20%20%20%20%20%20for%20_path%20in%20paths%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20k%20in%20range(len(_path)%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20path_edges.add((_path%5Bk%5D%2C%20_path%5Bk%20%2B%201%5D))%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(10%2C%206))%0A%20%20%20%20%20%20%20%20all_arcs%20%3D%20set()%0A%20%20%20%20%20%20%20%20for%20_n%2C%20_m%20in%20ARC%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20all_arcs.add((_n%2C%20_m))%0A%20%20%20%20%20%20%20%20%20%20%20%20all_arcs.add((_m%2C%20_n))%0A%20%20%20%20%20%20%20%20for%20_n%2C%20_m%20in%20all_arcs%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(_n%2C%20_m)%20not%20in%20path_edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x0%2C%20_y0%20%3D%20node_pos%5B_n%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20node_pos%5B_m%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.plot(%5B_x0%2C%20_x1%5D%2C%20%5B_y0%2C%20_y1%5D%2C%20color%3D'%23CFD8DC'%2C%20lw%3D1.5%2C%20zorder%3D1)%0A%20%20%20%20%20%20%20%20for%20_i%2C%20_path%20in%20enumerate(paths)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20k%20in%20range(len(_path)%20-%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_n%2C%20_m%20%3D%20(_path%5Bk%5D%2C%20_path%5Bk%20%2B%201%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x0%2C%20_y0%20%3D%20node_pos%5B_n%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20node_pos%5B_m%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(_x1%2C%20_y1)%2C%20xytext%3D(_x0%2C%20_y0)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3Dpath_colors%5B_i%5D%2C%20lw%3D2.5%2C%20connectionstyle%3D'arc3%2Crad%3D0.1'))%0A%20%20%20%20%20%20%20%20for%20node%2C%20(_x%2C%20_y)%20in%20node_pos.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20'%23E53935'%20if%20node%20%3D%3D%20SOURCE%20else%20'%231E88E5'%20if%20node%20%3D%3D%20SINK%20else%20'%2390CAF9'%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(_x%2C%20_y%2C%20s%3D500%2C%20color%3D_color%2C%20zorder%3D4%2C%20edgecolors%3D'white'%2C%20linewidths%3D1.5)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_x%2C%20_y%2C%20str(node)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D9%2C%20fontweight%3D'bold'%2C%20color%3D'white'%2C%20zorder%3D5)%0A%20%20%20%20%20%20%20%20_legend%20%3D%20%5Bmpatches.Patch(color%3Dpath_colors%5Bi%5D%2C%20label%3Df'Path%20%7Bi%20%2B%201%7D%3A%20%7B'%20-%3E%20'.join((str(n)%20for%20n%20in%20paths%5Bi%5D%5B%3A3%5D))%7D...')%20for%20i%20in%20range(len(paths))%5D%0A%20%20%20%20%20%20%20%20_legend%20%3D%20_legend%20%2B%20%5Bmpatches.Patch(color%3D'%23E53935'%2C%20label%3D'SOURCE%20(10)')%2C%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'SINK%20(11)')%5D%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D_legend%2C%20fontsize%3D8%2C%20loc%3D'upper%20left')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'G-1%20Network%20reliability%3A%20%7Bobj_val_g1%3A.0f%7D%20vertex-disjoint%20paths%20(%3E%3D%204%20required)')%0A%20%20%20%20%20%20%20%20_ax.axis('off')%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%20network%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%20G-2%20Mobile%20network%20dimensioning%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%20G%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g2dimens_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg2dimens_p.mos)%0A%0A%20%20%20%20A%20mobile%20phone%20network%20has%2010%20cells%20that%20must%20each%20be%20connected%20to%20a%205-node%20hub%20ring%3A%204%20simple%20hubs%20(nodes%201-4)%20and%20one%20MTSO%20(node%205).%20Each%20cell%20must%20make%20a%20fixed%20number%20of%20connections%20to%20the%20ring%2C%20constrained%20by%20ring%20capacity.%20The%20model%20minimizes%20total%20connection%20cost.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BCELLS%7D%20%3D%20%5C%7B1%2C%20%5Cldots%2C%2010%5C%7D%24%3B%20%24%5Ctext%7BNODES%7D%20%3D%20%5C%7B1%2C%20%5Cldots%2C%205%5C%7D%24%20(hubs%201-4%20%2B%20MTSO%205)%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BCOST%7D_%7Bc%2Cn%7D%24%20-%20cost%20to%20connect%20cell%20%24c%24%20to%20node%20%24n%24%3B%20%24%5Ctext%7BTRAF%7D_c%24%20-%20traffic%20from%20cell%20%24c%24%3B%20%24%5Ctext%7BCNCT%7D_c%24%20-%20required%20connections%3B%20%24%5Ctext%7BCAP%7D%24%20-%20ring%20segment%20capacity%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bifconnect%7D_%7Bc%2Cn%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20-%201%20if%20cell%20%24c%24%20connected%20to%20node%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_%7Bc%2Cn%7D%20%5Ctext%7BCOST%7D_%7Bc%2Cn%7D%20%5Ccdot%20%5Cmathit%7Bifconnect%7D_%7Bc%2Cn%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_n%20%5Cmathit%7Bifconnect%7D_%7Bc%2Cn%7D%20%3D%20%5Ctext%7BCNCT%7D_c%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%2Cn%20%5Cin%20%5Ctext%7BHUBS%7D%7D%20(%5Ctext%7BTRAF%7D_c%20%2F%20%5Ctext%7BCNCT%7D_c)%20%5Ccdot%20%5Cmathit%7Bifconnect%7D_%7Bc%2Cn%7D%20%5Cleq%202%20%5Ccdot%20%5Ctext%7BCAP%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%20the%20cost%20matrix%20as%20a%20%60pd.DataFrame%60%20with%20cells%20as%20rows%20(1..10)%20and%20nodes%20as%20columns%20(1..5).%20Passing%20a%20DataFrame%20lets%20MoselPy%20map%20rows%20and%20columns%20directly%20to%20the%20Mosel%202D%20array%20%60COST(CELLS%2C%20NODES)%60.%20Traffic%2C%20connection%20counts%2C%20and%20ring%20capacity%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%5B15%2C%208%2C%207%2C11%2C10%5D%2C%0A%20%20%20%20%20%20%20%20%5B%209%2C11%2C%208%2C%205%2C14%5D%2C%0A%20%20%20%20%20%20%20%20%5B12%2C%206%2C%207%2C15%2C15%5D%2C%0A%20%20%20%20%20%20%20%20%5B17%2C%205%2C%209%2C18%2C24%5D%2C%0A%20%20%20%20%20%20%20%20%5B%208%2C22%2C21%2C19%2C%206%5D%2C%0A%20%20%20%20%20%20%20%20%5B%207%2C25%2C15%2C%209%2C17%5D%2C%0A%20%20%20%20%20%20%20%20%5B19%2C25%2C21%2C20%2C22%5D%2C%0A%20%20%20%20%20%20%20%20%5B20%2C%209%2C15%2C18%2C25%5D%2C%0A%20%20%20%20%20%20%20%20%5B21%2C22%2C14%2C16%2C20%5D%2C%0A%20%20%20%20%20%20%20%20%5B25%2C24%2C13%2C%204%2C11%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20COST_G2%20%3D%20pd.DataFrame(cost_rows%2C%20index%3Drange(1%2C11)%2C%20columns%3Drange(1%2C6))%0A%20%20%20%20COST_G2.index.name%20%20%20%3D%20%22Cell%22%0A%20%20%20%20COST_G2.columns.name%20%3D%20%22Node%22%0A%0A%20%20%20%20TRAF%20%3D%20%7Bi%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B22%2C12%2C20%2C12%2C15%2C25%2C15%2C14%2C%208%2C22%5D%2C%201)%7D%0A%20%20%20%20CNCT%20%3D%20%7Bi%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B%202%2C%202%2C%202%2C%202%2C%203%2C%201%2C%203%2C%202%2C%202%2C%202%5D%2C%201)%7D%0A%20%20%20%20CAP_G2%20%3D%2048%0A%0A%20%20%20%20print(f%22Total%20traffic%3A%20%7Bsum(TRAF.values())%7D%20%20%7C%20%20Ring%20capacity%20per%20segment%3A%20%7BCAP_G2%7D%22)%0A%20%20%20%20print(f%22Maximum%20ring%20load%20(2*CAP)%3A%20%7B2*CAP_G2%7D%22)%0A%20%20%20%20print(f%22%5CnCost%20matrix%20(cells%20x%20nodes)%3A%22)%0A%20%20%20%20print(COST_G2.to_string())%0A%20%20%20%20return%20CAP_G2%2C%20CNCT%2C%20COST_G2%2C%20TRAF%2C%20cost_rows%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%60COST_G2%60%20is%20passed%20as%20a%20DataFrame%3B%20MoselPy%20maps%20it%20to%20the%20Mosel%202D%20array.%20%60CAP%60%20is%20passed%20as%20a%20scalar%20via%20%60input_data%60%20to%20match%20the%20Mosel%20identifier%20name.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP_G2%2C%20CNCT%2C%20COST_G2%2C%20MODELS%2C%20TRAF%2C%20cost_rows%2C%20mp)%3A%0A%20%20%20%20_model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fg2dimens_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'COST'%3A%20COST_G2%2C%20'TRAF'%3A%20TRAF%2C%20'CNCT'%3A%20CNCT%2C%20'CAP'%3A%20CAP_G2%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_g2%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_ifconnect%20%3D%20_output%5B'sol_ifconnect'%5D%0A%20%20%20%20%20%20%20%20assignment%20%3D%20%7Bc%3A%20%5Bn%20for%20n%20in%20range(1%2C%206)%20if%20sol_ifconnect.get((c%2C%20n)%2C%200)%20%3E%200.5%5D%20for%20c%20in%20range(1%2C%2011)%7D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20connection%20cost%3A%20%7Bobj_val_g2%3A.0f%7D')%0A%20%20%20%20%20%20%20%20total_ring%20%3D%20sum((TRAF%5Bc%5D%20%2F%20CNCT%5Bc%5D%20*%20(sol_ifconnect.get((c%2C%20n)%2C%200)%20%3E%200.5)%20for%20c%20in%20range(1%2C%2011)%20for%20n%20in%20range(1%2C%205)))%0A%20%20%20%20%20%20%20%20print(f'Total%20ring%20traffic%20load%3A%20%7Btotal_ring%3A.1f%7D%20(limit%3A%20%7B2%20*%20CAP_G2%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_c%20in%20range(1%2C%2011)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nodes_str%20%3D%20'%20'.join((str(n)%20for%20n%20in%20assignment%5B_c%5D))%20%20%23%20Build%20assignment%3A%20cell%20-%3E%20list%20of%20connected%20nodes%0A%20%20%20%20%20%20%20%20%20%20%20%20cost_c%20%3D%20sum((cost_rows%5B_c%20-%201%5D%5Bn%20-%201%5D%20for%20n%20in%20assignment%5B_c%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Cell%20%7B_c%3A%3E2%7D%20-%3E%20nodes%20%5B%7Bnodes_str%7D%5D%20%20cost%3D%7Bcost_c%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_g2%2C%20sol_ifconnect%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%20Grid%20showing%20which%20cells%20connect%20to%20which%20hub%20nodes.%20Each%20cell%20row%20shows%20its%20required%20number%20of%20connections%20(CNCT)%20as%20filled%20squares.%20Node%205%20is%20the%20MTSO.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(cost_rows%2C%20mo%2C%20mpatches%2C%20obj_val_g2%2C%20plt%2C%20sol_ifconnect)%3A%0A%20%20%20%20if%20'assignment'%20in%20locals()%3A%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%20node_labels%20%3D%20%7B1%3A%20'Hub%201'%2C%202%3A%20'Hub%202'%2C%203%3A%20'Hub%203'%2C%204%3A%20'Hub%204'%2C%205%3A%20'MTSO'%7D%0A%20%20%20%20%20%20%20%20colors%20%3D%20%7B'Hub%201'%3A%20'%231E88E5'%2C%20'Hub%202'%3A%20'%2343A047'%2C%20'Hub%203'%3A%20'%23FB8C00'%2C%20'Hub%204'%3A%20'%238E24AA'%2C%20'MTSO'%3A%20'%23E53935'%7D%0A%20%20%20%20%20%20%20%20for%20_c%20in%20range(1%2C%2011)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_n%20in%20range(1%2C%206)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20connected%20%3D%20sol_ifconnect.get((_c%2C%20_n)%2C%200)%20%3E%200.5%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20list(colors.values())%5B_n%20-%201%5D%20if%20connected%20else%20'%23ECEFF1'%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rect%20%3D%20plt.Rectangle((_n%20-%201%2C%2011%20-%20_c)%2C%201%2C%201%2C%20facecolor%3D_color%2C%20edgecolor%3D'white'%2C%20linewidth%3D2)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.add_patch(rect)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20connected%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_n%20-%200.5%2C%2011%20-%20_c%20%2B%200.5%2C%20str(cost_rows%5B_c%20-%201%5D%5B_n%20-%201%5D)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20color%3D'white'%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20_ax.set_xlim(0%2C%205)%0A%20%20%20%20%20%20%20%20_ax.set_ylim(1%2C%2011)%0A%20%20%20%20%20%20%20%20_ax.set_xticks(%5B0.5%2C%201.5%2C%202.5%2C%203.5%2C%204.5%5D)%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(%5Bnode_labels%5Bn%5D%20for%20n%20in%20range(1%2C%206)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_yticks(%5Bi%20%2B%200.5%20for%20i%20in%20range(1%2C%2011)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'Cell%20%7B11%20-%20i%7D'%20for%20i%20in%20range(1%2C%2011)%5D)%0A%20%20%20%20%20%20%20%20_ax.set_title(f'G-2%20Cell-to-hub%20assignments%20(total%20cost%3D%7Bobj_val_g2%3A.0f%7D)')%0A%20%20%20%20%20%20%20%20_legend%20%3D%20%5Bmpatches.Patch(color%3Dc%2C%20label%3Dl)%20for%20l%2C%20c%20in%20colors.items()%5D%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D_legend%2C%20loc%3D'lower%20right'%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%20assignment%20grid')%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%20G-3%20Routing%20telephone%20calls%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%20G%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g3routing_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg3routing_p.mos)%0A%0A%20%20%20%20A%20private%20telephone%20network%20connects%205%20French%20cities%20(Nantes%2C%20Nice%2C%20Paris%2C%20Troyes%2C%20Valenciennes)%20via%206%20bidirectional%20arcs%20with%20known%20capacities.%20Five%20call%20demands%20between%20city%20pairs%20must%20be%20routed%20through%20the%20network.%20Each%20demand%20can%20be%20split%20across%20multiple%20paths.%20The%20model%20maximizes%20total%20routed%20traffic.%0A%0A%20%20%20%20The%20model%20reads%20data%20in%20two%20%60initializations%20from%60%20blocks%3A%20the%20first%20populates%20arc%20capacities%2C%20demands%2C%20and%20the%20path-to-demand%20mapping%3B%20the%20second%20reads%20the%20route%20definitions%20once%20the%20number%20of%20arcs%20(%60NARC%60)%20is%20known.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BCALLS%7D%24%20(city%20pairs%2C%20auto-populated%20from%20DEM)%3B%20%24%5Ctext%7BARCS%7D%24%20(network%20links%2C%20auto-populated%20from%20CAP)%3B%20%24%5Ctext%7BPATHS%7D%20%3D%20%5C%7B1%2C%5Cldots%2C17%5C%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bflow%7D_p%20%5Cgeq%200%24%20integer%20-%20calls%20routed%20on%20path%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%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bp%7D%20%5Cmathit%7Bflow%7D_p%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bp%3A%5C%2C%20%5Ctext%7BCINDEX%7D_p%20%3D%20d%7D%20%5Cmathit%7Bflow%7D_p%20%5Cleq%20%5Ctext%7BDEM%7D_d%20%5Cquad%20%5Cforall%5C%2C%20d%20%5Cin%20%5Ctext%7BCALLS%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_%7Bp%2Cb%3A%5C%2C%20%5Ctext%7BROUTE%7D_%7Bp%2Cb%7D%20%3D%20a%7D%20%5Cmathit%7Bflow%7D_p%20%5Cleq%20%5Ctext%7BCAP%7D_a%20%5Cquad%20%5Cforall%5C%2C%20a%20%5Cin%20%5Ctext%7BARCS%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%20capacities%20%60CAP%60%20and%20call%20demands%20%60DEM%60%20as%20string-keyed%20dicts.%20The%20%60CINDEX%60%20dict%20maps%20each%20path%20index%20to%20its%20demand%20pair.%20The%20route%20array%20%60ROUTE%60%20uses%20%60(path%2C%20arc_position)%60%20tuple%20keys%3B%20only%20non-empty%20positions%20need%20to%20be%20specified%20(Mosel%20string%20arrays%20default%20to%20%60%22%5C%22%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%20CAP%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22Nantes-Paris%22%3A300%2C%20%22Nantes-Nice%22%3A120%2C%20%22Paris-Nice%22%3A300%2C%0A%20%20%20%20%20%20%20%20%22Paris-Valenciennes%22%3A200%2C%20%22Troyes-Nice%22%3A80%2C%20%22Troyes-Valenciennes%22%3A70%2C%0A%20%20%20%20%7D%0A%20%20%20%20DEM%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22Nantes-Nice%22%3A100%2C%20%22Nantes-Troyes%22%3A80%2C%20%22Nantes-Valenciennes%22%3A75%2C%0A%20%20%20%20%20%20%20%20%22Nice-Valenciennes%22%3A100%2C%20%22Paris-Troyes%22%3A70%2C%0A%20%20%20%20%7D%0A%20%20%20%20CINDEX%20%3D%20%7B%0A%20%20%20%20%20%20%20%201%3A%22Nantes-Nice%22%2C%20%20%202%3A%22Nantes-Nice%22%2C%20%20%203%3A%22Nantes-Nice%22%2C%0A%20%20%20%20%20%20%20%204%3A%22Nantes-Troyes%22%2C%205%3A%22Nantes-Troyes%22%2C%206%3A%22Nantes-Troyes%22%2C%207%3A%22Nantes-Troyes%22%2C%0A%20%20%20%20%20%20%20%208%3A%22Nantes-Valenciennes%22%2C%209%3A%22Nantes-Valenciennes%22%2C%0A%20%20%20%20%20%20%20%2010%3A%22Nantes-Valenciennes%22%2C11%3A%22Nantes-Valenciennes%22%2C%0A%20%20%20%20%20%20%20%2012%3A%22Nice-Valenciennes%22%2C%20%2013%3A%22Nice-Valenciennes%22%2C%2014%3A%22Nice-Valenciennes%22%2C%0A%20%20%20%20%20%20%20%2015%3A%22Paris-Troyes%22%2C%2016%3A%22Paris-Troyes%22%2C%2017%3A%22Paris-Troyes%22%2C%0A%20%20%20%20%7D%0A%20%20%20%20routes_raw%20%3D%20%7B%0A%20%20%20%20%20%20%20%201%3A%20%5B%22Nantes-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%202%3A%20%5B%22Nantes-Paris%22%2C%22Paris-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%203%3A%20%5B%22Nantes-Paris%22%2C%22Paris-Valenciennes%22%2C%22Valenciennes-Troyes%22%2C%22Troyes-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%204%3A%20%5B%22Nantes-Paris%22%2C%22Paris-Valenciennes%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%205%3A%20%5B%22Nantes-Paris%22%2C%22Paris-Nice%22%2C%22Troyes-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%206%3A%20%5B%22Nantes-Nice%22%2C%22Troyes-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%207%3A%20%5B%22Nantes-Nice%22%2C%22Paris-Nice%22%2C%22Paris-Valenciennes%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%208%3A%20%5B%22Nantes-Paris%22%2C%22Paris-Valenciennes%22%5D%2C%0A%20%20%20%20%20%20%20%209%3A%20%5B%22Nantes-Nice%22%2C%22Paris-Nice%22%2C%22Paris-Valenciennes%22%5D%2C%0A%20%20%20%20%20%20%20%2010%3A%5B%22Nantes-Paris%22%2C%22Paris-Nice%22%2C%22Troyes-Nice%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%2011%3A%5B%22Nantes-Nice%22%2C%22Troyes-Nice%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%2012%3A%5B%22Nantes-Nice%22%2C%22Nantes-Paris%22%2C%22Paris-Valenciennes%22%5D%2C%0A%20%20%20%20%20%20%20%2013%3A%5B%22Paris-Nice%22%2C%22Paris-Valenciennes%22%5D%2C%0A%20%20%20%20%20%20%20%2014%3A%5B%22Troyes-Nice%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%2015%3A%5B%22Paris-Valenciennes%22%2C%22Valenciennes-Troyes%22%5D%2C%0A%20%20%20%20%20%20%20%2016%3A%5B%22Nantes-Paris%22%2C%22Nantes-Nice%22%2C%22Troyes-Nice%22%5D%2C%0A%20%20%20%20%20%20%20%2017%3A%5B%22Paris-Nice%22%2C%22Troyes-Nice%22%5D%2C%0A%20%20%20%20%7D%0A%20%20%20%20%23%20Only%20non-empty%20(path%2C%20arc_position)%20entries%20needed%20-%20string%20arrays%20default%20to%20%22%22%0A%20%20%20%20ROUTE%20%3D%20%7B(p%2C%20b%2B1)%3A%20arc%20for%20p%2C%20arcs%20in%20routes_raw.items()%20for%20b%2C%20arc%20in%20enumerate(arcs)%7D%0A%0A%20%20%20%20print(f%22Demands%3A%20%7Blen(DEM)%7D%20city%20pairs%20%20%7C%20%20Arcs%3A%20%7Blen(CAP)%7D%20%20%7C%20%20Paths%3A%20%7Blen(routes_raw)%7D%22)%0A%20%20%20%20print(f%22Total%20demand%3A%20%7Bsum(DEM.values())%7D%20calls%22)%0A%20%20%20%20return%20CAP%2C%20CINDEX%2C%20DEM%2C%20ROUTE%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.%20The%20model%20reads%20data%20in%20two%20consecutive%20%60initializations%20from%20%22moselpy%3A%22%60%20blocks.%20Both%20blocks%20draw%20from%20the%20same%20%60input_data%60%20dict%3B%20the%20second%20block%20reads%20%60ROUTE%60%20after%20the%20model%20has%20computed%20%60NARC%20%3D%20getsize(ARCS)%60%20from%20the%20first%20block.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP%2C%20CINDEX%2C%20DEM%2C%20MODELS%2C%20ROUTE%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fg3routing_p.mos'%2C%20f'%7BMODELS%7D%2Fg3routing_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fg3routing_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'CAP'%3A%20CAP%2C%20'DEM'%3A%20DEM%2C%20'CINDEX'%3A%20CINDEX%2C%20'ROUTE'%3A%20ROUTE%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_g3%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_flow_1%20%3D%20_output%5B'sol_flow'%5D%0A%20%20%20%20%20%20%20%20sol_route%20%3D%20_output%5B'sol_route'%5D%0A%20%20%20%20%20%20%20%20print(f'Total%20routed%20calls%3A%20%7Bobj_val_g3%3A.0f%7D%20%20(total%20demand%3A%20%7Bsum(DEM.values())%7D)')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_demand%2C%20dem_val%20in%20sorted(DEM.items())%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_routed%20%3D%20sum((sol_flow_1.get(p%2C%200)%20for%20p%2C%20d%20in%20CINDEX.items()%20if%20d%20%3D%3D%20_demand))%0A%20%20%20%20%20%20%20%20%20%20%20%20unmet%20%3D%20dem_val%20-%20_routed%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_demand%3A%3C25%7D%20%20demand%3D%7Bdem_val%3A%3E3%7D%20%20routed%3D%7B_routed%3A.0f%7D'%20%2B%20(f'%20%20UNMET%3D%7Bunmet%3A.0f%7D'%20if%20unmet%20%3E%200.5%20else%20''))%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20p%20in%20sorted((k%20for%20k%2C%20d%20in%20CINDEX.items()%20if%20d%20%3D%3D%20_demand%20and%20sol_flow_1.get(k%2C%200)%20%3E%200.5))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20arcs%20%3D%20%5Bsol_route.get((p%2C%20b%20%2B%201)%2C%20'')%20for%20b%20in%20range(6)%20if%20sol_route.get((p%2C%20b%20%2B%201)%2C%20'')%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%20%20Path%20%7Bp%3A%3E2%7D%3A%20%7Bsol_flow_1%5Bp%5D%3A.0f%7D%20calls%20via%20%7B'%20%7C%20'.join(arcs)%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_g3%2C%20sol_flow_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%20Bar%20chart%20comparing%20demand%20vs%20routed%20calls%20for%20each%20city%20pair.%20Unmet%20demand%20is%20shown%20as%20a%20lighter%20overlay.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CINDEX%2C%20DEM%2C%20mo%2C%20obj_val_g3%2C%20plt%2C%20sol_flow_1)%3A%0A%20%20%20%20if%20'sol_flow'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20demands_list%20%3D%20sorted(DEM.keys())%0A%20%20%20%20%20%20%20%20routed_vals%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20unmet_vals%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_demand%20in%20demands_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_routed%20%3D%20sum((sol_flow_1.get(p%2C%200)%20for%20p%2C%20d%20in%20CINDEX.items()%20if%20d%20%3D%3D%20_demand))%0A%20%20%20%20%20%20%20%20%20%20%20%20routed_vals.append(_routed)%0A%20%20%20%20%20%20%20%20%20%20%20%20unmet_vals.append(DEM%5B_demand%5D%20-%20_routed)%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%20_x%20%3D%20range(len(demands_list))%0A%20%20%20%20%20%20%20%20_ax.bar(_x%2C%20routed_vals%2C%20color%3D'%231E88E5'%2C%20edgecolor%3D'white'%2C%20label%3D'Routed')%0A%20%20%20%20%20%20%20%20_ax.bar(_x%2C%20unmet_vals%2C%20bottom%3Drouted_vals%2C%20color%3D'%23FFCDD2'%2C%20edgecolor%3D'white'%2C%20label%3D'Unmet%20demand')%0A%20%20%20%20%20%20%20%20for%20_i%2C%20(r%2C%20u)%20in%20enumerate(zip(routed_vals%2C%20unmet_vals))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_i%2C%20r%20%2F%202%2C%20f'%7Br%3A.0f%7D'%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D9%2C%20color%3D'white'%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20u%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_i%2C%20r%20%2B%20u%20%2F%202%2C%20f'-%7Bu%3A.0f%7D'%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20color%3D'%23C62828')%0A%20%20%20%20%20%20%20%20_ax.set_xticks(_x)%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(%5Bd.replace('-'%2C%20'-%5Cn')%20for%20d%20in%20demands_list%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Calls')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'G-3%20Telephone%20routing%20-%20total%20routed%3A%20%7Bobj_val_g3%3A.0f%7D%20%2F%20%7Bsum(DEM.values())%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%20routing%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%20G-4%20Cabled%20network%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%20G%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g4cable_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg4cable_p.mos)%0A%0A%20%20%20%20A%20university%20wants%20to%20connect%206%20terminal%20buildings%20with%20underground%20cables.%20Cables%20must%20form%20a%20connected%20spanning%20tree%20(no%20closed%20loops).%20A%20simple%20degree%20constraint%20is%20insufficient%20to%20avoid%20sub-cycles%2C%20so%20each%20node%20is%20assigned%20a%20level%20variable%20used%20in%20additional%20linear%20constraints%20to%20enforce%20a%20spanning%20tree%20structure%20rooted%20at%20terminal%201.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BTERMINALS%7D%20%3D%20%5C%7B1%2C%20%5Cldots%2C%206%5C%7D%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BDIST%7D_%7Bs%2Ct%7D%24%20-%20distance%20between%20terminals%20(meters)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bifconnect%7D_%7Bs%2Ct%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Blevel%7D_t%20%5Cgeq%200%24%0A%0A%20%20%20%20%24%24%0A%20%20%20%20%5Cbegin%7Balign*%7D%0A%20%20%20%20%5Ctext%7Bminimize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Bs%20%5Cneq%20t%7D%20%5Ctext%7BDIST%7D_%7Bs%2Ct%7D%20%5Ccdot%20%5Cmathit%7Bifconnect%7D_%7Bs%2Ct%7D%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_%7Bs%20%5Cneq%20t%7D%20%5Cmathit%7Bifconnect%7D_%7Bs%2Ct%7D%20%3D%205%20%5Cquad%20(%5Ctext%7Bspanning%20tree%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%20%5Cneq%20s%7D%20%5Cmathit%7Bifconnect%7D_%7Bs%2Ct%7D%20%3D%201%20%5Cquad%20%5Cforall%5C%2C%20s%20%5Cin%20%5C%7B2%2C%5Cldots%2C6%5C%7D%20%5Cquad%20(%5Ctext%7Beach%20non-root%20connects%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%7Blevel%7D_t%20%5Cgeq%20%5Cmathit%7Blevel%7D_s%20%2B%201%20-%206%20%2B%206%20%5Ccdot%20%5Cmathit%7Bifconnect%7D_%7Bs%2Ct%7D%20%5Cquad%20(%5Ctext%7Bno%20sub-cycles%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%20the%206x6%20distance%20matrix%20as%20a%20Python%20dict%20with%20%60(s%2Ct)%60%20tuple%20keys.%20The%20matrix%20is%20symmetric%3B%20all%2030%20off-diagonal%20entries%20are%20provided.%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%20dist_rows%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B%20%200%2C%20120%2C%20%2092%2C%20265%2C%20149%2C%20194%5D%2C%0A%20%20%20%20%20%20%20%20%5B120%2C%20%20%200%2C%20141%2C%20170%2C%20%2093%2C%20164%5D%2C%0A%20%20%20%20%20%20%20%20%5B%2092%2C%20141%2C%20%20%200%2C%20218%2C%20103%2C%20116%5D%2C%0A%20%20%20%20%20%20%20%20%5B265%2C%20170%2C%20218%2C%20%20%200%2C%20110%2C%20126%5D%2C%0A%20%20%20%20%20%20%20%20%5B149%2C%20%2093%2C%20103%2C%20110%2C%20%20%200%2C%20%2072%5D%2C%0A%20%20%20%20%20%20%20%20%5B194%2C%20164%2C%20116%2C%20126%2C%20%2072%2C%20%20%200%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20DIST_G4%20%3D%20%7B(i%2B1%2C%20j%2B1)%3A%20dist_rows%5Bi%5D%5Bj%5D%20for%20i%20in%20range(6)%20for%20j%20in%20range(6)%7D%0A%0A%20%20%20%20print(%22Distance%20matrix%20(meters)%3A%22)%0A%20%20%20%20dm%20%3D%20pd.DataFrame(dist_rows%2C%20index%3Drange(1%2C7)%2C%20columns%3Drange(1%2C7))%0A%20%20%20%20dm.index.name%20%3D%20dm.columns.name%20%3D%20%22Terminal%22%0A%20%20%20%20print(dm.to_string())%0A%20%20%20%20return%20DIST_G4%2C%20dist_rows%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.%20Before%20running%20the%20model%2C%20inspect%20its%20metadata%20using%20%60model.name%60%2C%20%60model.version%60%2C%20and%20%60model.size%60%20-%20these%20properties%20are%20available%20immediately%20after%20%60load_model()%60%20without%20executing%20the%20model.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DIST_G4%2C%20MODELS%2C%20dist_rows%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fg4cable_p.mos'%2C%20f'%7BMODELS%7D%2Fg4cable_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fg4cable_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20print('Model%20metadata%20(before%20run)%3A')%0A%20%20%20%20print(f'%20%20name%3A%20%20%20%20%7B_model.name%7D')%0A%20%20%20%20print(f'%20%20version%3A%20%7B_model.version%7D')%0A%20%20%20%20print(f'%20%20size%3A%20%20%20%20%7B_model.size%3A%2C%7D%20bytes')%0A%20%20%20%20print()%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'DIST'%3A%20DIST_G4%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_g4%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_ifconnect_1%20%3D%20_output%5B'sol_ifconnect'%5D%0A%20%20%20%20%20%20%20%20edges%20%3D%20%5B(s%2C%20t)%20for%20s%20in%20range(1%2C%207)%20for%20t%20in%20range(1%2C%207)%20if%20s%20!%3D%20t%20and%20sol_ifconnect_1.get((s%2C%20t)%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20print(f'Minimum%20cable%20length%3A%20%7Bobj_val_g4%3A.0f%7D%20meters')%0A%20%20%20%20%20%20%20%20print(f'Connections%20(directed%2C%20rooted%20at%20terminal%201)%3A')%0A%20%20%20%20%20%20%20%20for%20_s%2C%20_t%20in%20sorted(edges)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Terminal%20%7B_s%7D%20-%3E%20Terminal%20%7B_t%7D%20%20(%7Bdist_rows%5B_s%20-%201%5D%5B_t%20-%201%5D%7D%20m)')%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%20edges%2C%20obj_val_g4%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%20Spanning%20tree%20visualization.%20Terminals%20are%20arranged%20by%20distance%20from%20the%20root%20(terminal%201).%20Selected%20cable%20connections%20are%20shown%20as%20solid%20lines.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dist_rows%2C%20edges%2C%20mo%2C%20mpatches%2C%20obj_val_g4%2C%20plt)%3A%0A%20%20%20%20if%20'edges'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20import%20math%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%20tpos%20%3D%20%7B1%3A%20(3%2C%204)%2C%202%3A%20(5%2C%203)%2C%203%3A%20(2%2C%203)%2C%204%3A%20(5%2C%201)%2C%205%3A%20(4%2C%202)%2C%206%3A%20(5%2C%202)%7D%0A%20%20%20%20%20%20%20%20for%20_s%20in%20range(1%2C%207)%3A%20%20%23%20Manual%20positions%20for%20a%20clear%20spanning%20tree%20layout%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_t%20in%20range(_s%20%2B%201%2C%207)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x0%2C%20_y0%20%3D%20tpos%5B_s%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20tpos%5B_t%5D%20%20%23%20Draw%20all%20terminal%20pairs%20(light%20background)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.plot(%5B_x0%2C%20_x1%5D%2C%20%5B_y0%2C%20_y1%5D%2C%20color%3D'%23ECEFF1'%2C%20lw%3D1%2C%20zorder%3D1)%0A%20%20%20%20%20%20%20%20for%20_s%2C%20_t%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_x0%2C%20_y0%20%3D%20tpos%5B_s%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20tpos%5B_t%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.plot(%5B_x0%2C%20_x1%5D%2C%20%5B_y0%2C%20_y1%5D%2C%20color%3D'%231E88E5'%2C%20lw%3D3%2C%20zorder%3D2)%0A%20%20%20%20%20%20%20%20%20%20%20%20mx%2C%20my%20%3D%20((_x0%20%2B%20_x1)%20%2F%202%2C%20(_y0%20%2B%20_y1)%20%2F%202)%20%20%23%20Draw%20cable%20connections%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(mx%2C%20my%20%2B%200.08%2C%20f'%7Bdist_rows%5B_s%20-%201%5D%5B_t%20-%201%5D%7Dm'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D8%2C%20color%3D'%231565C0')%0A%20%20%20%20%20%20%20%20for%20_t%2C%20(_x%2C%20_y)%20in%20tpos.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20'%23E53935'%20if%20_t%20%3D%3D%201%20else%20'%2343A047'%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(_x%2C%20_y%2C%20s%3D500%2C%20color%3D_color%2C%20zorder%3D4%2C%20edgecolors%3D'white'%2C%20linewidths%3D2)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_x%2C%20_y%2C%20str(_t)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D10%2C%20fontweight%3D'bold'%2C%20color%3D'white'%2C%20zorder%3D5)%0A%20%20%20%20%20%20%20%20root_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Root%20terminal%20(1)')%0A%20%20%20%20%20%20%20%20term_p%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3D'Terminal')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Broot_p%2C%20term_p%5D%2C%20fontsize%3D9)%20%20%23%20Draw%20terminals%0A%20%20%20%20%20%20%20%20_ax.set_title(f'G-4%20Cable%20network%20spanning%20tree%20-%20total%20length%20%3D%20%7Bobj_val_g4%3A.0f%7D%20m')%0A%20%20%20%20%20%20%20%20_ax.set_xlim(0.5%2C%206.5)%0A%20%20%20%20%20%20%20%20_ax.set_ylim(0%2C%205)%0A%20%20%20%20%20%20%20%20_ax.axis('off')%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%20cable%20network%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%20G-5%20Satellite%20scheduling%20(iterative%20MIP)%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%20G%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g5satell_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg5satell_p.mos)%0A%0A%20%20%20%20A%20satellite%20serves%204%20U.S.%20transmitters%20and%204%20European%20receivers.%20In%20each%20time%20slot%20(mode)%2C%20each%20transmitter%20is%20paired%20with%20exactly%20one%20receiver.%20A%20mode%20duration%20equals%20the%20minimum%20traffic%20packet%20that%20can%20be%20served.%20The%20model%20uses%20the%20Gonzalez-Sahni%20algorithm%3A%20it%20transforms%20the%20traffic%20matrix%20into%20a%20quasi-bistochastic%20form%2C%20then%20iteratively%20solves%20a%20matching%20MIP%20to%20find%20the%20longest%20serviceable%20packet%2C%20updates%20the%20traffic%20matrix%2C%20and%20repeats%20until%20all%20traffic%20is%20served.%0A%0A%20%20%20%20The%20entire%20iterative%20algorithm%20runs%20inside%20a%20single%20Mosel%20model%20execution.%20MoselPy%20calls%20%60execute_model()%60%20once%20and%20receives%20the%20complete%20schedule%20as%20output.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BTRANSM%7D%20%3D%20%5C%7B1%2C%5Cldots%2C4%5C%7D%24%3B%20%24%5Ctext%7BRECV%7D%20%3D%20%5C%7B1%2C%5Cldots%2C4%5C%7D%24%3B%20%24%5Ctext%7BMODES%7D%24%20(dynamic%20range%2C%20grows%20per%20iteration)%0A%0A%20%20%20%20**Decision%20variables%20per%20iteration%3A**%20%24%5Cmathit%7Bflow%7D_%7Bt%2Cr%7D%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20(transmitter-receiver%20pairing)%3B%20%24%5Cmathit%7Bpmin%7D%20%5Cgeq%200%24%20(mode%20duration)%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%204x4%20traffic%20matrix%20%60TRAF%60%20as%20a%20dict%20with%20%60(transmitter%2C%20receiver)%60%20tuple%20keys.%20Traffic%20values%20represent%20units%20of%20data%20to%20be%20transmitted%20between%20each%20station%20pair.%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%20traf_raw%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B%200%2C%20%207%2C%2011%2C%2015%5D%2C%0A%20%20%20%20%20%20%20%20%5B15%2C%20%208%2C%2013%2C%20%209%5D%2C%0A%20%20%20%20%20%20%20%20%5B17%2C%2012%2C%20%206%2C%2010%5D%2C%0A%20%20%20%20%20%20%20%20%5B%206%2C%2013%2C%2015%2C%20%204%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20TRAF_G5%20%3D%20%7B(i%2B1%2C%20j%2B1)%3A%20traf_raw%5Bi%5D%5Bj%5D%20for%20i%20in%20range(4)%20for%20j%20in%20range(4)%7D%0A%0A%20%20%20%20traf_df%20%3D%20pd.DataFrame(traf_raw%2C%20index%3D%5Bf%22T%7Bi%2B1%7D%22%20for%20i%20in%20range(4)%5D%2C%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%20columns%3D%5Bf%22R%7Bj%2B1%7D%22%20for%20j%20in%20range(4)%5D)%0A%20%20%20%20traf_df.index.name%20%3D%20%22Transmitter%22%0A%20%20%20%20traf_df.columns.name%20%3D%20%22Receiver%22%0A%20%20%20%20print(%22Traffic%20matrix%20(units%20to%20transmit)%3A%22)%0A%20%20%20%20print(traf_df.to_string())%0A%20%20%20%20print(f%22Total%20traffic%3A%20%7Bsum(traf_raw%5Bi%5D%5Bj%5D%20for%20i%20in%20range(4)%20for%20j%20in%20range(4))%7D%22)%0A%20%20%20%20return%20(TRAF_G5%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%20Use%20the%20context%20manager%20form%20of%20%60mp.load_model()%60.%20The%20iterative%20algorithm%20inside%20the%20Mosel%20model%20solves%20multiple%20MIP%20problems%20in%20a%20%60while%60%20loop%2C%20updating%20the%20traffic%20matrix%20after%20each%20mode%20until%20all%20traffic%20is%20scheduled.%20Python%20receives%20the%20complete%20schedule%20(mode%20durations%20and%20pairings)%20in%20a%20single%20%60output%60%20dict.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20TRAF_G5%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fg5satell_p.mos'%2C%20f'%7BMODELS%7D%2Fg5satell_p.bim')%0A%20%20%20%20with%20mp.load_model(f'%7BMODELS%7D%2Fg5satell_p.bim')%20as%20_model%3A%0A%20%20%20%20%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%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'%7D%2C%20input_data%3D%7B'TRAF'%3A%20TRAF_G5%7D)%0A%20%20%20%20%20%20%20%20prob_ok%20%3D%20_model.prob_status.is_solution_available%0A%20%20%20%20if%20prob_ok%3A%0A%20%20%20%20%20%20%20%20obj_val_g5%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20solpmin%20%3D%20_output%5B'solpmin'%5D%0A%20%20%20%20%20%20%20%20solflowt%20%3D%20_output%5B'solflowt'%5D%0A%20%20%20%20%20%20%20%20solflowr%20%3D%20_output%5B'solflowr'%5D%0A%20%20%20%20%20%20%20%20nmodes%20%3D%20max((m%20for%20t%2C%20m%20in%20solflowr.keys()))%0A%20%20%20%20%20%20%20%20schedule%20%3D%20%7B(t%2C%20m)%3A%20int(solflowr.get((t%2C%20m)%2C%200))%20for%20t%20in%20range(1%2C%205)%20for%20m%20in%20range(1%2C%20nmodes%20%2B%201)%7D%0A%20%20%20%20%20%20%20%20print(f'Total%20schedule%20duration%3A%20%7Bobj_val_g5%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Number%20of%20modes%3A%20%7Bnmodes%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f'%7B'Mode'%3A%3E5%7D%20%20%7B'Duration'%3A%3E9%7D%20%20%7B'T1-%3E'%3A%3E5%7D%20%20%7B'T2-%3E'%3A%3E5%7D%20%20%7B'T3-%3E'%3A%3E5%7D%20%20%7B'T4-%3E'%3A%3E5%7D')%0A%20%20%20%20%20%20%20%20_cumtime%20%3D%200%0A%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%20nmodes%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_dur%20%3D%20int(solpmin.get(_m%2C%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20pairs%20%3D%20%5Bf'R%7Bschedule%5Bt%2C%20_m%5D%7D'%20if%20schedule%5Bt%2C%20_m%5D%20%3E%200%20else%20'%20%20-'%20for%20t%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_m%3A%3E3%7D%20%20%20%20%7B_cumtime%3A%3E4%7D-%7B_cumtime%20%2B%20_dur%3A%3C4%7D%20%20%20%7B'%20%20'.join(pairs)%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20_cumtime%20%3D%20_cumtime%20%2B%20_dur%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20found')%0A%20%20%20%20return%20nmodes%2C%20obj_val_g5%2C%20schedule%2C%20solpmin%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Gantt%20chart%20of%20the%20satellite%20schedule.%20Each%20row%20represents%20one%20transmitter%3B%20colored%20blocks%20show%20which%20receiver%20it%20is%20paired%20with%20in%20each%20mode.%20Block%20width%20is%20proportional%20to%20mode%20duration.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20mpatches%2C%20nmodes%2C%20obj_val_g5%2C%20plt%2C%20schedule%2C%20solpmin)%3A%0A%20%20%20%20if%20'schedule'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20recv_colors%20%3D%20%7B1%3A%20'%231E88E5'%2C%202%3A%20'%2343A047'%2C%203%3A%20'%23FB8C00'%2C%204%3A%20'%238E24AA'%7D%0A%20%20%20%20%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(4%2C%201%2C%20figsize%3D(12%2C%206)%2C%20sharex%3DTrue)%0A%20%20%20%20%20%20%20%20for%20_t%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B_t%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_cumtime%20%3D%200%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_m%20in%20range(1%2C%20nmodes%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_dur%20%3D%20int(solpmin.get(_m%2C%200))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20recv%20%3D%20schedule%5B_t%2C%20_m%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20recv_colors.get(recv%2C%20'%2390CAF9')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.barh(0%2C%20_dur%2C%20left%3D_cumtime%2C%20height%3D0.7%2C%20color%3D_color%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_dur%20%3E%3D%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_cumtime%20%2B%20_dur%20%2F%202%2C%200%2C%20f'R%7Brecv%7D'%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20color%3D'white'%2C%20fontweight%3D'bold')%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_cumtime%20%3D%20_cumtime%20%2B%20_dur%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_yticks(%5B0%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_yticklabels(%5Bf'T%7B_t%7D'%5D%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.set_xlim(0%2C%20obj_val_g5)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.axvline(obj_val_g5%2C%20color%3D'%2390A4AE'%2C%20lw%3D1%2C%20linestyle%3D'--')%0A%20%20%20%20%20%20%20%20_axes%5B-1%5D.set_xlabel('Time%20(data%20units)')%0A%20%20%20%20%20%20%20%20_axes%5B0%5D.set_title(f'G-5%20Satellite%20schedule%20-%20total%20duration%20%3D%20%7Bobj_val_g5%3A.0f%7D%20%20(%7Bnmodes%7D%20modes)')%0A%20%20%20%20%20%20%20%20_legend%20%3D%20%5Bmpatches.Patch(color%3Drecv_colors%5Br%5D%2C%20label%3Df'Receiver%20R%7Br%7D')%20for%20r%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20_fig.legend(handles%3D_legend%2C%20loc%3D'lower%20right'%2C%20ncol%3D4%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%20Gantt%20chart')%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20G-6%20Transmitter%20placement%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%20G%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60g6transmit_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Ftelecomm%2Fg6transmit_p.mos)%0A%0A%20%20%20%20A%20mobile%20phone%20operator%20plans%20to%20equip%20an%20uncovered%20region.%20There%20are%207%20candidate%20transmitter%20locations.%20Each%20location%20can%20cover%20a%20subset%20of%2015%20communities%20with%20known%20populations.%20Which%20transmitters%20should%20be%20built%20to%20maximize%20total%20population%20covered%2C%20subject%20to%20a%20budget%20constraint%3F%0A%0A%20%20%20%20The%20coverage%20data%20is%20sparse%3A%20only%20%60(place%2C%20community)%60%20pairs%20where%20%60COVER(p%2Cc)%20%3D%201%60%20are%20specified%3B%20all%20other%20entries%20default%20to%200%20in%20Mosel's%20dense%20array.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BPLACES%7D%20%3D%20%5C%7B1%2C%5Cldots%2C7%5C%7D%24%3B%20%24%5Ctext%7BCOMMS%7D%20%3D%20%5C%7B1%2C%5Cldots%2C15%5C%7D%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bbuild%7D_p%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Bcovered%7D_c%20%5Cin%20%5C%7B0%2C1%5C%7D%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_c%20%5Ctext%7BPOP%7D_c%20%5Ccdot%20%5Cmathit%7Bcovered%7D_c%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Csum_p%20%5Ctext%7BCOVER%7D_%7Bp%2Cc%7D%20%5Ccdot%20%5Cmathit%7Bbuild%7D_p%20%5Cgeq%20%5Cmathit%7Bcovered%7D_c%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_p%20%5Ctext%7BCOST%7D_p%20%5Ccdot%20%5Cmathit%7Bbuild%7D_p%20%5Cleq%20%5Ctext%7BBUDGET%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%20%60COST%60%20as%20a%201-indexed%20dict%20and%20%60POP%60%20as%20a%201-indexed%20dict.%20The%20%60COVER%60%20array%20is%20provided%20as%20a%20sparse%20dict%20with%20only%20the%20non-zero%20%60(place%2C%20community)%60%20entries.%20Mosel%20initializes%20the%20remaining%202D%20array%20entries%20to%200.%20%60BUDGET%60%20is%20an%20integer%20passed%20via%20%60exec_params%60%20to%20enable%20easy%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%20COST_G6%20%3D%20%7Bi%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B1.8%2C%201.3%2C%204.0%2C%203.5%2C%203.8%2C%202.6%2C%202.1%5D%2C%201)%7D%0A%20%20%20%20POP_G6%20%20%3D%20%7Bi%3A%20v%20for%20i%2C%20v%20in%20enumerate(%5B2%2C4%2C13%2C6%2C9%2C4%2C8%2C12%2C10%2C11%2C6%2C14%2C9%2C3%2C6%5D%2C%201)%7D%0A%0A%20%20%20%20%23%20Sparse%20coverage%3A%20only%20(place%2C%20community)%20pairs%20that%20equal%201%0A%20%20%20%20cover_entries%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(1%2C1)%2C(1%2C2)%2C(1%2C4)%2C%0A%20%20%20%20%20%20%20%20(2%2C2)%2C(2%2C3)%2C(2%2C5)%2C%0A%20%20%20%20%20%20%20%20(3%2C4)%2C(3%2C7)%2C(3%2C8)%2C(3%2C10)%2C%0A%20%20%20%20%20%20%20%20(4%2C5)%2C(4%2C6)%2C(4%2C8)%2C(4%2C9)%2C%0A%20%20%20%20%20%20%20%20(5%2C8)%2C(5%2C9)%2C(5%2C12)%2C%0A%20%20%20%20%20%20%20%20(6%2C7)%2C(6%2C10)%2C(6%2C11)%2C(6%2C12)%2C(6%2C15)%2C%0A%20%20%20%20%20%20%20%20(7%2C12)%2C(7%2C13)%2C(7%2C14)%2C(7%2C15)%2C%0A%20%20%20%20%5D%0A%20%20%20%20COVER_G6%20%3D%20%7B(p%2Cc)%3A%201%20for%20p%2Cc%20in%20cover_entries%7D%0A%0A%20%20%20%20BUDGET_DEFAULT%20%3D%2010%0A%0A%20%20%20%20total_pop%20%3D%20sum(POP_G6.values())%0A%20%20%20%20print(f%22Communities%3A%2015%20%20%7C%20%20Candidate%20sites%3A%207%20%20%7C%20%20Total%20population%3A%20%7Btotal_pop%7D%22)%0A%20%20%20%20print(f%22Default%20budget%3A%20%7BBUDGET_DEFAULT%7D%20%20%7C%20%20Total%20cost%20if%20all%20built%3A%20%7Bsum(COST_G6.values())%3A.1f%7D%22)%0A%20%20%20%20return%20BUDGET_DEFAULT%2C%20COST_G6%2C%20COVER_G6%2C%20POP_G6%2C%20total_pop%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%20BUDGET%0A%0A%20%20%20%20Compile%20and%20load%20the%20model%20once.%20Run%20it%20multiple%20times%20with%20different%20%60BUDGET%60%20values%20via%20%60exec_params%60.%20Each%20run%20finds%20the%20maximum%20coverage%20achievable%20within%20the%20given%20budget.%20Results%20are%20collected%20into%20a%20%60pd.Series%60%20indexed%20by%20budget%20value.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(BUDGET_DEFAULT%2C%20COST_G6%2C%20COVER_G6%2C%20MODELS%2C%20POP_G6%2C%20mp%2C%20pd)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fg6transmit_p.mos'%2C%20f'%7BMODELS%7D%2Fg6transmit_p.bim')%0A%20%20%20%20_model%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fg6transmit_p.bim')%0A%20%20%20%20_model.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20budget_values%20%3D%20%5B5%2C%206%2C%207%2C%208%2C%209%2C%2010%2C%2011%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20Parametric%20run%3A%20vary%20BUDGET%0A%20%20%20%20sensitivity%20%3D%20%7B%7D%0A%20%20%20%20for%20budget%20in%20budget_values%3A%0A%20%20%20%20%20%20%20%20out%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'BUDGET'%3A%20budget%7D%2C%20input_data%3D%7B'COST'%3A%20COST_G6%2C%20'COVER'%3A%20COVER_G6%2C%20'POP'%3A%20POP_G6%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%20sensitivity%5Bbudget%5D%20%3D%20out%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'BUDGET%3D%7Bbudget%7D%3A%20no%20solution%20(%7B_model.prob_status%7D)')%0A%20%20%20%20coverage_series%20%3D%20pd.Series(sensitivity%2C%20name%3D'Population%20covered')%0A%20%20%20%20coverage_series.index.name%20%3D%20'Budget'%0A%20%20%20%20print('Coverage%20sensitivity%3A')%0A%20%20%20%20print(coverage_series.apply(lambda%20v%3A%20f'%7Bv%3A.0f%7D').to_string())%0A%20%20%20%20out_default%20%3D%20_model.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'BUDGET'%3A%20BUDGET_DEFAULT%7D%2C%20input_data%3D%7B'COST'%3A%20COST_G6%2C%20'COVER'%3A%20COVER_G6%2C%20'POP'%3A%20POP_G6%7D)%0A%20%20%20%20if%20_model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_g6%20%3D%20out_default%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_build%20%3D%20out_default%5B'sol_build'%5D%0A%20%20%20%20%20%20%20%20sol_covered%20%3D%20out_default%5B'sol_covered'%5D%0A%20%20%20%20%20%20%20%20built_sites%20%3D%20%5Bp%20for%20p%20in%20range(1%2C%208)%20if%20sol_build.get(p%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20actual_cost%20%3D%20sum((COST_G6%5Bp%5D%20for%20p%20in%20built_sites))%0A%20%20%20%20%23%20Best%20solution%20at%20default%20budget%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20print(f'Default%20solution%20(budget%3D%7BBUDGET_DEFAULT%7D)%3A%20coverage%3D%7Bobj_val_g6%3A.0f%7D%2C%20sites%3D%7Bbuilt_sites%7D%2C%20cost%3D%7Bactual_cost%3A.1f%7D')%0A%20%20%20%20return%20coverage_series%2C%20obj_val_g6%2C%20sensitivity%2C%20sol_covered%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)%20coverage%20sensitivity%20curve%20vs%20budget%3B%20(right)%20coverage%20breakdown%20at%20the%20default%20budget%20showing%20which%20communities%20are%20covered%20and%20which%20are%20not.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20BUDGET_DEFAULT%2C%0A%20%20%20%20POP_G6%2C%0A%20%20%20%20coverage_series%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20mpatches%2C%0A%20%20%20%20obj_val_g6%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20sensitivity%2C%0A%20%20%20%20sol_covered%2C%0A%20%20%20%20total_pop%2C%0A)%3A%0A%20%20%20%20if%20coverage_series%20is%20not%20None%20and%20(not%20coverage_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(coverage_series.index%2C%20coverage_series.values%2C%20marker%3D'o'%2C%20color%3D'%231E88E5'%2C%20linewidth%3D2)%20%20%23%20Left%3A%20sensitivity%20curve%0A%20%20%20%20%20%20%20%20_ax.axvline(BUDGET_DEFAULT%2C%20color%3D'%23E53935'%2C%20linestyle%3D'--'%2C%20lw%3D1.5%2C%20label%3Df'Default%20budget%3D%7BBUDGET_DEFAULT%7D')%0A%20%20%20%20%20%20%20%20_ax.axhline(total_pop%2C%20color%3D'%2390A4AE'%2C%20linestyle%3D'%3A'%2C%20lw%3D1.5%2C%20label%3Df'Total%20population%3D%7Btotal_pop%7D')%0A%20%20%20%20%20%20%20%20for%20b%2C%20v%20in%20sensitivity.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(f'%7Bv%3A.0f%7D'%2C%20(b%2C%20v)%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('Budget')%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Population%20covered%20(thousands)')%0A%20%20%20%20%20%20%20%20_ax.set_title('G-6%20Transmitter%20placement%20-%20coverage%20sensitivity')%0A%20%20%20%20%20%20%20%20_ax.legend(fontsize%3D8)%0A%20%20%20%20%20%20%20%20ax2%20%3D%20_axes%5B1%5D%0A%20%20%20%20%20%20%20%20if%20'sol_covered'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pops%20%3D%20%5BPOP_G6%5Bc%5D%20for%20c%20in%20range(1%2C%2016)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20covered%20%3D%20%5Bsol_covered.get(c%2C%200)%20%3E%200.5%20for%20c%20in%20range(1%2C%2016)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20colors_c%20%3D%20%5B'%231E88E5'%20if%20cv%20else%20'%23BDBDBD'%20for%20cv%20in%20covered%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20bars%20%3D%20ax2.bar(range(1%2C%2016)%2C%20pops%2C%20color%3Dcolors_c%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.set_xticks(range(1%2C%2016))%20%20%23%20Right%3A%20community%20coverage%20at%20default%20budget%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.set_xticklabels(%5Bf'C%7Bc%7D'%20for%20c%20in%20range(1%2C%2016)%5D%2C%20fontsize%3D7)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.set_ylabel('Population%20(thousands)')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.set_title(f'Coverage%20at%20budget%3D%7BBUDGET_DEFAULT%7D%20%20(%7Bobj_val_g6%3A.0f%7D%2F%7Btotal_pop%7Dk%20covered)')%0A%20%20%20%20%20%20%20%20%20%20%20%20cov_p%20%3D%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'Covered')%0A%20%20%20%20%20%20%20%20%20%20%20%20unc_p%20%3D%20mpatches.Patch(color%3D'%23BDBDBD'%2C%20label%3D'Not%20covered')%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.legend(handles%3D%5Bcov_p%2C%20unc_p%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%20results%20available%20-%20skipping%20transmitter%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
f94817bdae19f06bee28840078e1ae95