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%20Public%20Services%22%0A%23%20description%20%3D%20%22Water%2C%20CCTV%2C%20districting%2C%20gritting%2C%20DEA%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-moselpy-publicservices.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%20J%3A%20Public%20Services%20and%20Utilities**%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_public_services.py***%20-%20Public%20services%20and%20utilities%20problems%20from%20the%20Xpress%20Applications%20Book%20-%20water%20supply%20network%20flow%2C%20CCTV%20camera%20placement%2C%20electoral%20redistricting%2C%20road%20gritting%20circuits%2C%20tax%20office%20location%2C%20and%20hospital%20efficiency%20analysis%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%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%22public_services%22)%0A%20%20%20%20DATA%20%20%20%3D%20str(_root%20%2F%20%22data%22%20%2F%20%22public_services%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%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%20Public%20services%20and%20utilities%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%20IP%20examples%20from%20Chapter%20J%20of%20the%20Xpress%20Applications%20Book%2C%20solved%20via%20%5BMoselPy%5D(https%3A%2F%2Fpypi.org%2Fproject%2Fmoselpy%2F).%20Examples%20include%20water%20supply%20network%20flow%2C%20CCTV%20camera%20placement%2C%20electoral%20redistricting%2C%20road%20gritting%20circuits%2C%20tax%20office%20location%2C%20and%20hospital%20efficiency%20analysis.%0A%0A%20%20%20%20%3E%20**Mosel%20source%20files%3A**%20%5B%60models%2Fpublic_services%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services)%20%26nbsp%3B%7C%26nbsp%3B%20**Data%20files%3A**%20%5B%60data%2Fpublic_services%2F%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Ftree%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fdata%2Fpublic_services)%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_10)%0A%0A%20%20%20%20%23%23%23%20MoselPy%20features%20demonstrated%20in%20this%20chapter%0A%0A%20%20%20%20-%20**%60ARCS%3A%20range%60%20auto-populated%20from%20arc%20data**%3A%20PIPE%20dict%20keys%20populate%20the%20ARCS%20range%20automatically%20(J-1).%0A%20%20%20%20-%20**Dynamic%20sparse%20array%20with%20%60exists()%60**%3A%20STREET%20and%20LEN%20adjacency%20matrices%20populated%20from%20sparse%20Python%20dicts%20(J-2%2C%20J-4).%0A%20%20%20%20-%20**Python%20preprocessing%20passed%20to%20Mosel**%3A%20J-3%20enumerates%20all%20valid%20electoral%20districts%20in%20Python%20using%20a%20recursive%20algorithm%2C%20then%20passes%20the%20computed%20%60DISTR%60%20and%20%60MAJ%60%20arrays%20to%20a%20MIP-only%20Mosel%20model%20via%20%60input_data%60.%0A%20%20%20%20-%20**Eulerian%20circuit%20extracted%20in%20Python**%3A%20J-4%20solves%20the%20arc-frequency%20LP%20in%20Mosel%2C%20then%20runs%20Hierholzer's%20algorithm%20in%20Python%20to%20extract%20the%20tour%20from%20the%20returned%20arc%20usage%20dict.%0A%20%20%20%20-%20**Floyd-Warshall%20inside%20Mosel**%3A%20distance%20preprocessing%20runs%20inside%20a%20single%20%60execute_model%60%20call%20(J-5).%0A%20%20%20%20-%20**Named%20%60linctr%60%20reassigned%20in%20a%20loop**%3A%20DEA%20model%20constraints%20redefined%20per%20hospital%3B%20efficiency%20scores%20collected%20into%20%60pd.Series%60%20(J-6).%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%20J-1%20Water%20supply%20network%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%20J%2C%20Example%201*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60j1water_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj1water_p.mos)%0A%0A%20%20%20%20A%20water%20transport%20network%20connects%20a%20source%20node%20(a%20reservoir%2C%20node%2011)%20to%20a%20sink%20node%20(a%20city%2C%20node%2012)%20via%2010%20intermediate%20nodes.%20Twenty%20pipes%20with%20individual%20capacities%20carry%20water.%20The%20model%20maximizes%20total%20flow%20from%20source%20to%20sink%20subject%20to%20flow%20conservation%20and%20capacity%20constraints.%0A%0A%20%20%20%20The%20%60ARCS%3A%20range%60%20in%20Mosel%20is%20automatically%20populated%20from%20the%20%60PIPE%60%20data%20dict%20keys%2C%20so%20no%20arc%20count%20needs%20to%20be%20specified%20explicitly.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BARCS%7D%24%20(auto-populated%20from%20PIPE)%2C%20%24%5Ctext%7BNODES%7D%20%3D%201..12%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BPIPE%7D_%7Ba%2C1%7D%24%2C%20%24%5Ctext%7BPIPE%7D_%7Ba%2C2%7D%24%20-%20from%2Fto%20nodes%3B%20%24%5Ctext%7BCAP%7D_a%24%20-%20pipe%20capacity%3B%20%24%5Ctext%7BSOURCE%7D%3D11%24%2C%20%24%5Ctext%7BSINK%7D%3D12%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bflow%7D_a%20%5Cgeq%200%24%20-%20flow%20on%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%7Bmaximize%7D%20%20%20%5Cquad%20%26%20%5Csum_%7Ba%3A%5C%2C%20%5Ctext%7BPIPE%7D_%7Ba%2C2%7D%3D12%7D%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%20%5Ctext%7BPIPE%7D_%7Ba%2C1%7D%3Dn%7D%20%5Cmathit%7Bflow%7D_a%20%3D%20%5Csum_%7Ba%3A%5C%2C%20%5Ctext%7BPIPE%7D_%7Ba%2C2%7D%3Dn%7D%20%5Cmathit%7Bflow%7D_a%20%5Cquad%20%5Cforall%5C%2C%20n%20%5Cneq%20%5C%7B11%2C12%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%5Cleq%20%5Ctext%7BCAP%7D_a%20%5Cquad%20%5Cforall%5C%2C%20a%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%20arcs%20as%20a%20dict%20with%20%60(arc_index%2C%201)%60%20and%20%60(arc_index%2C%202)%60%20keys%20for%20the%20from-%20and%20to-nodes%2C%20plus%20%60CAP%60%20as%20a%201-indexed%20capacity%20dict.%20The%20%60ARCS%3A%20range%60%20in%20Mosel%20auto-extends%20to%20cover%20all%20arc%20indices%20present%20in%20%60PIPE%60.%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%20pipes_raw%20%3D%20%5B(1%2C%203)%2C%20(1%2C%205)%2C%20(1%2C%206)%2C%20(2%2C%205)%2C%20(2%2C%206)%2C%20(3%2C%204)%2C%20(3%2C%205)%2C%20(4%2C%208)%2C%20(4%2C%209)%2C%20(5%2C%208)%2C%20(5%2C%209)%2C%20(5%2C%2010)%2C%20(6%2C%207)%2C%20(7%2C%209)%2C%20(7%2C%2010)%2C%20(8%2C%2012)%2C%20(9%2C%2012)%2C%20(10%2C%2012)%2C%20(11%2C%201)%2C%20(11%2C%202)%5D%0A%20%20%20%20PIPE%20%3D%20%7B%7D%0A%20%20%20%20for%20_a%2C%20(_frm%2C%20_to)%20in%20enumerate(pipes_raw%2C%20start%3D1)%3A%0A%20%20%20%20%20%20%20%20PIPE%5B_a%2C%201%5D%20%3D%20_frm%0A%20%20%20%20%20%20%20%20PIPE%5B_a%2C%202%5D%20%3D%20_to%0A%20%20%20%20CAP%20%3D%20%7Ba%3A%20c%20for%20a%2C%20c%20in%20enumerate(%5B20%2C%2015%2C%2012%2C%206%2C%2022%2C%2015%2C%2010%2C%207%2C%2010%2C%2010%2C%2015%2C%2015%2C%2022%2C%2010%2C%2010%2C%2018%2C%2015%2C%2020%2C%2035%2C%2025%5D%2C%20start%3D1)%7D%0A%20%20%20%20SOURCE%2C%20SINK%20%3D%20(11%2C%2012)%0A%20%20%20%20pipe_df%20%3D%20pd.DataFrame(%7B'From'%3A%20%5BPIPE%5Ba%2C%201%5D%20for%20a%20in%20range(1%2C%2021)%5D%2C%20'To'%3A%20%5BPIPE%5Ba%2C%202%5D%20for%20a%20in%20range(1%2C%2021)%5D%2C%20'Cap'%3A%20%5BCAP%5Ba%5D%20for%20a%20in%20range(1%2C%2021)%5D%7D%2C%20index%3Drange(1%2C%2021))%0A%20%20%20%20pipe_df.index.name%20%3D%20'Arc'%0A%20%20%20%20print(pipe_df.to_string())%0A%20%20%20%20return%20CAP%2C%20PIPE%2C%20SINK%2C%20SOURCE%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%60PIPE%60%20and%20%60CAP%60%20are%20passed%20via%20%60input_data%60%3B%20%60SOURCE%60%20and%20%60SINK%60%20are%20overridden%20via%20%60exec_params%60%20even%20though%20they%20match%20the%20model%20defaults%2C%20to%20show%20the%20pattern%20explicitly.%20The%20%60ARCS%60%20range%20auto-populates%20from%20the%20%60PIPE%60%20dict%20keys.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(CAP%2C%20MODELS%2C%20PIPE%2C%20SINK%2C%20SOURCE%2C%20mp)%3A%0A%20%20%20%20model%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fj1water_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'SOURCE'%3A%20SOURCE%2C%20'SINK'%3A%20SINK%7D%2C%20input_data%3D%7B'PIPE'%3A%20PIPE%2C%20'CAP'%3A%20CAP%7D)%0A%20%20%20%20if%20model.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_j1%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'Maximum%20total%20flow%3A%20%7Bobj_val_j1%3A.0f%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%3E5%7D%20%7B'To'%3A%3E5%7D%20%7B'Flow'%3A%3E6%7D%20%7B'Cap'%3A%3E5%7D')%0A%20%20%20%20%20%20%20%20for%20_a%20in%20range(1%2C%2021)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_f%20%3D%20sol_flow.get(_a%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_f%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%7BPIPE%5B_a%2C%201%5D%3A%3E5%7D%20%7BPIPE%5B_a%2C%202%5D%3A%3E5%7D%20%7B_f%3A%3E6.1f%7D%20%7BCAP%5B_a%5D%3A%3E5%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel.prob_status%7D)')%0A%20%20%20%20return%20obj_val_j1%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%20Network%20diagram%20showing%20flow%20on%20each%20pipe.%20Node%20size%20reflects%20the%20number%20of%20incident%20pipes%3B%20arrow%20thickness%20and%20label%20show%20the%20flow.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(PIPE%2C%20SINK%2C%20SOURCE%2C%20mo%2C%20mpatches%2C%20obj_val_j1%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%20_pos%20%3D%20%7B1%3A%20(30%2C%2035)%2C%202%3A%20(30%2C%2015)%2C%203%3A%20(53%2C%2041)%2C%204%3A%20(70%2C%2041)%2C%205%3A%20(62%2C%2023)%2C%206%3A%20(53%2C%207)%2C%207%3A%20(70%2C%207)%2C%208%3A%20(95%2C%2041)%2C%209%3A%20(95%2C%2023)%2C%2010%3A%20(95%2C%207)%2C%2011%3A%20(6%2C%2023)%2C%2012%3A%20(119%2C%2023)%7D%20%20%23%20Node%20coordinates%20from%20original%20data%20(POS%20array)%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(11%2C%207))%0A%20%20%20%20%20%20%20%20_ax.set_xlim(-5%2C%20130)%0A%20%20%20%20%20%20%20%20_ax.set_ylim(-5%2C%2050)%0A%20%20%20%20%20%20%20%20for%20_a%20in%20range(1%2C%2021)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_frm%2C%20_to%20%3D%20(PIPE%5B_a%2C%201%5D%2C%20PIPE%5B_a%2C%202%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20fx%2C%20fy%20%3D%20_pos%5B_frm%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20tx%2C%20ty%20%3D%20_pos%5B_to%5D%20%20%23%20Draw%20arcs%0A%20%20%20%20%20%20%20%20%20%20%20%20_f%20%3D%20sol_flow.get(_a%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20'%231E88E5'%20if%20_f%20%3E%200.5%20else%20'%23BDBDBD'%0A%20%20%20%20%20%20%20%20%20%20%20%20_lw%20%3D%201%20%2B%20_f%20*%200.15%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(tx%2C%20ty)%2C%20xytext%3D(fx%2C%20fy)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3D_color%2C%20lw%3D_lw%2C%20connectionstyle%3D'arc3%2Crad%3D0.0'))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_f%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_mx%2C%20_my%20%3D%20((fx%20%2B%20tx)%20%2F%202%2C%20(fy%20%2B%20ty)%20%2F%202)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_mx%2C%20_my%2C%20f'%7B_f%3A.0f%7D'%2C%20fontsize%3D7%2C%20color%3D'%231E88E5'%2C%20ha%3D'center'%2C%20va%3D'bottom')%0A%20%20%20%20%20%20%20%20for%20_n%2C%20(_x%2C%20_y)%20in%20_pos.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20'%23E53935'%20if%20_n%20%3D%3D%20SOURCE%20else%20'%2343A047'%20if%20_n%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%3D250%2C%20color%3D_color%2C%20zorder%3D3)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_x%2C%20_y%2C%20str(_n)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20src_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3Df'Source%20(node%20%7BSOURCE%7D)')%0A%20%20%20%20%20%20%20%20sink_p%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3Df'Sink%20(node%20%7BSINK%7D)')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bsrc_p%2C%20sink_p%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_title(f'J-1%20Water%20supply%20network%20(max%20flow%20%3D%20%7Bobj_val_j1%3A.0f%7D)')%20%20%23%20Draw%20nodes%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%20J-2%20CCTV%20surveillance%20(IP)%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%20J%2C%20Example%202*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60j2bigbro_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj2bigbro_p.mos)%0A%0A%20%20%20%20A%20town%20council%20must%20cover%20all%20streets%20in%20a%20district%20with%20CCTV%20cameras.%20Cameras%20can%20be%20placed%20at%20any%20street%20intersection%3B%20a%20camera%20covers%20all%20streets%20incident%20to%20its%20node.%20The%20model%20finds%20the%20minimum%20number%20of%20cameras%20(minimum%20vertex%20cover).%0A%0A%20%20%20%20The%20street%20graph%20is%20encoded%20as%20a%20dynamic%20sparse%20adjacency%20matrix%20%60STREET%60.%20Only%20one%20direction%20per%20edge%20is%20stored%20in%20the%20data%3B%20the%20reverse%20direction%20is%20filled%20in%20by%20the%20model.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BNODES%7D%20%3D%201..49%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BSTREET%7D_%7Bn%2Cm%7D%24%20-%201%20if%20a%20street%20connects%20nodes%20%24n%24%20and%20%24m%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bplace%7D_n%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20-%201%20if%20camera%20placed%20at%20intersection%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_n%20%5Cmathit%7Bplace%7D_n%20%5C%5C%0A%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%5Cquad%20%26%20%5Cmathit%7Bplace%7D_n%20%2B%20%5Cmathit%7Bplace%7D_m%20%5Cgeq%201%20%5Cquad%20%5Cforall%5C%2C%20(n%2Cm)%20%5Cin%20%5Ctext%7Bstreets%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%20street%20network%20as%20a%20sparse%20dict%20with%20%60(node1%2C%20node2)%60%20tuple%20keys.%20Only%20one%20direction%20per%20street%20is%20needed%3B%20the%20model%20fills%20in%20the%20reverse%20direction.%20The%20%60STREET%60%20dynamic%20array%20auto-extends%20%60NODES%60%20from%20the%20dict%20keys.%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%20streets%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(1%2C2)%2C(1%2C3)%2C(2%2C39)%2C(2%2C41)%2C(3%2C4)%2C(3%2C11)%2C(3%2C12)%2C(3%2C16)%2C(4%2C5)%2C(4%2C6)%2C(4%2C9)%2C%0A%20%20%20%20%20%20%20%20(6%2C7)%2C(6%2C8)%2C(9%2C10)%2C(11%2C21)%2C(12%2C13)%2C(12%2C15)%2C(13%2C14)%2C(14%2C15)%2C(14%2C18)%2C(15%2C16)%2C%0A%20%20%20%20%20%20%20%20(15%2C19)%2C(16%2C20)%2C(17%2C18)%2C(18%2C19)%2C(19%2C20)%2C(20%2C21)%2C(21%2C22)%2C(22%2C23)%2C(22%2C25)%2C%0A%20%20%20%20%20%20%20%20(23%2C32)%2C(24%2C25)%2C(25%2C26)%2C(25%2C30)%2C(26%2C27)%2C(26%2C28)%2C(28%2C29)%2C(30%2C31)%2C(31%2C32)%2C%0A%20%20%20%20%20%20%20%20(31%2C33)%2C(32%2C38)%2C(32%2C39)%2C(33%2C34)%2C(33%2C37)%2C(34%2C35)%2C(35%2C36)%2C(37%2C38)%2C(37%2C43)%2C%0A%20%20%20%20%20%20%20%20(38%2C40)%2C(39%2C40)%2C(40%2C41)%2C(41%2C42)%2C(43%2C44)%2C(44%2C49)%2C(44%2C45)%2C(45%2C46)%2C(45%2C47)%2C(47%2C48)%2C%0A%20%20%20%20%5D%0A%20%20%20%20STREET%20%3D%20%7B(n%2C%20m)%3A%201%20for%20n%2C%20m%20in%20streets%7D%0A%0A%20%20%20%20print(f%22Intersections%3A%2049%20%20%20Streets%3A%20%7Blen(streets)%7D%22)%0A%20%20%20%20return%20STREET%2C%20streets%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%60STREET%60%20is%20passed%20as%20a%20sparse%20dict%3B%20the%20dynamic%20array%20in%20Mosel%20auto-creates%20entries%20only%20for%20the%20supplied%20keys.%20The%20model%20adds%20the%20reverse%20direction%20internally.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(MODELS%2C%20STREET%2C%20mp%2C%20streets)%3A%0A%20%20%20%20model_1%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fj2bigbro_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'STREET'%3A%20STREET%7D)%0A%20%20%20%20if%20model_1.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_j2%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_place%20%3D%20_output%5B'sol_place'%5D%0A%20%20%20%20%20%20%20%20cameras%20%3D%20sorted((n%20for%20n%20in%20range(1%2C%2050)%20if%20sol_place.get(n%2C%200)%20%3E%200.5))%0A%20%20%20%20%20%20%20%20print(f'Minimum%20cameras%20required%3A%20%7Bobj_val_j2%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Camera%20locations%3A%20%7Bcameras%7D')%0A%20%20%20%20%20%20%20%20uncovered%20%3D%20%5B(n%2C%20m)%20for%20n%2C%20m%20in%20streets%20if%20sol_place.get(n%2C%200)%20%3C%200.5%20and%20sol_place.get(m%2C%200)%20%3C%200.5%5D%0A%20%20%20%20%20%20%20%20print(f'Uncovered%20streets%3A%20%7Blen(uncovered)%7D%20(should%20be%200)')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_1.prob_status%7D)')%0A%20%20%20%20return%20obj_val_j2%2C%20sol_place%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%20diagram%20showing%20camera%20locations%20(filled%20red)%20vs%20uncovered%20nodes%20(gray).%20Streets%20covered%20by%20each%20camera%20are%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20mpatches%2C%20obj_val_j2%2C%20plt%2C%20sol_place%2C%20streets)%3A%0A%20%20%20%20if%20'sol_place'%20in%20locals()%3A%0A%20%20%20%20%20%20%20%20pos_raw%20%3D%20%5B%5B30%2C%205%5D%2C%20%5B83%2C%2024%5D%2C%20%5B10%2C%2042%5D%2C%20%5B22%2C%2038%5D%2C%20%5B39%2C%2041%5D%2C%20%5B25%2C%2032%5D%2C%20%5B40%2C%2035%5D%2C%20%5B30%2C%2020%5D%2C%20%5B21%2C%2044%5D%2C%20%5B30%2C%2048%5D%2C%20%5B30%2C%2051%5D%2C%20%5B10%2C%2052%5D%2C%20%5B9%2C%2056%5D%2C%20%5B9%2C%2068%5D%2C%20%5B13%2C%2064%5D%2C%20%5B18%2C%2060%5D%2C%20%5B12%2C%2079%5D%2C%20%5B15%2C%2077%5D%2C%20%5B19%2C%2074%5D%2C%20%5B23%2C%2070%5D%2C%20%5B35%2C%2061%5D%2C%20%5B40%2C%2065%5D%2C%20%5B42%2C%2066%5D%2C%20%5B28%2C%2093%5D%2C%20%5B45%2C%2079%5D%2C%20%5B49%2C%2084%5D%2C%20%5B52%2C%2090%5D%2C%20%5B58%2C%2087%5D%2C%20%5B60%2C%2081%5D%2C%20%5B50%2C%2071%5D%2C%20%5B60%2C%2075%5D%2C%20%5B62%2C%2065%5D%2C%20%5B70%2C%2082%5D%2C%20%5B66%2C%2092%5D%2C%20%5B72%2C%2096%5D%2C%20%5B68%2C%20105%5D%2C%20%5B79%2C%2086%5D%2C%20%5B84%2C%2075%5D%2C%20%5B72%2C%2060%5D%2C%20%5B88%2C%2068%5D%2C%20%5B95%2C%2070%5D%2C%20%5B102%2C%2092%5D%2C%20%5B92%2C%2092%5D%2C%20%5B90%2C%2097%5D%2C%20%5B86%2C%20106%5D%2C%20%5B76%2C%20107%5D%2C%20%5B90%2C%20106%5D%2C%20%5B89%2C%20109%5D%2C%20%5B95%2C%2099%5D%5D%20%20%23%20Node%20positions%20from%20original%20data%0A%20%20%20%20%20%20%20%20_pos%20%3D%20%7Bn%20%2B%201%3A%20(x%2C%20y)%20for%20n%2C%20(x%2C%20y)%20in%20enumerate(pos_raw)%7D%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(9%2C%2011))%0A%20%20%20%20%20%20%20%20for%20_n%2C%20m%20in%20streets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20_pos%5B_n%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_x2%2C%20_y2%20%3D%20_pos%5Bm%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.plot(%5B_x1%2C%20_x2%5D%2C%20%5B_y1%2C%20_y2%5D%2C%20color%3D'%23BDBDBD'%2C%20lw%3D1%2C%20zorder%3D1)%0A%20%20%20%20%20%20%20%20for%20_n%20in%20range(1%2C%2050)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_x%2C%20_y%20%3D%20_pos%5B_n%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20has_cam%20%3D%20sol_place.get(_n%2C%200)%20%3E%200.5%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(_x%2C%20_y%2C%20s%3D80%2C%20color%3D'%23E53935'%20if%20has_cam%20else%20'%2390CAF9'%2C%20zorder%3D2%2C%20edgecolors%3D'white'%2C%20lw%3D0.5)%20%20%23%20Draw%20streets%0A%20%20%20%20%20%20%20%20cam_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3Df'Camera%20(%7Bobj_val_j2%3A.0f%7D%20total)')%0A%20%20%20%20%20%20%20%20node_p%20%3D%20mpatches.Patch(color%3D'%2390CAF9'%2C%20label%3D'No%20camera')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bcam_p%2C%20node_p%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_title(f'J-2%20CCTV%20placement%20(%7Bobj_val_j2%3A.0f%7D%20cameras)')%20%20%23%20Draw%20nodes%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%20surveillance%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%20J-3%20Electoral%20redistricting%20(IP)%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%20J%2C%20Example%203*%0A%0A%20%20%20%20%3E%20**Mosel%20models%3A**%20%5B%60j3elect_mip_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj3elect_mip_p.mos)%20(MIP%20only%2C%20Python%20preprocessing)%2C%20%5B%60j3elect_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj3elect_p.mos)%20(original%2C%20with%20data%20preprocessing%20in%20Mosel)%0A%0A%20%20%20%20A%20capital%20is%20divided%20into%2014%20quarters%20that%20must%20be%20grouped%20into%206%20electoral%20districts.%20Each%20district%20must%20be%20contiguous%20(quarters%20must%20be%20adjacent)%20and%20have%20between%2030%2C000%20and%20100%2C000%20voters.%20The%20objective%20maximizes%20the%20number%20of%20districts%20where%20the%20target%20candidate%20wins%20a%20majority.%0A%0A%20%20%20%20In%20this%20notebook%20we%20move%20the%20district%20enumeration%20**to%20Python**%3A%20a%20recursive%20function%20mirrors%20the%20Mosel%20preprocessing%20from%20%60j3elect_p.mos%60%2C%20enumerating%20all%2048%20valid%20districts%20and%20computing%20their%20majority%20flags.%20The%20resulting%20%60DISTR%60%20and%20%60MAJ%60%20arrays%20are%20then%20passed%20directly%20to%20a%20compact%20**MIP-only**%20Mosel%20model%20via%20%60input_data%60.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BQUARTERS%7D%20%3D%201..14%24%2C%20%24%5Ctext%7BRDIST%7D%20%3D%201..48%24%20(all%20valid%20districts)%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BPOP%7D_q%24%2C%20%24%5Ctext%7BVOTES%7D_q%24%2C%20%24%5Ctext%7BMINPOP%7D%24%2C%20%24%5Ctext%7BMAXPOP%7D%24%2C%20%24%5Ctext%7BMINSINGLE%7D%24%2C%20%24%5Ctext%7BREQD%7D%3D6%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bchoose%7D_d%20%5Cin%20%5C%7B0%2C1%5C%7D%24%20-%201%20if%20district%20%24d%24%20is%20selected%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%20problem%20data%20and%20implement%20the%20district%20enumeration%20in%20Python.%20The%20%60enumerate_districts%60%20function%20recursively%20expands%20quarter%20sets%20by%20adding%20adjacent%20neighbors%2C%20saving%20any%20contiguous%20set%20within%20the%20population%20limits%20as%20a%20valid%20district.%20It%20mirrors%20the%20Mosel%20preprocessing%20in%20%60j3elect_p.mos%60%2C%20but%20runs%20entirely%20in%20Python.%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%20NEIGHB%20%3D%20%7B(1%2C%201)%3A%202%2C%20(1%2C%202)%3A%205%2C%20(2%2C%201)%3A%203%2C%20(2%2C%202)%3A%205%2C%20(3%2C%201)%3A%204%2C%20(3%2C%202)%3A%205%2C%20(4%2C%201)%3A%205%2C%20(4%2C%202)%3A%2010%2C%20(5%2C%201)%3A%206%2C%20(5%2C%202)%3A%2010%2C%20(6%2C%201)%3A%207%2C%20(6%2C%202)%3A%208%2C%20(7%2C%201)%3A%208%2C%20(7%2C%202)%3A%209%2C%20(8%2C%201)%3A%209%2C%20(8%2C%202)%3A%2010%2C%20(8%2C%203)%3A%2011%2C%20(9%2C%201)%3A%2011%2C%20(9%2C%202)%3A%2012%2C%20(10%2C%201)%3A%2011%2C%20(10%2C%202)%3A%2013%2C%20(11%2C%201)%3A%2012%2C%20(11%2C%202)%3A%2013%2C%20(12%2C%201)%3A%2014%2C%20(13%2C%201)%3A%2014%7D%0A%20%20%20%20POP%20%3D%20%7B1%3A%2030%2C%202%3A%2050%2C%203%3A%2020%2C%204%3A%2070%2C%205%3A%2020%2C%206%3A%2040%2C%207%3A%2030%2C%208%3A%2030%2C%209%3A%2040%2C%2010%3A%2060%2C%2011%3A%2010%2C%2012%3A%2060%2C%2013%3A%2040%2C%2014%3A%2040%7D%0A%20%20%20%20VOTES%20%3D%20%7B1%3A%2017.5%2C%202%3A%2015%2C%203%3A%2014.2%2C%204%3A%2042%2C%205%3A%2018%2C%206%3A%209%2C%207%3A%2012%2C%208%3A%2010%2C%209%3A%2026%2C%2010%3A%2034%2C%2011%3A%202.5%2C%2012%3A%2027%2C%2013%3A%2029%2C%2014%3A%2015%7D%0A%20%20%20%20MINPOP%2C%20MAXPOP%2C%20MINSINGLE%2C%20REQD%20%3D%20(30%2C%20100%2C%2050%2C%206)%0A%20%20%20%20QUARTERS%20%3D%20range(1%2C%2015)%0A%0A%20%20%20%20def%20enumerate_districts(QUARTERS%2C%20NEIGHB%2C%20POP%2C%20VOTES%2C%20MINPOP%2C%20MAXPOP%2C%20MINSINGLE)%3A%0A%20%20%20%20%20%20%20%20DISTR%2C%20MAJ%20%3D%20(%7B%7D%2C%20%7B%7D)%0A%20%20%20%20%20%20%20%20numd%20%3D%20%5B0%5D%0A%0A%20%20%20%20%20%20%20%20def%20save_distr(sQ)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20numd%5B0%5D%20%3D%20numd%5B0%5D%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20q%20in%20sQ%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DISTR%5Bnumd%5B0%5D%2C%20q%5D%20%3D%201%0A%0A%20%20%20%20%20%20%20%20def%20add_neighb(toadd%2C%20sQ)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nQ%20%3D%20sQ%20%7C%20%7Btoadd%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sum((POP%5Bq%5D%20for%20q%20in%20nQ))%20%3E%3D%20MINPOP%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_distr(nQ)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20idx%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20NEIGHB.get((toadd%2C%20idx))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20is%20None%3A%0A%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%20if%20sum((POP%5Bq%5D%20for%20q%20in%20nQ))%20%2B%20POP%5Bp%5D%20%3C%3D%20MAXPOP%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20add_neighb(p%2C%20nQ)%0A%20%20%20%20%20%20%20%20for%20q%20in%20QUARTERS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20POP%5Bq%5D%20%3E%3D%20MINSINGLE%20and%20q%20!%3D%2010%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_distr(%7Bq%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20idx%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20NEIGHB.get((q%2C%20idx))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20is%20None%3A%0A%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%20if%20POP%5Bq%5D%20%2B%20POP%5Bp%5D%20%3C%3D%20MAXPOP%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20add_neighb(p%2C%20%7Bq%7D)%0A%20%20%20%20%20%20%20%20for%20d%20in%20range(1%2C%20numd%5B0%5D%20%2B%201)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20qs%20%3D%20%5Bq%20for%20q%20in%20QUARTERS%20if%20DISTR.get((d%2C%20q)%2C%200)%20%3E%200%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20pop_d%20%3D%20sum((POP%5Bq%5D%20for%20q%20in%20qs))%0A%20%20%20%20%20%20%20%20%20%20%20%20votes_d%20%3D%20sum((VOTES%5Bq%5D%20for%20q%20in%20qs))%0A%20%20%20%20%20%20%20%20%20%20%20%20MAJ%5Bd%5D%20%3D%201%20if%20votes_d%20%2F%20pop_d%20%3E%3D%200.5%20else%200%0A%20%20%20%20%20%20%20%20return%20(DISTR%2C%20MAJ%2C%20numd%5B0%5D)%0A%20%20%20%20DISTR_J3%2C%20MAJ_J3%2C%20NDISTS%20%3D%20enumerate_districts(QUARTERS%2C%20NEIGHB%2C%20POP%2C%20VOTES%2C%20MINPOP%2C%20MAXPOP%2C%20MINSINGLE)%0A%20%20%20%20maj_count%20%3D%20sum(MAJ_J3.values())%0A%20%20%20%20print(f'Valid%20districts%20enumerated%3A%20%7BNDISTS%7D')%0A%20%20%20%20print(f'Districts%20with%20majority%3A%20%7Bmaj_count%7D%2F%7BNDISTS%7D')%0A%20%20%20%20print(f'Non-zero%20DISTR%20entries%3A%20%7Blen(DISTR_J3)%7D')%0A%20%20%20%20return%20DISTR_J3%2C%20MAJ_J3%2C%20NDISTS%2C%20POP%2C%20REQD%2C%20VOTES%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Run%20the%20MIP-only%20model%20%60j3elect_mip_p.mos%60%2C%20passing%20the%20Python-computed%20%60DISTR%60%20and%20%60MAJ%60%20arrays%20via%20%60input_data%60%20together%20with%20%60NDISTS%60%20via%20%60exec_params%60.%20The%20model%20selects%20the%20optimal%206%20districts%20without%20any%20internal%20preprocessing.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(DISTR_J3%2C%20MAJ_J3%2C%20MODELS%2C%20NDISTS%2C%20POP%2C%20REQD%2C%20VOTES%2C%20mp)%3A%0A%20%20%20%20model_2%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fj3elect_mip_p.mos'%2C%20exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'NDISTS'%3A%20NDISTS%2C%20'REQD'%3A%20REQD%7D%2C%20input_data%3D%7B'DISTR'%3A%20DISTR_J3%2C%20'MAJ'%3A%20MAJ_J3%7D)%0A%20%20%20%20if%20model_2.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_j3%20%3D%20_output%5B'obj_val'%5D%0A%20%20%20%20%20%20%20%20sol_choose%20%3D%20_output%5B'sol_choose'%5D%0A%20%20%20%20%20%20%20%20distr%20%3D%20_output%5B'DISTR'%5D%0A%20%20%20%20%20%20%20%20maj%20%3D%20_output%5B'MAJ'%5D%0A%20%20%20%20%20%20%20%20_chosen%20%3D%20sorted((d%20for%20d%2C%20v%20in%20sol_choose.items()%20if%20v%20%3E%200.5))%0A%20%20%20%20%20%20%20%20print(f'Maximum%20majority%20districts%3A%20%7Bobj_val_j3%3A.0f%7D%20out%20of%20%7BREQD%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20_d%20in%20_chosen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_quarters%20%3D%20sorted((q%20for%20q%20in%20range(1%2C%2015)%20if%20distr.get((_d%2C%20q)%2C%200)%20%3E%200.5))%0A%20%20%20%20%20%20%20%20%20%20%20%20_pop_d%20%3D%20sum((POP%5Bq%5D%20for%20q%20in%20_quarters))%0A%20%20%20%20%20%20%20%20%20%20%20%20_votes_d%20%3D%20sum((VOTES%5Bq%5D%20for%20q%20in%20_quarters))%0A%20%20%20%20%20%20%20%20%20%20%20%20_pct%20%3D%20100%20*%20_votes_d%20%2F%20_pop_d%0A%20%20%20%20%20%20%20%20%20%20%20%20win%20%3D%20'WIN'%20if%20maj.get(_d%2C%200)%20%3E%200.5%20else%20'lose'%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20District%20%7B_d%7D%3A%20quarters%20%7B_quarters%7D%20%20pop%3D%7B_pop_d%7Dk%20%20votes%3D%7B_pct%3A.1f%7D%25%20%20%5B%7Bwin%7D%5D')%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%20distr%2C%20maj%2C%20model_2%2C%20obj_val_j3%2C%20sol_choose%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%20showing%20vote%20percentage%20per%20chosen%20district%2C%20with%20winning%20districts%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20POP%2C%0A%20%20%20%20REQD%2C%0A%20%20%20%20VOTES%2C%0A%20%20%20%20distr%2C%0A%20%20%20%20maj%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20model_2%2C%0A%20%20%20%20mpatches%2C%0A%20%20%20%20obj_val_j3%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20sol_choose%2C%0A)%3A%0A%20%20%20%20if%20'sol_choose'%20in%20locals()%20and%20model_2.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20_chosen%20%3D%20sorted((d%20for%20d%2C%20v%20in%20sol_choose.items()%20if%20v%20%3E%200.5))%0A%20%20%20%20%20%20%20%20pcts%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20labels%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20colors%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_d%20in%20_chosen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_quarters%20%3D%20%5Bq%20for%20q%20in%20range(1%2C%2015)%20if%20distr.get((_d%2C%20q)%2C%200)%20%3E%200.5%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_pop_d%20%3D%20sum((POP%5Bq%5D%20for%20q%20in%20_quarters))%0A%20%20%20%20%20%20%20%20%20%20%20%20_votes_d%20%3D%20sum((VOTES%5Bq%5D%20for%20q%20in%20_quarters))%0A%20%20%20%20%20%20%20%20%20%20%20%20_pct%20%3D%20100%20*%20_votes_d%20%2F%20_pop_d%0A%20%20%20%20%20%20%20%20%20%20%20%20pcts.append(_pct)%0A%20%20%20%20%20%20%20%20%20%20%20%20labels.append(f'D%7B_d%7D%5Cn%7B_quarters%7D')%0A%20%20%20%20%20%20%20%20%20%20%20%20colors.append('%2343A047'%20if%20maj.get(_d%2C%200)%20%3E%200.5%20else%20'%23E53935')%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_bars%20%3D%20_ax.bar(range(len(_chosen))%2C%20pcts%2C%20color%3Dcolors%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax.axhline(50%2C%20color%3D'black'%2C%20linestyle%3D'--'%2C%20linewidth%3D1%2C%20label%3D'50%25%20majority')%0A%20%20%20%20%20%20%20%20for%20_i%2C%20(_bar%2C%20_pct)%20in%20enumerate(zip(_bars%2C%20pcts))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_bar.get_x()%20%2B%20_bar.get_width()%20%2F%202%2C%20_pct%20%2B%200.5%2C%20f'%7B_pct%3A.1f%7D%25'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_xticks(range(len(_chosen)))%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(labels%2C%20fontsize%3D7)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Vote%20share%20(%25)')%0A%20%20%20%20%20%20%20%20_ax.set_title(f'J-3%20Electoral%20districts%20(%7Bobj_val_j3%3A.0f%7D%2F%7BREQD%7D%20majority%20wins)')%0A%20%20%20%20%20%20%20%20win_p%20%3D%20mpatches.Patch(color%3D'%2343A047'%2C%20label%3D'Majority%20win')%0A%20%20%20%20%20%20%20%20lose_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Majority%20loss')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bwin_p%2C%20lose_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%20solution%20available%20-%20skipping%20electoral%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%20J-4%20Road%20gritting%20circuit%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%20J%2C%20Example%204*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60j4grit_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj4grit_p.mos)%0A%0A%20%20%20%20A%20gritting%20truck%20must%20traverse%20every%20street%20in%20a%20village%20at%20least%20once%2C%20starting%20from%20intersection%201.%20Streets%20are%20directed%20arcs%20with%20given%20lengths.%20The%20model%20finds%20the%20minimum%20total%20distance%20tour%20(Chinese%20postman%20problem).%0A%0A%20%20%20%20Decision%20variables%20represent%20arc%20traversal%20frequency.%20Flow%20balance%20constraints%20ensure%20every%20intersection%20is%20entered%20as%20many%20times%20as%20it%20is%20exited.%20After%20solving%2C%20the%20arc%20usage%20dict%20%60sol_use%60%20is%20returned%20to%20Python%2C%20which%20extracts%20the%20Eulerian%20circuit%20using%20Hierholzer's%20algorithm.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BISEC%7D%20%3D%201..12%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BLEN%7D_%7Bi%2Cj%7D%24%20-%20length%20of%20street%20from%20%24i%24%20to%20%24j%24%20(sparse)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Buse%7D_%7Bi%2Cj%7D%20%5Cgeq%201%24%20-%20frequency%20of%20traversal%20of%20street%20%24(i%2Cj)%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%20street%20lengths%20as%20a%20sparse%20dict%20with%20%60(from%2C%20to)%60%20tuple%20keys.%20The%20%60LEN%60%20dynamic%20array%20auto-extends%20from%20the%20keys%3B%20%60use%60%20variables%20are%20created%20only%20for%20existing%20streets.%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%20LEN%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(1%2C2)%3A150%2C(1%2C5)%3A165%2C(2%2C3)%3A130%2C(2%2C5)%3A230%2C(2%2C6)%3A160%2C(3%2C2)%3A140%2C%0A%20%20%20%20%20%20%20%20(3%2C4)%3A100%2C(4%2C3)%3A100%2C(4%2C8)%3A190%2C(5%2C1)%3A165%2C(5%2C6)%3A144%2C(6%2C2)%3A170%2C%0A%20%20%20%20%20%20%20%20(6%2C5)%3A144%2C(6%2C7)%3A128%2C(6%2C9)%3A218%2C(6%2C10)%3A174%2C(7%2C3)%3A200%2C(7%2C6)%3A122%2C%0A%20%20%20%20%20%20%20%20(7%2C8)%3A109%2C(7%2C11)%3A185%2C(8%2C4)%3A180%2C(8%2C11)%3A141%2C(8%2C12)%3A190%2C(9%2C5)%3A194%2C%0A%20%20%20%20%20%20%20%20(9%2C10)%3A148%2C(10%2C6)%3A174%2C(10%2C7)%3A233%2C(11%2C7)%3A185%2C(11%2C10)%3A135%2C(12%2C11)%3A110%2C%0A%20%20%20%20%7D%0A%20%20%20%20print(f%22Streets%3A%20%7Blen(LEN)%7D%22)%0A%20%20%20%20total_one_pass%20%3D%20sum(LEN.values())%0A%20%20%20%20print(f%22Total%20street%20length%20(one%20pass)%3A%20%7Btotal_one_pass%7D%22)%0A%20%20%20%20print(f%22Expected%20gritting%20length%20(with%20repeats)%3A%205990%22)%0A%20%20%20%20return%20(LEN%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.%20%60LEN%60%20is%20passed%20as%20a%20sparse%20dict%3B%20dynamic%20arc%20variables%20are%20created%20only%20for%20existing%20streets.%20The%20model%20returns%20%60obj_val%60%20and%20%60sol_use%60%20(arc%20traversal%20frequencies).%20Python%20then%20runs%20**Hierholzer's%20algorithm**%20to%20extract%20the%20Eulerian%20circuit%3A%20starting%20from%20intersection%201%2C%20the%20algorithm%20greedily%20follows%20unused%20edges%2C%20inserting%20sub-tours%20whenever%20it%20finds%20an%20intersection%20with%20remaining%20unused%20edges.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(LEN%2C%20MODELS%2C%20mp)%3A%0A%20%20%20%20from%20collections%20import%20defaultdict%0A%0A%20%20%20%20def%20hierholzer(sol_use%2C%20start%3D1)%3A%0A%20%20%20%20%20%20%20%20adj%20%3D%20defaultdict(list)%0A%20%20%20%20%20%20%20%20for%20(i%2C%20j)%2C%20v%20in%20sorted(sol_use.items())%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_%20in%20range(round(v))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20adj%5Bi%5D.append(j)%0A%20%20%20%20%20%20%20%20circuit%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20stack%20%3D%20%5Bstart%5D%0A%20%20%20%20%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20v%20%3D%20stack%5B-1%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj%5Bv%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20stack.append(adj%5Bv%5D.pop(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%20circuit.append(stack.pop())%0A%20%20%20%20%20%20%20%20circuit.reverse()%0A%20%20%20%20%20%20%20%20return%20circuit%0A%20%20%20%20model_3%2C%20_output%20%3D%20mp.execute_model(f'%7BMODELS%7D%2Fj4grit_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'LEN'%3A%20LEN%7D)%0A%20%20%20%20if%20model_3.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_j4%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%20tour_seq%20%3D%20hierholzer(sol_use%2C%20start%3D1)%0A%20%20%20%20%20%20%20%20print(f'Minimum%20gritting%20length%3A%20%7Bobj_val_j4%3A.0f%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20repeats%20%3D%20%7Barc%3A%20round(v)%20-%201%20for%20arc%2C%20v%20in%20sol_use.items()%20if%20round(v)%20%3E%201%7D%0A%20%20%20%20%20%20%20%20if%20repeats%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print('Streets%20traversed%20more%20than%20once%3A')%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20(_i%2C%20_j)%2C%20extra%20in%20sorted(repeats.items())%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7B_i%7D%20-%3E%20%7B_j%7D%3A%20%7Bextra%20%2B%201%7Dx%20(extra%20%7Bextra%7D)')%0A%20%20%20%20%20%20%20%20print(f'%5CnTour%20(%7Blen(tour_seq)%7D%20steps)%3A%20%7B'%20-%3E%20'.join((str(n)%20for%20n%20in%20tour_seq%5B%3A10%5D))%7D%20...')%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_j4%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%20Network%20visualization%20showing%20the%20gritting%20circuit.%20Arc%20thickness%20reflects%20traversal%20frequency%3B%20arcs%20traversed%20more%20than%20once%20are%20highlighted.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(LEN%2C%20mo%2C%20mpatches%2C%20obj_val_j4%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%20node_pos%20%3D%20%7B1%3A%20(2%2C%205)%2C%202%3A%20(3%2C%208)%2C%203%3A%20(5%2C%208)%2C%204%3A%20(6%2C%207)%2C%205%3A%20(2%2C%203)%2C%206%3A%20(4%2C%203)%2C%207%3A%20(6%2C%205)%2C%208%3A%20(8%2C%207)%2C%209%3A%20(4%2C%201)%2C%2010%3A%20(6%2C%203)%2C%2011%3A%20(8%2C%205)%2C%2012%3A%20(10%2C%207)%7D%20%20%23%20Approximate%20positions%20for%20the%2012%20intersections%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(9%2C%207))%0A%20%20%20%20%20%20%20%20max_use%20%3D%20max((round(v)%20for%20v%20in%20sol_use.values()))%20if%20sol_use%20else%201%0A%20%20%20%20%20%20%20%20for%20(_i%2C%20_j)%2C%20freq_v%20in%20sol_use.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20freq%20%3D%20round(freq_v)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_i%20not%20in%20node_pos%20or%20_j%20not%20in%20node_pos%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20_x1%2C%20_y1%20%3D%20node_pos%5B_i%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_x2%2C%20_y2%20%3D%20node_pos%5B_j%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_color%20%3D%20'%23E53935'%20if%20freq%20%3E%201%20else%20'%231E88E5'%0A%20%20%20%20%20%20%20%20%20%20%20%20_lw%20%3D%201%20%2B%20freq%20*%201.5%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(''%2C%20xy%3D(_x2%2C%20_y2)%2C%20xytext%3D(_x1%2C%20_y1)%2C%20arrowprops%3Ddict(arrowstyle%3D'-%3E'%2C%20color%3D_color%2C%20lw%3D_lw%2C%20connectionstyle%3D'arc3%2Crad%3D0.1'))%0A%20%20%20%20%20%20%20%20%20%20%20%20_mx%2C%20_my%20%3D%20((_x1%20%2B%20_x2)%20%2F%202%2C%20(_y1%20%2B%20_y2)%20%2F%202)%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_mx%2C%20_my%2C%20f'%7BLEN.get((_i%2C%20_j)%2C%200)%7D'%2C%20fontsize%3D6%2C%20color%3D'%23555'%2C%20ha%3D'center'%2C%20va%3D'center')%0A%20%20%20%20%20%20%20%20for%20_n%2C%20(_x%2C%20_y)%20in%20node_pos.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.scatter(_x%2C%20_y%2C%20s%3D300%2C%20color%3D'%2343A047'%2C%20zorder%3D3%2C%20edgecolors%3D'white')%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_x%2C%20_y%2C%20str(_n)%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20fontsize%3D8%2C%20fontweight%3D'bold'%2C%20color%3D'white')%0A%20%20%20%20%20%20%20%20once_p%20%3D%20mpatches.Patch(color%3D'%231E88E5'%2C%20label%3D'Traversed%20once')%0A%20%20%20%20%20%20%20%20twice_p%20%3D%20mpatches.Patch(color%3D'%23E53935'%2C%20label%3D'Traversed%20%3E1x')%0A%20%20%20%20%20%20%20%20_ax.legend(handles%3D%5Bonce_p%2C%20twice_p%5D%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_title(f'J-4%20Gritting%20circuit%20(total%3D%7Bobj_val_j4%3A.0f%7D)')%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%20gritting%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%20J-5%20Tax%20office%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%20J%2C%20Example%205*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60j5tax_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj5tax_p.mos)%0A%0A%20%20%20%20Twelve%20cities%20are%20connected%20by%20roads.%20Tax%20offices%20must%20be%20located%20in%20%60NUMLOC%60%20of%20the%20cities%20to%20minimize%20the%20total%20population-weighted%20travel%20distance.%20The%20model%20uses%20Floyd-Warshall%20to%20compute%20all-pairs%20shortest%20paths%20from%20the%20sparse%20road%20network%20before%20solving%20the%20MIP.%0A%0A%20%20%20%20The%20Floyd-Warshall%20preprocessing%20runs%20entirely%20inside%20Mosel%20in%20the%20same%20%60execute_model%60%20call%2C%20with%20Python%20only%20supplying%20the%20sparse%20road%20lengths%20and%20population.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BCITIES%7D%20%3D%201..12%24%0A%0A%20%20%20%20**Parameters%3A**%20%24%5Ctext%7BLEN%7D_%7Bc%2Cd%7D%24%20(sparse%20road%20lengths)%2C%20%24%5Ctext%7BPOP%7D_c%24%2C%20%24%5Ctext%7BNUMLOC%7D%3D3%24%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Bbuild%7D_c%20%5Cin%20%5C%7B0%2C1%5C%7D%24%3B%20%24%5Cmathit%7Bdepend%7D_%7Bc%2Cd%7D%20%5Cgeq%200%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%20road%20lengths%20as%20a%20sparse%20dict%20and%20populations%20as%20a%201-indexed%20dict.%20%60NUMLOC%60%20is%20set%20via%20%60exec_params%60%20to%20demonstrate%20how%20the%20number%20of%20offices%20can%20be%20varied%20from%20Python%20without%20changing%20the%20model.%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%20LEN_J5%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(1%2C2)%3A15%2C(1%2C5)%3A24%2C(1%2C7)%3A18%2C(2%2C3)%3A22%2C(3%2C4)%3A18%2C(3%2C5)%3A16%2C(3%2C9)%3A20%2C%0A%20%20%20%20%20%20%20%20(4%2C6)%3A12%2C(5%2C8)%3A12%2C(5%2C9)%3A24%2C(6%2C9)%3A12%2C(6%2C12)%3A22%2C(7%2C8)%3A15%2C(7%2C10)%3A22%2C%0A%20%20%20%20%20%20%20%20(8%2C9)%3A30%2C(8%2C11)%3A25%2C(9%2C11)%3A19%2C(9%2C12)%3A19%2C(10%2C11)%3A19%2C(11%2C12)%3A21%2C%0A%20%20%20%20%7D%0A%20%20%20%20POP_J5%20%3D%20%7B1%3A15%2C2%3A10%2C3%3A12%2C4%3A18%2C5%3A5%2C6%3A24%2C7%3A11%2C8%3A16%2C9%3A13%2C10%3A22%2C11%3A19%2C12%3A20%7D%0A%20%20%20%20NUMLOC_J5%20%3D%203%0A%0A%20%20%20%20total_pop%20%3D%20sum(POP_J5.values())%0A%20%20%20%20print(f%22Total%20population%3A%20%7Btotal_pop%7D%22)%0A%20%20%20%20print(f%22Number%20of%20offices%20to%20open%3A%20%7BNUMLOC_J5%7D%22)%0A%20%20%20%20print(f%22Road%20connections%3A%20%7Blen(LEN_J5)%7D%22)%0A%20%20%20%20return%20LEN_J5%2C%20NUMLOC_J5%2C%20POP_J5%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%20to%20allow%20varying%20%60NUMLOC%60%20via%20%60exec_params%60.%20The%20Floyd-Warshall%20distance%20computation%20runs%20automatically%20inside%20the%20model%20before%20the%20MIP%20is%20solved.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(LEN_J5%2C%20MODELS%2C%20NUMLOC_J5%2C%20POP_J5%2C%20mp)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fj5tax_p.mos'%2C%20f'%7BMODELS%7D%2Fj5tax_p.bim')%0A%20%20%20%20model_4%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fj5tax_p.bim')%0A%20%20%20%20model_4.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_4.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'NUMLOC'%3A%20NUMLOC_J5%7D%2C%20input_data%3D%7B'LEN'%3A%20LEN_J5%2C%20'POP'%3A%20POP_J5%7D)%0A%20%20%20%20if%20model_4.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20obj_val_j5%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_depend%20%3D%20_output%5B'sol_depend'%5D%0A%20%20%20%20%20%20%20%20dist_matrix%20%3D%20_output%5B'DIST'%5D%0A%20%20%20%20%20%20%20%20offices%20%3D%20sorted((c%20for%20c%20in%20range(1%2C%2013)%20if%20sol_build.get(c%2C%200)%20%3E%200.5))%0A%20%20%20%20%20%20%20%20avg_dist%20%3D%20obj_val_j5%20%2F%20sum(POP_J5.values())%0A%20%20%20%20%20%20%20%20print(f'Total%20weighted%20distance%3A%20%7Bobj_val_j5%3A%2C.0f%7D')%0A%20%20%20%20%20%20%20%20print(f'Average%20distance%20per%20inhabitant%3A%20%7Bavg_dist%3A.2f%7D')%0A%20%20%20%20%20%20%20%20print(f'%5CnOffice%20locations%3A%20%7Boffices%7D')%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20for%20office%20in%20offices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20served%20%3D%20sorted((c%20for%20c%20in%20range(1%2C%2013)%20if%20sol_depend.get((c%2C%20office)%2C%200)%20%3E%200.5))%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20Office%20at%20city%20%7Boffice%7D%3A%20serves%20cities%20%7Bserved%7D')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_4.prob_status%7D)')%0A%20%20%20%20return%20(model_4%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%23%23%20Sensitivity%20analysis%3A%20varying%20number%20of%20offices%0A%0A%20%20%20%20The%20model%20is%20already%20compiled.%20Run%20with%20different%20%60NUMLOC%60%20values%20to%20see%20how%20average%20travel%20distance%20decreases%20as%20more%20offices%20are%20added.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(LEN_J5%2C%20POP_J5%2C%20model_4%2C%20pd)%3A%0A%20%20%20%20numloc_range%20%3D%20range(1%2C%207)%0A%20%20%20%20sens_results%20%3D%20%7B%7D%0A%20%20%20%20for%20nl%20in%20numloc_range%3A%0A%20%20%20%20%20%20%20%20out%20%3D%20model_4.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%2C%20'NUMLOC'%3A%20nl%7D%2C%20input_data%3D%7B'LEN'%3A%20LEN_J5%2C%20'POP'%3A%20POP_J5%7D)%0A%20%20%20%20%20%20%20%20if%20model_4.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20sens_results%5Bnl%5D%20%3D%20out%5B'obj_val'%5D%20%2F%20sum(POP_J5.values())%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f'NUMLOC%3D%7Bnl%7D%3A%20infeasible%20(%7Bmodel_4.prob_status%7D)')%0A%20%20%20%20sens_series%20%3D%20pd.Series(sens_results%2C%20name%3D'Avg%20distance%20per%20inhabitant')%0A%20%20%20%20sens_series.index.name%20%3D%20'Offices'%0A%20%20%20%20print('Average%20distance%20vs%20number%20of%20offices%3A')%0A%20%20%20%20print(sens_series.apply(lambda%20v%3A%20f'%7Bv%3A.2f%7D').to_string())%0A%20%20%20%20return%20(sens_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%20Sensitivity%20curve%20showing%20average%20travel%20distance%20per%20inhabitant%20as%20a%20function%20of%20the%20number%20of%20offices%20opened.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(NUMLOC_J5%2C%20mo%2C%20plt%2C%20sens_series)%3A%0A%20%20%20%20if%20sens_series%20is%20not%20None%20and%20(not%20sens_series.empty)%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20_ax%20%3D%20plt.subplots(figsize%3D(7%2C%204))%0A%20%20%20%20%20%20%20%20_ax.plot(sens_series.index%2C%20sens_series.values%2C%20marker%3D'o'%2C%20color%3D'%231E88E5'%2C%20linewidth%3D2)%0A%20%20%20%20%20%20%20%20_ax.axvline(NUMLOC_J5%2C%20color%3D'red'%2C%20linestyle%3D'--'%2C%20linewidth%3D1.5%2C%20label%3Df'Default%20NUMLOC%3D%7BNUMLOC_J5%7D')%0A%20%20%20%20%20%20%20%20for%20_x%2C%20_y%20in%20sens_series.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.annotate(f'%7B_y%3A.2f%7D'%2C%20(_x%2C%20_y)%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('Number%20of%20tax%20offices')%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Avg%20distance%20per%20inhabitant')%0A%20%20%20%20%20%20%20%20_ax.set_title('J-5%20Tax%20office%20location%20-%20distance%20sensitivity')%0A%20%20%20%20%20%20%20%20_ax.legend(fontsize%3D8)%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20sensitivity%20data%20-%20skipping%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%20J-6%20Hospital%20efficiency%20(DEA)%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%20J%2C%20Example%206*%0A%0A%20%20%20%20%3E%20**Mosel%20model%3A**%20%5B%60j6hospit_p.mos%60%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%2Fblob%2Fmain%2Fmarimo%2Fpublic%2Fmoselpy%2Fmodels%2Fpublic_services%2Fj6hospit_p.mos)%0A%0A%20%20%20%20Data%20Envelopment%20Analysis%20(DEA)%20measures%20the%20relative%20efficiency%20of%20four%20hospitals.%20Each%20hospital%20is%20compared%20against%20a%20fictitious%20best-practice%20reference%20constructed%20as%20a%20convex%20combination%20of%20all%20hospitals.%20The%20efficiency%20of%20the%20reference%20hospital%20is%20minimized%20subject%20to%20matching%20or%20exceeding%20the%20current%20hospital's%20service%20outputs%20while%20staying%20within%20its%20resource%20inputs.%0A%0A%20%20%20%20Named%20%60linctr%60%20constraints%20(%60LimServ%60%2C%20%60LimRes%60)%20are%20reassigned%20in-place%20in%20each%20loop%20iteration%20to%20adapt%20the%20reference%20to%20the%20hospital%20being%20evaluated.%20All%20four%20efficiencies%20are%20collected%20and%20exported%20together.%0A%0A%20%20%20%20**Sets%3A**%20%24%5Ctext%7BHOSP%7D%20%3D%201..4%24%3B%20%24%5Ctext%7BSERV%7D%24%2C%20%24%5Ctext%7BRES%7D%24%20(auto-populated)%0A%0A%20%20%20%20**Decision%20variables%3A**%20%24%5Cmathit%7Beff%7D%24%20-%20efficiency%20index%3B%20%24%5Cmathit%7Bcoef%7D_h%20%5Cgeq%200%24%20-%20combination%20weights%3B%20%24%5Cmathit%7Bfserv%7D_s%24%2C%20%24%5Cmathit%7Bfres%7D_r%24%20-%20reference%20hospital%20indicators%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%20service%20and%20resource%20indicators%20as%202D%20dicts%20with%20%60(indicator_name%2C%20hospital)%60%20keys.%20The%20%60SERV%60%20and%20%60RES%60%20sets%20auto-populate%20from%20the%20dict%20keys.%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%20INDSERV%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(%22admissions%22%2C%20%20%20%201)%3A%2030.12%2C%20(%22admissions%22%2C%20%20%20%202)%3A%2018.54%2C%0A%20%20%20%20%20%20%20%20(%22admissions%22%2C%20%20%20%203)%3A%2020.88%2C%20(%22admissions%22%2C%20%20%20%204)%3A%2010.42%2C%0A%20%20%20%20%20%20%20%20(%22consultations%22%2C%201)%3A%2013.54%2C%20(%22consultations%22%2C%202)%3A%2014.45%2C%0A%20%20%20%20%20%20%20%20(%22consultations%22%2C%203)%3A%20%208.52%2C%20(%22consultations%22%2C%204)%3A%2017.74%2C%0A%20%20%20%20%20%20%20%20(%22doctors%22%2C%20%20%20%20%20%20%201)%3A%2013%2C%20%20%20%20(%22doctors%22%2C%20%20%20%20%20%20%202)%3A%20%207%2C%0A%20%20%20%20%20%20%20%20(%22doctors%22%2C%20%20%20%20%20%20%203)%3A%20%208%2C%20%20%20%20(%22doctors%22%2C%20%20%20%20%20%20%204)%3A%2026%2C%0A%20%20%20%20%20%20%20%20(%22nurses%22%2C%20%20%20%20%20%20%20%201)%3A%2079%2C%20%20%20%20(%22nurses%22%2C%20%20%20%20%20%20%20%202)%3A%2055%2C%0A%20%20%20%20%20%20%20%20(%22nurses%22%2C%20%20%20%20%20%20%20%203)%3A%2047%2C%20%20%20%20(%22nurses%22%2C%20%20%20%20%20%20%20%204)%3A%2050%2C%0A%20%20%20%20%7D%0A%20%20%20%20INDRES%20%3D%20%7B%0A%20%20%20%20%20%20%20%20(%22non-med%20pers%22%2C%201)%3A%2090%2C%20%20%20%20(%22non-med%20pers%22%2C%202)%3A%2087%2C%0A%20%20%20%20%20%20%20%20(%22non-med%20pers%22%2C%203)%3A%2051%2C%20%20%20%20(%22non-med%20pers%22%2C%204)%3A%2066%2C%0A%20%20%20%20%20%20%20%20(%22expense%22%2C%20%20%20%20%20%201)%3A%2038.89%2C%20(%22expense%22%2C%20%20%20%20%20%202)%3A%20109.48%2C%0A%20%20%20%20%20%20%20%20(%22expense%22%2C%20%20%20%20%20%203)%3A%2040.43%2C%20(%22expense%22%2C%20%20%20%20%20%204)%3A%2048.41%2C%0A%20%20%20%20%20%20%20%20(%22beds%22%2C%20%20%20%20%20%20%20%20%201)%3A%2034%2C%20%20%20%20(%22beds%22%2C%20%20%20%20%20%20%20%20%202)%3A%2033%2C%0A%20%20%20%20%20%20%20%20(%22beds%22%2C%20%20%20%20%20%20%20%20%203)%3A%2020%2C%20%20%20%20(%22beds%22%2C%20%20%20%20%20%20%20%20%204)%3A%2033%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20hosp_names%20%3D%20%7B1%3A%20%22H1%22%2C%202%3A%20%22H2%22%2C%203%3A%20%22H3%22%2C%204%3A%20%22H4%22%7D%0A%20%20%20%20serv_names%20%3D%20sorted(%7Bs%20for%20s%2C%20_%20in%20INDSERV%7D)%0A%20%20%20%20res_names%20%20%3D%20sorted(%7Br%20for%20r%2C%20_%20in%20INDRES%7D)%0A%0A%20%20%20%20serv_df%20%3D%20pd.DataFrame(%7Bf%22H%7Bh%7D%22%3A%20%7Bs%3A%20INDSERV%5B(s%2Ch)%5D%20for%20s%20in%20serv_names%7D%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%20for%20h%20in%20range(1%2C5)%7D)%0A%20%20%20%20print(%22Service%20indicators%3A%22)%0A%20%20%20%20print(serv_df.to_string())%0A%20%20%20%20return%20INDRES%2C%20INDSERV%2C%20hosp_names%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%20solves%20DEA%20for%20all%20four%20hospitals%20in%20a%20single%20%60run()%60%20call%2C%20reassigning%20named%20%60linctr%60%20constraints%20inside%20a%20%60forall%60%20loop.%20The%20%60efficiencies%60%20array%20(indexed%201..4)%20is%20returned%20as%20a%20single%20output%20dict%20entry.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(INDRES%2C%20INDSERV%2C%20MODELS%2C%20hosp_names%2C%20mp%2C%20pd)%3A%0A%20%20%20%20mp.compile_model(f'%7BMODELS%7D%2Fj6hospit_p.mos'%2C%20f'%7BMODELS%7D%2Fj6hospit_p.bim')%0A%20%20%20%20model_5%20%3D%20mp.load_model(f'%7BMODELS%7D%2Fj6hospit_p.bim')%0A%20%20%20%20model_5.set_default_stream(mp.StreamType.OUTPUT%2C%20'null%3A')%0A%20%20%20%20_output%20%3D%20model_5.run(exec_params%3D%7B'DATASOURCE'%3A%20'moselpy%3A'%2C%20'OUTFILE'%3A%20'moselpy%3A'%7D%2C%20input_data%3D%7B'INDSERV'%3A%20INDSERV%2C%20'INDRES'%3A%20INDRES%7D)%0A%20%20%20%20if%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20efficiencies%20%3D%20_output%5B'efficiencies'%5D%0A%20%20%20%20%20%20%20%20eff_series%20%3D%20pd.Series(%7Bhosp_names%5Bh%5D%3A%20efficiencies.get(h%2C%200)%20for%20h%20in%20range(1%2C%205)%7D%2C%20name%3D'DEA%20efficiency')%0A%20%20%20%20%20%20%20%20print('Hospital%20efficiency%20scores%20(1.0%20%3D%20fully%20efficient)%3A')%0A%20%20%20%20%20%20%20%20print(eff_series.apply(lambda%20v%3A%20f'%7Bv%3A.4f%7D').to_string())%0A%20%20%20%20%20%20%20%20print()%0A%20%20%20%20%20%20%20%20inefficient%20%3D%20%5Bhosp_names%5Bh%5D%20for%20h%20in%20range(1%2C%205)%20if%20efficiencies.get(h%2C%201)%20%3C%200.9999%5D%0A%20%20%20%20%20%20%20%20if%20inefficient%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_h%20in%20range(1%2C%205)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20eff%20%3D%20efficiencies.get(_h%2C%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20eff%20%3C%200.9999%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20slack%20%3D%20(1%20-%20eff)%20*%20100%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f'%20%20%7Bhosp_names%5B_h%5D%7D%3A%20efficiency%20%7Beff%3A.4f%7D%20-%20could%20reduce%20resources%20by%20%7Bslack%3A.1f%7D%25')%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print('All%20hospitals%20are%20fully%20efficient.')%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(f'No%20solution%20found%20(%7Bmodel_5.prob_status%7D)')%0A%20%20%20%20return%20efficiencies%2C%20model_5%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%20Efficiency%20bar%20chart%20and%20radar%20plot%20comparing%20service%20and%20resource%20indicators%20across%20hospitals.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(INDSERV%2C%20efficiencies%2C%20mo%2C%20model_5%2C%20np%2C%20plt)%3A%0A%20%20%20%20if%20'efficiencies'%20in%20locals()%20and%20model_5.prob_status.is_solution_available%3A%0A%20%20%20%20%20%20%20%20_fig%2C%20axes%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(12%2C%204))%0A%20%20%20%20%20%20%20%20_ax%20%3D%20axes%5B0%5D%0A%20%20%20%20%20%20%20%20eff_vals%20%3D%20%5Befficiencies.get(h%2C%200)%20for%20h%20in%20range(1%2C%205)%5D%0A%20%20%20%20%20%20%20%20colors_eff%20%3D%20%5B'%2343A047'%20if%20v%20%3E%200.9999%20else%20'%23E53935'%20for%20v%20in%20eff_vals%5D%0A%20%20%20%20%20%20%20%20_bars%20%3D%20_ax.bar(%5Bf'H%7Bh%7D'%20for%20h%20in%20range(1%2C%205)%5D%2C%20eff_vals%2C%20color%3Dcolors_eff%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20_ax.axhline(1.0%2C%20color%3D'black'%2C%20linestyle%3D'--'%2C%20linewidth%3D1%2C%20label%3D'Fully%20efficient')%0A%20%20%20%20%20%20%20%20for%20_bar%2C%20v%20in%20zip(_bars%2C%20eff_vals)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.text(_bar.get_x()%20%2B%20_bar.get_width()%20%2F%202%2C%20v%20%2B%200.005%2C%20f'%7Bv%3A.3f%7D'%2C%20ha%3D'center'%2C%20va%3D'bottom'%2C%20fontsize%3D9)%0A%20%20%20%20%20%20%20%20_ax.set_ylim(0.85%2C%201.05)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel('Efficiency%20score')%0A%20%20%20%20%20%20%20%20_ax.set_title('J-6%20Hospital%20DEA%20efficiency')%0A%20%20%20%20%20%20%20%20_ax.legend(fontsize%3D8)%0A%20%20%20%20%20%20%20%20ax2%20%3D%20axes%5B1%5D%0A%20%20%20%20%20%20%20%20serv_display%20%3D%20%5B'admissions'%2C%20'consultations'%2C%20'doctors'%2C%20'nurses'%5D%0A%20%20%20%20%20%20%20%20_x%20%3D%20np.arange(len(serv_display))%0A%20%20%20%20%20%20%20%20width%20%3D%200.2%0A%20%20%20%20%20%20%20%20for%20_i%2C%20_h%20in%20enumerate(range(1%2C%205))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20vals%20%3D%20%5BINDSERV%5Bs%2C%20_h%5D%20for%20s%20in%20serv_display%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20max_v%20%3D%20%5Bmax((INDSERV%5Bs%2C%20hh%5D%20for%20hh%20in%20range(1%2C%205)))%20for%20s%20in%20serv_display%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20norm%20%3D%20%5Bv%20%2F%20m%20for%20v%2C%20m%20in%20zip(vals%2C%20max_v)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20ax2.bar(_x%20%2B%20_i%20*%20width%20-%200.3%2C%20norm%2C%20width%2C%20label%3Df'H%7B_h%7D'%2C%20edgecolor%3D'white')%0A%20%20%20%20%20%20%20%20ax2.set_xticks(_x)%0A%20%20%20%20%20%20%20%20ax2.set_xticklabels(serv_display%2C%20rotation%3D15%2C%20ha%3D'right'%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20ax2.set_ylabel('Normalized%20indicator%20(0-1)')%0A%20%20%20%20%20%20%20%20ax2.set_title('J-6%20Service%20indicators%20(normalized)')%0A%20%20%20%20%20%20%20%20ax2.legend(fontsize%3D8)%0A%20%20%20%20%20%20%20%20plt.tight_layout()%0A%20%20%20%20%20%20%20%20mo.output.append(plt.gcf())%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print('No%20solution%20available%20-%20skipping%20hospital%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
d2c24c52aa65cf8ab681e14fdce16ea5